Enhance DaliCloudBridge: add support for LTE UART transport and extend configuration options
Signed-off-by: Tony <tonylu@tony-cloud.com>
This commit is contained in:
+1
-1
@@ -19,7 +19,7 @@ idf_component_register(
|
||||
"src/gateway_cloud.cpp"
|
||||
"src/gateway_provisioning.cpp"
|
||||
INCLUDE_DIRS "include"
|
||||
REQUIRES mqtt cjson nvs_flash
|
||||
REQUIRES mqtt cjson nvs_flash esp_driver_uart
|
||||
)
|
||||
|
||||
set_property(TARGET ${COMPONENT_LIB} PROPERTY CXX_STANDARD 17)
|
||||
|
||||
@@ -4,10 +4,14 @@
|
||||
#include "dali_comm.hpp"
|
||||
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#ifdef ESP_PLATFORM
|
||||
#include "esp_event_base.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "mqtt_client.h"
|
||||
#endif
|
||||
|
||||
@@ -17,22 +21,35 @@ struct GatewayCloudConfig {
|
||||
std::string username = "device";
|
||||
std::string password;
|
||||
std::string topicPrefix = "devices";
|
||||
std::string cemiTransport = "mqtt";
|
||||
bool lteUartEnabled = false;
|
||||
int lteUartPort = -1;
|
||||
int lteTxPin = -1;
|
||||
int lteRxPin = -1;
|
||||
int lteBaudrate = 115200;
|
||||
int lteRxBufferSize = 2048;
|
||||
int lteTxBufferSize = 2048;
|
||||
int qos = 1;
|
||||
};
|
||||
|
||||
// DaliCloudBridge bridges MQTT cloud topics and local DALI frames.
|
||||
class DaliCloudBridge {
|
||||
public:
|
||||
using CemiDownlinkHandler = std::function<bool(const uint8_t* data, size_t len)>;
|
||||
|
||||
explicit DaliCloudBridge(DaliComm& comm);
|
||||
|
||||
bool start(const GatewayCloudConfig& config);
|
||||
void stop();
|
||||
bool isConnected() const;
|
||||
bool lteUartActive() const;
|
||||
DaliBridgeEngine& bridge() { return bridge_; }
|
||||
const DaliBridgeEngine& bridge() const { return bridge_; }
|
||||
void setCemiDownlinkHandler(CemiDownlinkHandler handler);
|
||||
|
||||
bool publishStatus(const std::string& status);
|
||||
bool publishRegister(const std::string& payloadJson);
|
||||
bool publishCemiFrame(const uint8_t* data, size_t len);
|
||||
|
||||
private:
|
||||
#ifdef ESP_PLATFORM
|
||||
@@ -44,19 +61,34 @@ class DaliCloudBridge {
|
||||
#endif
|
||||
|
||||
bool handleDownlink(const std::string& payload);
|
||||
bool handleCemiDownlink(const uint8_t* payload, size_t len);
|
||||
bool publishJSON(const std::string& topic, const std::string& payloadJson);
|
||||
bool publishBytes(const std::string& topic, const uint8_t* data, size_t len, int qos);
|
||||
bool startLteUart();
|
||||
void stopLteUart();
|
||||
bool publishLteUartBytes(const uint8_t* data, size_t len);
|
||||
void lteUartLoop();
|
||||
|
||||
std::string topicDown() const;
|
||||
std::string topicUp() const;
|
||||
std::string topicStatus() const;
|
||||
std::string topicRegister() const;
|
||||
std::string topicCemiDown() const;
|
||||
std::string topicCemiUp() const;
|
||||
std::string topicCemiStatus() const;
|
||||
|
||||
DaliComm& comm_;
|
||||
DaliBridgeEngine bridge_;
|
||||
GatewayCloudConfig config_;
|
||||
CemiDownlinkHandler cemi_downlink_handler_;
|
||||
std::atomic<bool> connected_{false};
|
||||
std::atomic<bool> lte_uart_started_{false};
|
||||
std::atomic<bool> lte_uart_stop_requested_{false};
|
||||
std::atomic<uint32_t> cemi_sequence_{0};
|
||||
|
||||
#ifdef ESP_PLATFORM
|
||||
static void lteUartTaskEntry(void* arg);
|
||||
esp_mqtt_client_handle_t client_ = nullptr;
|
||||
TaskHandle_t lte_uart_task_ = nullptr;
|
||||
#endif
|
||||
};
|
||||
|
||||
+342
-12
@@ -1,16 +1,26 @@
|
||||
#include "gateway_cloud.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cctype>
|
||||
#include <cstring>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
|
||||
#ifdef ESP_PLATFORM
|
||||
extern "C" {
|
||||
#include "cJSON.h"
|
||||
#include "driver/uart.h"
|
||||
#include "esp_log.h"
|
||||
}
|
||||
|
||||
namespace {
|
||||
constexpr const char* kTag = "dali_cloud_bridge";
|
||||
constexpr std::array<uint8_t, 4> kCemiEnvelopeMagic{'D', 'K', 'C', '1'};
|
||||
constexpr uint8_t kCemiDirectionDown = 1;
|
||||
constexpr uint8_t kCemiDirectionUp = 2;
|
||||
constexpr size_t kCemiEnvelopeHeaderSize = 14;
|
||||
constexpr size_t kMaxCemiEnvelopeSize = kCemiEnvelopeHeaderSize + 255 + 4096;
|
||||
|
||||
std::string jsonEscape(const std::string& value) {
|
||||
std::string out;
|
||||
@@ -143,20 +153,139 @@ bool isKnownBridgeRequestKey(const char* key) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string lowerCopy(const std::string& value) {
|
||||
std::string out(value);
|
||||
std::transform(out.begin(), out.end(), out.begin(), [](unsigned char ch) {
|
||||
return static_cast<char>(std::tolower(ch));
|
||||
});
|
||||
return out;
|
||||
}
|
||||
|
||||
bool cemiUsesMqtt(const GatewayCloudConfig& config) {
|
||||
const std::string mode = lowerCopy(config.cemiTransport);
|
||||
return mode.empty() || mode == "mqtt" || mode == "hybrid" ||
|
||||
mode == "mqtt_lte_uart" || mode == "both";
|
||||
}
|
||||
|
||||
bool cemiUsesLteUart(const GatewayCloudConfig& config) {
|
||||
if (config.lteUartEnabled) {
|
||||
return true;
|
||||
}
|
||||
const std::string mode = lowerCopy(config.cemiTransport);
|
||||
return mode == "lte_uart" || mode == "serial_lte" || mode == "uart" ||
|
||||
mode == "hybrid" || mode == "mqtt_lte_uart" || mode == "both";
|
||||
}
|
||||
|
||||
void writeBe16(uint8_t* data, uint16_t value) {
|
||||
data[0] = static_cast<uint8_t>((value >> 8) & 0xff);
|
||||
data[1] = static_cast<uint8_t>(value & 0xff);
|
||||
}
|
||||
|
||||
void writeBe32(uint8_t* data, uint32_t value) {
|
||||
data[0] = static_cast<uint8_t>((value >> 24) & 0xff);
|
||||
data[1] = static_cast<uint8_t>((value >> 16) & 0xff);
|
||||
data[2] = static_cast<uint8_t>((value >> 8) & 0xff);
|
||||
data[3] = static_cast<uint8_t>(value & 0xff);
|
||||
}
|
||||
|
||||
uint16_t readBe16(const uint8_t* data) {
|
||||
return static_cast<uint16_t>((static_cast<uint16_t>(data[0]) << 8) | data[1]);
|
||||
}
|
||||
|
||||
void consumeCemiSerialBuffer(std::vector<uint8_t>* buffer,
|
||||
const std::function<bool(const uint8_t*, size_t)>& handler) {
|
||||
if (buffer == nullptr) {
|
||||
return;
|
||||
}
|
||||
while (buffer->size() >= kCemiEnvelopeHeaderSize) {
|
||||
auto magic = std::search(buffer->begin(), buffer->end(), kCemiEnvelopeMagic.begin(),
|
||||
kCemiEnvelopeMagic.end());
|
||||
if (magic == buffer->end()) {
|
||||
buffer->clear();
|
||||
return;
|
||||
}
|
||||
if (magic != buffer->begin()) {
|
||||
buffer->erase(buffer->begin(), magic);
|
||||
}
|
||||
if (buffer->size() < kCemiEnvelopeHeaderSize) {
|
||||
return;
|
||||
}
|
||||
const size_t session_len = (*buffer)[10];
|
||||
const size_t cemi_len = readBe16(buffer->data() + 11);
|
||||
const size_t frame_len = kCemiEnvelopeHeaderSize + session_len + cemi_len;
|
||||
if (frame_len > kMaxCemiEnvelopeSize || cemi_len == 0) {
|
||||
buffer->erase(buffer->begin());
|
||||
continue;
|
||||
}
|
||||
if (buffer->size() < frame_len) {
|
||||
return;
|
||||
}
|
||||
handler(buffer->data(), frame_len);
|
||||
buffer->erase(buffer->begin(), buffer->begin() + static_cast<std::ptrdiff_t>(frame_len));
|
||||
}
|
||||
if (buffer->size() > kMaxCemiEnvelopeSize) {
|
||||
buffer->erase(buffer->begin(), buffer->end() - static_cast<std::ptrdiff_t>(kCemiEnvelopeHeaderSize));
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<uint8_t> buildCemiEnvelope(uint8_t direction, uint32_t sequence,
|
||||
const uint8_t* data, size_t len) {
|
||||
if (data == nullptr || len == 0 || len > 4096) {
|
||||
return {};
|
||||
}
|
||||
std::vector<uint8_t> out(kCemiEnvelopeHeaderSize + len);
|
||||
std::copy(kCemiEnvelopeMagic.begin(), kCemiEnvelopeMagic.end(), out.begin());
|
||||
out[4] = direction;
|
||||
out[5] = 0;
|
||||
writeBe32(&out[6], sequence);
|
||||
out[10] = 0;
|
||||
writeBe16(&out[11], static_cast<uint16_t>(len));
|
||||
out[13] = 0;
|
||||
std::memcpy(out.data() + kCemiEnvelopeHeaderSize, data, len);
|
||||
return out;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> decodeCemiEnvelope(const uint8_t* payload, size_t len) {
|
||||
if (payload == nullptr || len < kCemiEnvelopeHeaderSize ||
|
||||
std::memcmp(payload, kCemiEnvelopeMagic.data(), kCemiEnvelopeMagic.size()) != 0 ||
|
||||
payload[4] != kCemiDirectionDown) {
|
||||
return {};
|
||||
}
|
||||
const size_t session_len = payload[10];
|
||||
const size_t cemi_len = readBe16(&payload[11]);
|
||||
if (len != kCemiEnvelopeHeaderSize + session_len + cemi_len || cemi_len == 0) {
|
||||
return {};
|
||||
}
|
||||
const uint8_t* cemi = payload + kCemiEnvelopeHeaderSize + session_len;
|
||||
return std::vector<uint8_t>(cemi, cemi + cemi_len);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
#endif
|
||||
|
||||
DaliCloudBridge::DaliCloudBridge(DaliComm& comm) : comm_(comm), bridge_(comm) {}
|
||||
|
||||
bool DaliCloudBridge::start(const GatewayCloudConfig& config) {
|
||||
#ifdef ESP_PLATFORM
|
||||
stop();
|
||||
#endif
|
||||
config_ = config;
|
||||
if (config_.brokerURI.empty() || config_.deviceID.empty()) {
|
||||
const bool mqtt_requested = cemiUsesMqtt(config_) && !config_.brokerURI.empty() &&
|
||||
!config_.deviceID.empty();
|
||||
const bool lte_uart_requested = cemiUsesLteUart(config_);
|
||||
if (!mqtt_requested && !lte_uart_requested) {
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef ESP_PLATFORM
|
||||
if (client_ != nullptr) {
|
||||
stop();
|
||||
bool started = false;
|
||||
if (lte_uart_requested) {
|
||||
started = startLteUart() || started;
|
||||
}
|
||||
|
||||
if (!mqtt_requested) {
|
||||
connected_.store(started);
|
||||
return started;
|
||||
}
|
||||
|
||||
esp_mqtt_client_config_t mqttCfg = {};
|
||||
@@ -168,7 +297,7 @@ bool DaliCloudBridge::start(const GatewayCloudConfig& config) {
|
||||
client_ = esp_mqtt_client_init(&mqttCfg);
|
||||
if (client_ == nullptr) {
|
||||
ESP_LOGE(kTag, "esp_mqtt_client_init failed");
|
||||
return false;
|
||||
return started;
|
||||
}
|
||||
|
||||
esp_mqtt_client_register_event(client_, MQTT_EVENT_ANY, &DaliCloudBridge::mqttEventHandler,
|
||||
@@ -177,17 +306,20 @@ bool DaliCloudBridge::start(const GatewayCloudConfig& config) {
|
||||
ESP_LOGE(kTag, "esp_mqtt_client_start failed");
|
||||
esp_mqtt_client_destroy(client_);
|
||||
client_ = nullptr;
|
||||
return false;
|
||||
return started;
|
||||
}
|
||||
return true;
|
||||
#else
|
||||
(void)config;
|
||||
(void)mqtt_requested;
|
||||
(void)lte_uart_requested;
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
void DaliCloudBridge::stop() {
|
||||
#ifdef ESP_PLATFORM
|
||||
stopLteUart();
|
||||
connected_.store(false);
|
||||
if (client_ != nullptr) {
|
||||
esp_mqtt_client_stop(client_);
|
||||
@@ -197,7 +329,15 @@ void DaliCloudBridge::stop() {
|
||||
#endif
|
||||
}
|
||||
|
||||
bool DaliCloudBridge::isConnected() const { return connected_.load(); }
|
||||
bool DaliCloudBridge::isConnected() const {
|
||||
return connected_.load() || lte_uart_started_.load();
|
||||
}
|
||||
|
||||
bool DaliCloudBridge::lteUartActive() const { return lte_uart_started_.load(); }
|
||||
|
||||
void DaliCloudBridge::setCemiDownlinkHandler(CemiDownlinkHandler handler) {
|
||||
cemi_downlink_handler_ = std::move(handler);
|
||||
}
|
||||
|
||||
bool DaliCloudBridge::publishStatus(const std::string& status) {
|
||||
std::string payload = "{\"type\":\"status\",\"status\":\"" + status + "\"}";
|
||||
@@ -208,6 +348,148 @@ bool DaliCloudBridge::publishRegister(const std::string& payloadJson) {
|
||||
return publishJSON(topicRegister(), payloadJson);
|
||||
}
|
||||
|
||||
bool DaliCloudBridge::publishCemiFrame(const uint8_t* data, size_t len) {
|
||||
#ifdef ESP_PLATFORM
|
||||
const uint32_t sequence = cemi_sequence_.fetch_add(1, std::memory_order_relaxed);
|
||||
const auto envelope = buildCemiEnvelope(kCemiDirectionUp, sequence, data, len);
|
||||
if (envelope.empty()) {
|
||||
return false;
|
||||
}
|
||||
bool delivered = false;
|
||||
if (cemiUsesLteUart(config_)) {
|
||||
delivered = publishLteUartBytes(envelope.data(), envelope.size()) || delivered;
|
||||
}
|
||||
if (cemiUsesMqtt(config_) && client_ != nullptr) {
|
||||
delivered = publishBytes(topicCemiUp(), envelope.data(), envelope.size(), 0) || delivered;
|
||||
}
|
||||
return delivered;
|
||||
#else
|
||||
(void)data;
|
||||
(void)len;
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool DaliCloudBridge::startLteUart() {
|
||||
#ifdef ESP_PLATFORM
|
||||
const int port = config_.lteUartPort;
|
||||
if (port < 0 || port > 2 || config_.lteBaudrate <= 0) {
|
||||
ESP_LOGW(kTag, "LTE UART cEMI transport disabled by invalid port=%d baud=%d",
|
||||
port, config_.lteBaudrate);
|
||||
return false;
|
||||
}
|
||||
|
||||
uart_config_t uart_config = {};
|
||||
uart_config.baud_rate = config_.lteBaudrate;
|
||||
uart_config.data_bits = UART_DATA_8_BITS;
|
||||
uart_config.parity = UART_PARITY_DISABLE;
|
||||
uart_config.stop_bits = UART_STOP_BITS_1;
|
||||
uart_config.flow_ctrl = UART_HW_FLOWCTRL_DISABLE;
|
||||
uart_config.source_clk = UART_SCLK_DEFAULT;
|
||||
|
||||
const uart_port_t uart_port = static_cast<uart_port_t>(port);
|
||||
esp_err_t err = uart_param_config(uart_port, &uart_config);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(kTag, "uart_param_config failed: %s", esp_err_to_name(err));
|
||||
return false;
|
||||
}
|
||||
err = uart_set_pin(uart_port,
|
||||
config_.lteTxPin < 0 ? UART_PIN_NO_CHANGE : config_.lteTxPin,
|
||||
config_.lteRxPin < 0 ? UART_PIN_NO_CHANGE : config_.lteRxPin,
|
||||
UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(kTag, "uart_set_pin failed: %s", esp_err_to_name(err));
|
||||
return false;
|
||||
}
|
||||
|
||||
const int rx_buffer = std::max(config_.lteRxBufferSize, 512);
|
||||
const int tx_buffer = std::max(config_.lteTxBufferSize, 512);
|
||||
err = uart_driver_install(uart_port, rx_buffer, tx_buffer, 0, nullptr, 0);
|
||||
if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) {
|
||||
ESP_LOGE(kTag, "uart_driver_install failed: %s", esp_err_to_name(err));
|
||||
return false;
|
||||
}
|
||||
|
||||
lte_uart_stop_requested_.store(false);
|
||||
lte_uart_started_.store(true);
|
||||
if (xTaskCreate(&DaliCloudBridge::lteUartTaskEntry, "cloud_lte_cemi", 4096,
|
||||
this, 5, <e_uart_task_) != pdPASS) {
|
||||
lte_uart_started_.store(false);
|
||||
uart_driver_delete(uart_port);
|
||||
lte_uart_task_ = nullptr;
|
||||
ESP_LOGE(kTag, "failed to create LTE UART cEMI task");
|
||||
return false;
|
||||
}
|
||||
ESP_LOGI(kTag, "LTE UART cEMI transport started on UART%d", port);
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
void DaliCloudBridge::stopLteUart() {
|
||||
#ifdef ESP_PLATFORM
|
||||
if (!lte_uart_started_.load()) {
|
||||
return;
|
||||
}
|
||||
lte_uart_stop_requested_.store(true);
|
||||
for (int i = 0; i < 20 && lte_uart_task_ != nullptr; ++i) {
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
}
|
||||
const int port = config_.lteUartPort;
|
||||
if (port >= 0 && port <= 2) {
|
||||
uart_driver_delete(static_cast<uart_port_t>(port));
|
||||
}
|
||||
lte_uart_started_.store(false);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool DaliCloudBridge::publishLteUartBytes(const uint8_t* data, size_t len) {
|
||||
#ifdef ESP_PLATFORM
|
||||
if (!lte_uart_started_.load() || data == nullptr || len == 0 ||
|
||||
config_.lteUartPort < 0 || config_.lteUartPort > 2) {
|
||||
return false;
|
||||
}
|
||||
const int written = uart_write_bytes(static_cast<uart_port_t>(config_.lteUartPort),
|
||||
reinterpret_cast<const char*>(data), len);
|
||||
return written == static_cast<int>(len);
|
||||
#else
|
||||
(void)data;
|
||||
(void)len;
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef ESP_PLATFORM
|
||||
void DaliCloudBridge::lteUartTaskEntry(void* arg) {
|
||||
auto* self = static_cast<DaliCloudBridge*>(arg);
|
||||
if (self != nullptr) {
|
||||
self->lteUartLoop();
|
||||
}
|
||||
vTaskDelete(nullptr);
|
||||
}
|
||||
|
||||
void DaliCloudBridge::lteUartLoop() {
|
||||
std::vector<uint8_t> buffer;
|
||||
buffer.reserve(kMaxCemiEnvelopeSize);
|
||||
std::array<uint8_t, 128> chunk{};
|
||||
const uart_port_t port = static_cast<uart_port_t>(config_.lteUartPort);
|
||||
while (!lte_uart_stop_requested_.load()) {
|
||||
const int read = uart_read_bytes(port, chunk.data(), chunk.size(), pdMS_TO_TICKS(100));
|
||||
if (read <= 0) {
|
||||
continue;
|
||||
}
|
||||
buffer.insert(buffer.end(), chunk.begin(), chunk.begin() + read);
|
||||
consumeCemiSerialBuffer(&buffer, [this](const uint8_t* payload, size_t len) {
|
||||
return handleCemiDownlink(payload, len);
|
||||
});
|
||||
}
|
||||
lte_uart_task_ = nullptr;
|
||||
}
|
||||
#else
|
||||
void DaliCloudBridge::lteUartLoop() {}
|
||||
#endif
|
||||
|
||||
#ifdef ESP_PLATFORM
|
||||
std::string buildRegisterPayload(const GatewayCloudConfig& config) {
|
||||
const std::string deviceID = jsonEscape(config.deviceID);
|
||||
@@ -226,12 +508,8 @@ std::string buildRegisterPayload(const GatewayCloudConfig& config) {
|
||||
|
||||
bool DaliCloudBridge::publishJSON(const std::string& topic, const std::string& payloadJson) {
|
||||
#ifdef ESP_PLATFORM
|
||||
if (client_ == nullptr || !connected_.load()) {
|
||||
return false;
|
||||
}
|
||||
int msgID = esp_mqtt_client_publish(client_, topic.c_str(), payloadJson.c_str(),
|
||||
static_cast<int>(payloadJson.size()), config_.qos, 0);
|
||||
return msgID >= 0;
|
||||
return publishBytes(topic, reinterpret_cast<const uint8_t*>(payloadJson.data()),
|
||||
payloadJson.size(), config_.qos);
|
||||
#else
|
||||
(void)topic;
|
||||
(void)payloadJson;
|
||||
@@ -239,6 +517,25 @@ bool DaliCloudBridge::publishJSON(const std::string& topic, const std::string& p
|
||||
#endif
|
||||
}
|
||||
|
||||
bool DaliCloudBridge::publishBytes(const std::string& topic, const uint8_t* data,
|
||||
size_t len, int qos) {
|
||||
#ifdef ESP_PLATFORM
|
||||
if (client_ == nullptr || !connected_.load() || data == nullptr) {
|
||||
return false;
|
||||
}
|
||||
int msgID = esp_mqtt_client_publish(client_, topic.c_str(),
|
||||
reinterpret_cast<const char*>(data),
|
||||
static_cast<int>(len), qos, 0);
|
||||
return msgID >= 0;
|
||||
#else
|
||||
(void)topic;
|
||||
(void)data;
|
||||
(void)len;
|
||||
(void)qos;
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string DaliCloudBridge::topicDown() const {
|
||||
return config_.topicPrefix + "/" + config_.deviceID + "/down";
|
||||
}
|
||||
@@ -255,6 +552,18 @@ std::string DaliCloudBridge::topicRegister() const {
|
||||
return config_.topicPrefix + "/" + config_.deviceID + "/register";
|
||||
}
|
||||
|
||||
std::string DaliCloudBridge::topicCemiDown() const {
|
||||
return config_.topicPrefix + "/" + config_.deviceID + "/cemi/down";
|
||||
}
|
||||
|
||||
std::string DaliCloudBridge::topicCemiUp() const {
|
||||
return config_.topicPrefix + "/" + config_.deviceID + "/cemi/up";
|
||||
}
|
||||
|
||||
std::string DaliCloudBridge::topicCemiStatus() const {
|
||||
return config_.topicPrefix + "/" + config_.deviceID + "/cemi/status";
|
||||
}
|
||||
|
||||
bool DaliCloudBridge::handleDownlink(const std::string& payload) {
|
||||
#ifdef ESP_PLATFORM
|
||||
cJSON* root = cJSON_Parse(payload.c_str());
|
||||
@@ -341,6 +650,24 @@ bool DaliCloudBridge::handleDownlink(const std::string& payload) {
|
||||
#endif
|
||||
}
|
||||
|
||||
bool DaliCloudBridge::handleCemiDownlink(const uint8_t* payload, size_t len) {
|
||||
#ifdef ESP_PLATFORM
|
||||
if (!cemi_downlink_handler_) {
|
||||
return false;
|
||||
}
|
||||
const auto cemi = decodeCemiEnvelope(payload, len);
|
||||
if (cemi.empty()) {
|
||||
ESP_LOGW(kTag, "invalid cEMI cloud payload len=%u", static_cast<unsigned>(len));
|
||||
return false;
|
||||
}
|
||||
return cemi_downlink_handler_(cemi.data(), cemi.size());
|
||||
#else
|
||||
(void)payload;
|
||||
(void)len;
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef ESP_PLATFORM
|
||||
void DaliCloudBridge::mqttEventHandler(void* handler_args,
|
||||
esp_event_base_t base,
|
||||
@@ -361,6 +688,7 @@ void DaliCloudBridge::onMqttEvent(esp_mqtt_event_handle_t event) {
|
||||
connected_.store(true);
|
||||
ESP_LOGI(kTag, "MQTT connected");
|
||||
esp_mqtt_client_subscribe(client_, topicDown().c_str(), config_.qos);
|
||||
esp_mqtt_client_subscribe(client_, topicCemiDown().c_str(), 0);
|
||||
publishStatus("online");
|
||||
publishRegister(buildRegisterPayload(config_));
|
||||
break;
|
||||
@@ -374,6 +702,8 @@ void DaliCloudBridge::onMqttEvent(esp_mqtt_event_handle_t event) {
|
||||
std::string data(event->data, event->data_len);
|
||||
if (topic == topicDown()) {
|
||||
handleDownlink(data);
|
||||
} else if (topic == topicCemiDown()) {
|
||||
handleCemiDownlink(reinterpret_cast<const uint8_t*>(data.data()), data.size());
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -14,6 +14,14 @@ constexpr const char* kKeyDeviceID = "device_id";
|
||||
constexpr const char* kKeyUsername = "username";
|
||||
constexpr const char* kKeyPassword = "password";
|
||||
constexpr const char* kKeyTopicPrefix = "topic_prefix";
|
||||
constexpr const char* kKeyCemiTransport = "cemi_transport";
|
||||
constexpr const char* kKeyLteUartEnabled = "lte_uart_en";
|
||||
constexpr const char* kKeyLteUartPort = "lte_uart_port";
|
||||
constexpr const char* kKeyLteTxPin = "lte_tx_pin";
|
||||
constexpr const char* kKeyLteRxPin = "lte_rx_pin";
|
||||
constexpr const char* kKeyLteBaudrate = "lte_baudrate";
|
||||
constexpr const char* kKeyLteRxBuffer = "lte_rx_buf";
|
||||
constexpr const char* kKeyLteTxBuffer = "lte_tx_buf";
|
||||
constexpr const char* kKeyQos = "qos";
|
||||
|
||||
esp_err_t writeString(nvs_handle_t handle, const char* key, const std::string& value) {
|
||||
@@ -52,6 +60,15 @@ esp_err_t GatewayProvisioningStore::save(const GatewayCloudConfig& config) const
|
||||
if (err == ESP_OK) err = writeString(handle, kKeyUsername, config.username);
|
||||
if (err == ESP_OK) err = writeString(handle, kKeyPassword, config.password);
|
||||
if (err == ESP_OK) err = writeString(handle, kKeyTopicPrefix, config.topicPrefix);
|
||||
if (err == ESP_OK) err = writeString(handle, kKeyCemiTransport, config.cemiTransport);
|
||||
if (err == ESP_OK) err = nvs_set_i32(handle, kKeyLteUartEnabled,
|
||||
config.lteUartEnabled ? 1 : 0);
|
||||
if (err == ESP_OK) err = nvs_set_i32(handle, kKeyLteUartPort, config.lteUartPort);
|
||||
if (err == ESP_OK) err = nvs_set_i32(handle, kKeyLteTxPin, config.lteTxPin);
|
||||
if (err == ESP_OK) err = nvs_set_i32(handle, kKeyLteRxPin, config.lteRxPin);
|
||||
if (err == ESP_OK) err = nvs_set_i32(handle, kKeyLteBaudrate, config.lteBaudrate);
|
||||
if (err == ESP_OK) err = nvs_set_i32(handle, kKeyLteRxBuffer, config.lteRxBufferSize);
|
||||
if (err == ESP_OK) err = nvs_set_i32(handle, kKeyLteTxBuffer, config.lteTxBufferSize);
|
||||
if (err == ESP_OK) err = nvs_set_i32(handle, kKeyQos, config.qos);
|
||||
if (err == ESP_OK) err = nvs_commit(handle);
|
||||
|
||||
@@ -83,6 +100,34 @@ esp_err_t GatewayProvisioningStore::load(GatewayCloudConfig* config) const {
|
||||
config->topicPrefix = "devices";
|
||||
}
|
||||
|
||||
esp_err_t transportErr = readString(handle, kKeyCemiTransport, &config->cemiTransport);
|
||||
if (transportErr != ESP_OK) {
|
||||
config->cemiTransport = "mqtt";
|
||||
}
|
||||
|
||||
int32_t intValue = 0;
|
||||
if (nvs_get_i32(handle, kKeyLteUartEnabled, &intValue) == ESP_OK) {
|
||||
config->lteUartEnabled = intValue != 0;
|
||||
}
|
||||
if (nvs_get_i32(handle, kKeyLteUartPort, &intValue) == ESP_OK) {
|
||||
config->lteUartPort = intValue;
|
||||
}
|
||||
if (nvs_get_i32(handle, kKeyLteTxPin, &intValue) == ESP_OK) {
|
||||
config->lteTxPin = intValue;
|
||||
}
|
||||
if (nvs_get_i32(handle, kKeyLteRxPin, &intValue) == ESP_OK) {
|
||||
config->lteRxPin = intValue;
|
||||
}
|
||||
if (nvs_get_i32(handle, kKeyLteBaudrate, &intValue) == ESP_OK) {
|
||||
config->lteBaudrate = intValue;
|
||||
}
|
||||
if (nvs_get_i32(handle, kKeyLteRxBuffer, &intValue) == ESP_OK) {
|
||||
config->lteRxBufferSize = intValue;
|
||||
}
|
||||
if (nvs_get_i32(handle, kKeyLteTxBuffer, &intValue) == ESP_OK) {
|
||||
config->lteTxBufferSize = intValue;
|
||||
}
|
||||
|
||||
int32_t qos = 1;
|
||||
esp_err_t qosErr = nvs_get_i32(handle, kKeyQos, &qos);
|
||||
if (qosErr == ESP_OK) {
|
||||
|
||||
Reference in New Issue
Block a user