From f18f7570edb5f63db29f17d7a42ec3a520db8900 Mon Sep 17 00:00:00 2001 From: Tony Date: Wed, 27 May 2026 15:20:16 +0800 Subject: [PATCH] Enhance DaliCloudBridge: add support for LTE UART transport and extend configuration options Signed-off-by: Tony --- CMakeLists.txt | 2 +- include/gateway_cloud.hpp | 32 ++++ src/gateway_cloud.cpp | 354 +++++++++++++++++++++++++++++++++-- src/gateway_provisioning.cpp | 45 +++++ 4 files changed, 420 insertions(+), 13 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1117ee4..f028864 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) diff --git a/include/gateway_cloud.hpp b/include/gateway_cloud.hpp index 6bc4956..fada120 100644 --- a/include/gateway_cloud.hpp +++ b/include/gateway_cloud.hpp @@ -4,10 +4,14 @@ #include "dali_comm.hpp" #include +#include #include +#include #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; + 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 connected_{false}; + std::atomic lte_uart_started_{false}; + std::atomic lte_uart_stop_requested_{false}; + std::atomic 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 }; diff --git a/src/gateway_cloud.cpp b/src/gateway_cloud.cpp index 11c59d7..0fd2ac6 100644 --- a/src/gateway_cloud.cpp +++ b/src/gateway_cloud.cpp @@ -1,16 +1,26 @@ #include "gateway_cloud.hpp" +#include +#include +#include #include #include +#include #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 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(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((value >> 8) & 0xff); + data[1] = static_cast(value & 0xff); +} + +void writeBe32(uint8_t* data, uint32_t value) { + data[0] = static_cast((value >> 24) & 0xff); + data[1] = static_cast((value >> 16) & 0xff); + data[2] = static_cast((value >> 8) & 0xff); + data[3] = static_cast(value & 0xff); +} + +uint16_t readBe16(const uint8_t* data) { + return static_cast((static_cast(data[0]) << 8) | data[1]); +} + +void consumeCemiSerialBuffer(std::vector* buffer, + const std::function& 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(frame_len)); + } + if (buffer->size() > kMaxCemiEnvelopeSize) { + buffer->erase(buffer->begin(), buffer->end() - static_cast(kCemiEnvelopeHeaderSize)); + } +} + +std::vector buildCemiEnvelope(uint8_t direction, uint32_t sequence, + const uint8_t* data, size_t len) { + if (data == nullptr || len == 0 || len > 4096) { + return {}; + } + std::vector 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(len)); + out[13] = 0; + std::memcpy(out.data() + kCemiEnvelopeHeaderSize, data, len); + return out; +} + +std::vector 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(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(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(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(config_.lteUartPort), + reinterpret_cast(data), len); + return written == static_cast(len); +#else + (void)data; + (void)len; + return false; +#endif +} + +#ifdef ESP_PLATFORM +void DaliCloudBridge::lteUartTaskEntry(void* arg) { + auto* self = static_cast(arg); + if (self != nullptr) { + self->lteUartLoop(); + } + vTaskDelete(nullptr); +} + +void DaliCloudBridge::lteUartLoop() { + std::vector buffer; + buffer.reserve(kMaxCemiEnvelopeSize); + std::array chunk{}; + const uart_port_t port = static_cast(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(payloadJson.size()), config_.qos, 0); - return msgID >= 0; + return publishBytes(topic, reinterpret_cast(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(data), + static_cast(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(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(data.data()), data.size()); } break; } diff --git a/src/gateway_provisioning.cpp b/src/gateway_provisioning.cpp index 22c9acf..833d46d 100644 --- a/src/gateway_provisioning.cpp +++ b/src/gateway_provisioning.cpp @@ -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) {