From 70c39ea1e1308afdb59348f10cf9d014f5d5271a Mon Sep 17 00:00:00 2001 From: Tony Date: Fri, 1 May 2026 04:39:58 +0800 Subject: [PATCH] Refactor GatewayController to integrate GatewayCache - Updated CMakeLists.txt to require gateway_cache component. - Modified gateway_controller.hpp to include GatewayCache and adjust constructor. - Removed internal scene and group management logic from GatewayController, delegating to GatewayCache. - Simplified scene and group operations by utilizing GatewayCache methods for enabling, setting details, and deleting. - Eliminated NVS storage handling code, as scene and group data is now managed by GatewayCache. - Updated command handling methods to use cached data instead of internal structures. Co-authored-by: Copilot --- apps/gateway/main/CMakeLists.txt | 2 +- apps/gateway/main/Kconfig.projbuild | 26 + apps/gateway/main/app_main.cpp | 21 + apps/gateway/sdkconfig | 8 + components/gateway_cache/CMakeLists.txt | 7 + .../gateway_cache/include/gateway_cache.hpp | 116 ++++ .../gateway_cache/src/gateway_cache.cpp | 544 ++++++++++++++++++ components/gateway_controller/CMakeLists.txt | 2 +- .../include/gateway_controller.hpp | 49 +- .../src/gateway_controller.cpp | 406 ++----------- 10 files changed, 781 insertions(+), 400 deletions(-) create mode 100644 components/gateway_cache/CMakeLists.txt create mode 100644 components/gateway_cache/include/gateway_cache.hpp create mode 100644 components/gateway_cache/src/gateway_cache.cpp diff --git a/apps/gateway/main/CMakeLists.txt b/apps/gateway/main/CMakeLists.txt index 7ae7970..01b22d4 100644 --- a/apps/gateway/main/CMakeLists.txt +++ b/apps/gateway/main/CMakeLists.txt @@ -1,6 +1,6 @@ idf_component_register( SRCS "app_main.cpp" - REQUIRES gateway_core gateway_controller gateway_network gateway_bridge dali_domain gateway_runtime gateway_ble gateway_usb_setup log + REQUIRES gateway_core gateway_controller gateway_network gateway_bridge gateway_cache dali_domain gateway_runtime gateway_ble gateway_usb_setup log ) set_property(TARGET ${COMPONENT_LIB} PROPERTY CXX_STANDARD 17) \ No newline at end of file diff --git a/apps/gateway/main/Kconfig.projbuild b/apps/gateway/main/Kconfig.projbuild index cf9ff36..b018dad 100644 --- a/apps/gateway/main/Kconfig.projbuild +++ b/apps/gateway/main/Kconfig.projbuild @@ -219,6 +219,32 @@ config GATEWAY_CHANNEL2_SERIAL_QUERY_TIMEOUT_MS endmenu +menu "Gateway Cache" + +config GATEWAY_CACHE_FLUSH_INTERVAL_MS + int "Cache flush interval ms" + range 100 600000 + default 5000 + help + Interval used to batch scene and group cache writes to flash. + +choice GATEWAY_CACHE_CONFLICT_PRIORITY + prompt "Cache conflict priority default" + default GATEWAY_CACHE_OUTSIDE_BUS_FIRST + help + Default source of truth to use when future cache reconciliation detects + changes made on the DALI bus outside this gateway. + +config GATEWAY_CACHE_OUTSIDE_BUS_FIRST + bool "Outside bus first" + +config GATEWAY_CACHE_LOCAL_GATEWAY_FIRST + bool "Local gateway first" + +endchoice + +endmenu + config GATEWAY_ENABLE_DALI_BUS bool "Legacy single local DALI bus switch" default n diff --git a/apps/gateway/main/app_main.cpp b/apps/gateway/main/app_main.cpp index 6309133..7283ac9 100644 --- a/apps/gateway/main/app_main.cpp +++ b/apps/gateway/main/app_main.cpp @@ -1,6 +1,7 @@ #include "dali_domain.hpp" #include "gateway_ble.hpp" #include "gateway_bridge.hpp" +#include "gateway_cache.hpp" #include "gateway_controller.hpp" #include "gateway_core.hpp" #include "gateway_network.hpp" @@ -74,6 +75,10 @@ #define CONFIG_GATEWAY_BRIDGE_BACNET_TASK_PRIORITY 5 #endif +#ifndef CONFIG_GATEWAY_CACHE_FLUSH_INTERVAL_MS +#define CONFIG_GATEWAY_CACHE_FLUSH_INTERVAL_MS 5000 +#endif + namespace { constexpr const char* kProjectName = "DALI_485_Gateway"; constexpr const char* kProjectVersion = "0.1.0"; @@ -175,8 +180,17 @@ constexpr bool kCloudBridgeStartupEnabled = true; constexpr bool kCloudBridgeStartupEnabled = false; #endif +#ifdef CONFIG_GATEWAY_CACHE_LOCAL_GATEWAY_FIRST +constexpr gateway::GatewayCachePriorityMode kCachePriorityMode = + gateway::GatewayCachePriorityMode::kLocalGatewayFirst; +#else +constexpr gateway::GatewayCachePriorityMode kCachePriorityMode = + gateway::GatewayCachePriorityMode::kOutsideBusFirst; +#endif + std::unique_ptr s_dali_domain; std::unique_ptr s_runtime; +std::unique_ptr s_cache; std::unique_ptr s_controller; std::unique_ptr s_bridge; std::unique_ptr s_network; @@ -409,6 +423,12 @@ extern "C" void app_main(void) { s_runtime->setGatewayCount(CONFIG_GATEWAY_CHANNEL_COUNT); ESP_ERROR_CHECK(BindConfiguredChannels(*s_dali_domain, *s_runtime)); + gateway::GatewayCacheConfig cache_config; + cache_config.flush_interval_ms = static_cast(CONFIG_GATEWAY_CACHE_FLUSH_INTERVAL_MS); + cache_config.default_priority_mode = kCachePriorityMode; + s_cache = std::make_unique(cache_config); + ESP_ERROR_CHECK(s_cache->start()); + gateway::GatewayControllerConfig controller_config; controller_config.setup_supported = true; controller_config.ble_supported = profile.enable_ble; @@ -418,6 +438,7 @@ extern "C" void app_main(void) { controller_config.internal_group_supported = true; s_controller = std::make_unique(*s_runtime, *s_dali_domain, + *s_cache, controller_config); ESP_ERROR_CHECK(s_controller->start()); diff --git a/apps/gateway/sdkconfig b/apps/gateway/sdkconfig index c6dbe3c..d86ba61 100644 --- a/apps/gateway/sdkconfig +++ b/apps/gateway/sdkconfig @@ -618,6 +618,14 @@ CONFIG_GATEWAY_CHANNEL2_PHY_DISABLED=y # CONFIG_GATEWAY_CHANNEL2_PHY_UART2 is not set # end of Gateway Channel 2 +# +# Gateway Cache +# +CONFIG_GATEWAY_CACHE_FLUSH_INTERVAL_MS=5000 +CONFIG_GATEWAY_CACHE_OUTSIDE_BUS_FIRST=y +# CONFIG_GATEWAY_CACHE_LOCAL_GATEWAY_FIRST is not set +# end of Gateway Cache + # CONFIG_GATEWAY_ENABLE_DALI_BUS is not set # diff --git a/components/gateway_cache/CMakeLists.txt b/components/gateway_cache/CMakeLists.txt new file mode 100644 index 0000000..c363795 --- /dev/null +++ b/components/gateway_cache/CMakeLists.txt @@ -0,0 +1,7 @@ +idf_component_register( + SRCS "src/gateway_cache.cpp" + INCLUDE_DIRS "include" + REQUIRES freertos log nvs_flash +) + +set_property(TARGET ${COMPONENT_LIB} PROPERTY CXX_STANDARD 17) \ No newline at end of file diff --git a/components/gateway_cache/include/gateway_cache.hpp b/components/gateway_cache/include/gateway_cache.hpp new file mode 100644 index 0000000..73c2580 --- /dev/null +++ b/components/gateway_cache/include/gateway_cache.hpp @@ -0,0 +1,116 @@ +#pragma once + +#include +#include +#include +#include + +#include "esp_err.h" +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" +#include "freertos/task.h" +#include "nvs.h" + +namespace gateway { + +enum class GatewayCachePriorityMode : uint8_t { + kOutsideBusFirst = 0, + kLocalGatewayFirst = 1, +}; + +struct GatewayCacheConfig { + std::string storage_namespace{"gateway_rt"}; + uint32_t flush_interval_ms{5000}; + uint32_t task_stack_size{4096}; + UBaseType_t task_priority{3}; + GatewayCachePriorityMode default_priority_mode{GatewayCachePriorityMode::kOutsideBusFirst}; +}; + +struct GatewayCacheChannelFlags { + bool need_update_group{false}; + bool need_update_scene{false}; + bool need_update_settings{false}; +}; + +class GatewayCache { + public: + struct SceneEntry { + bool enabled{false}; + uint8_t brightness{254}; + uint8_t color_mode{2}; + uint8_t data1{0}; + uint8_t data2{0}; + uint8_t data3{0}; + std::string name; + }; + + struct GroupEntry { + bool enabled{false}; + uint8_t target_type{2}; + uint8_t target_value{0}; + std::string name; + }; + + using SceneStore = std::array; + using GroupStore = std::array; + + explicit GatewayCache(GatewayCacheConfig config = {}); + ~GatewayCache(); + + esp_err_t start(); + void preloadChannel(uint8_t gateway_id); + + SceneStore scenes(uint8_t gateway_id); + GroupStore groups(uint8_t gateway_id); + SceneEntry scene(uint8_t gateway_id, uint8_t scene_id); + GroupEntry group(uint8_t gateway_id, uint8_t group_id); + + bool setSceneEnabled(uint8_t gateway_id, uint8_t scene_id, bool enabled); + bool setSceneDetail(uint8_t gateway_id, uint8_t scene_id, uint8_t brightness, + uint8_t color_mode, uint8_t data1, uint8_t data2, uint8_t data3); + bool setSceneName(uint8_t gateway_id, uint8_t scene_id, std::string_view name); + bool deleteScene(uint8_t gateway_id, uint8_t scene_id); + std::pair sceneMask(uint8_t gateway_id); + + bool setGroupEnabled(uint8_t gateway_id, uint8_t group_id, bool enabled); + bool setGroupDetail(uint8_t gateway_id, uint8_t group_id, uint8_t target_type, + uint8_t target_value); + bool setGroupName(uint8_t gateway_id, uint8_t group_id, std::string_view name); + bool deleteGroup(uint8_t gateway_id, uint8_t group_id); + std::pair groupMask(uint8_t gateway_id); + + GatewayCacheChannelFlags channelFlags(uint8_t gateway_id); + void markGroupUpdateNeeded(uint8_t gateway_id, bool needed = true); + void markSceneUpdateNeeded(uint8_t gateway_id, bool needed = true); + void markSettingsUpdateNeeded(uint8_t gateway_id, bool needed = true); + + GatewayCachePriorityMode priorityMode(); + void setPriorityMode(GatewayCachePriorityMode mode); + + private: + static void TaskEntry(void* arg); + void taskLoop(); + bool flushDirty(); + + bool openStorageLocked(); + void closeStorageLocked(); + SceneStore& ensureSceneStoreLocked(uint8_t gateway_id); + GroupStore& ensureGroupStoreLocked(uint8_t gateway_id); + void loadSceneStoreLocked(uint8_t gateway_id, SceneStore& scenes); + void loadGroupStoreLocked(uint8_t gateway_id, GroupStore& groups); + std::string readStringLocked(std::string_view key); + bool writeStringLocked(std::string_view key, std::string_view value); + bool eraseKeyLocked(std::string_view key); + + GatewayCacheConfig config_; + GatewayCachePriorityMode priority_mode_; + TaskHandle_t task_handle_{nullptr}; + SemaphoreHandle_t lock_{nullptr}; + nvs_handle_t storage_{0}; + std::map scenes_; + std::map groups_; + std::map channel_flags_; + bool dirty_{false}; +}; + +} // namespace gateway \ No newline at end of file diff --git a/components/gateway_cache/src/gateway_cache.cpp b/components/gateway_cache/src/gateway_cache.cpp new file mode 100644 index 0000000..25eef84 --- /dev/null +++ b/components/gateway_cache/src/gateway_cache.cpp @@ -0,0 +1,544 @@ +#include "gateway_cache.hpp" + +#include +#include +#include +#include +#include + +#include "esp_log.h" + +namespace gateway { + +namespace { + +constexpr const char* kTag = "gateway_cache"; +constexpr size_t kMaxNameBytes = 32; + +class LockGuard { + public: + explicit LockGuard(SemaphoreHandle_t lock) : lock_(lock) { + if (lock_ != nullptr) { + xSemaphoreTakeRecursive(lock_, portMAX_DELAY); + } + } + + ~LockGuard() { + if (lock_ != nullptr) { + xSemaphoreGiveRecursive(lock_); + } + } + + private: + SemaphoreHandle_t lock_; +}; + +std::string ShortKey(const char* prefix, uint8_t gateway_id, uint8_t slot) { + char key[16] = {0}; + std::snprintf(key, sizeof(key), "%s%u_%u", prefix, gateway_id, slot); + return std::string(key); +} + +std::vector ParseCsv(std::string_view raw) { + std::vector values; + size_t start = 0; + while (start < raw.size()) { + const size_t comma = raw.find(',', start); + const size_t end = comma == std::string_view::npos ? raw.size() : comma; + if (end > start) { + values.push_back(static_cast(std::strtol(std::string(raw.substr(start, end - start)).c_str(), + nullptr, 10))); + } + if (comma == std::string_view::npos) { + break; + } + start = comma + 1; + } + return values; +} + +std::string NormalizeName(std::string_view name) { + std::string normalized(name); + if (normalized.size() > kMaxNameBytes) { + normalized.resize(kMaxNameBytes); + } + return normalized; +} + +bool IsDefaultScene(const GatewayCache::SceneEntry& scene) { + return !scene.enabled && scene.brightness == 254 && scene.color_mode == 2 && scene.data1 == 0 && + scene.data2 == 0 && scene.data3 == 0; +} + +bool IsDefaultGroup(const GatewayCache::GroupEntry& group) { + return !group.enabled && group.target_type == 2 && group.target_value == 0; +} + +std::string BuildScenePayload(const GatewayCache::SceneEntry& scene) { + char payload[32] = {0}; + std::snprintf(payload, sizeof(payload), "%u,%u,%u,%u,%u,%u", scene.enabled ? 1 : 0, + scene.brightness, scene.color_mode, scene.data1, scene.data2, scene.data3); + return std::string(payload); +} + +std::string BuildGroupPayload(const GatewayCache::GroupEntry& group) { + char payload[24] = {0}; + std::snprintf(payload, sizeof(payload), "%u,%u,%u", group.enabled ? 1 : 0, group.target_type, + group.target_value); + return std::string(payload); +} + +} // namespace + +GatewayCache::GatewayCache(GatewayCacheConfig config) + : config_(std::move(config)), + priority_mode_(config_.default_priority_mode), + lock_(xSemaphoreCreateRecursiveMutex()) {} + +GatewayCache::~GatewayCache() { + if (task_handle_ != nullptr) { + vTaskDelete(task_handle_); + task_handle_ = nullptr; + } + + { + LockGuard guard(lock_); + flushDirty(); + closeStorageLocked(); + } + + if (lock_ != nullptr) { + vSemaphoreDelete(lock_); + lock_ = nullptr; + } +} + +esp_err_t GatewayCache::start() { + { + LockGuard guard(lock_); + if (!openStorageLocked()) { + return ESP_FAIL; + } + } + + if (task_handle_ != nullptr) { + return ESP_OK; + } + + const BaseType_t created = xTaskCreate(&GatewayCache::TaskEntry, "gateway_cache", + config_.task_stack_size, this, config_.task_priority, + &task_handle_); + if (created != pdPASS) { + task_handle_ = nullptr; + ESP_LOGE(kTag, "failed to create cache task"); + return ESP_ERR_NO_MEM; + } + + ESP_LOGI(kTag, "cache started namespace=%s flush_interval_ms=%u", + config_.storage_namespace.c_str(), static_cast(config_.flush_interval_ms)); + return ESP_OK; +} + +void GatewayCache::preloadChannel(uint8_t gateway_id) { + LockGuard guard(lock_); + ensureSceneStoreLocked(gateway_id); + ensureGroupStoreLocked(gateway_id); +} + +GatewayCache::SceneStore GatewayCache::scenes(uint8_t gateway_id) { + LockGuard guard(lock_); + return ensureSceneStoreLocked(gateway_id); +} + +GatewayCache::GroupStore GatewayCache::groups(uint8_t gateway_id) { + LockGuard guard(lock_); + return ensureGroupStoreLocked(gateway_id); +} + +GatewayCache::SceneEntry GatewayCache::scene(uint8_t gateway_id, uint8_t scene_id) { + LockGuard guard(lock_); + if (scene_id >= 16) { + return {}; + } + return ensureSceneStoreLocked(gateway_id)[scene_id]; +} + +GatewayCache::GroupEntry GatewayCache::group(uint8_t gateway_id, uint8_t group_id) { + LockGuard guard(lock_); + if (group_id >= 16) { + return {}; + } + return ensureGroupStoreLocked(gateway_id)[group_id]; +} + +bool GatewayCache::setSceneEnabled(uint8_t gateway_id, uint8_t scene_id, bool enabled) { + LockGuard guard(lock_); + if (scene_id >= 16) { + return false; + } + auto& entry = ensureSceneStoreLocked(gateway_id)[scene_id]; + if (entry.enabled == enabled) { + return true; + } + entry.enabled = enabled; + dirty_ = true; + return true; +} + +bool GatewayCache::setSceneDetail(uint8_t gateway_id, uint8_t scene_id, uint8_t brightness, + uint8_t color_mode, uint8_t data1, uint8_t data2, + uint8_t data3) { + LockGuard guard(lock_); + if (scene_id >= 16) { + return false; + } + auto& entry = ensureSceneStoreLocked(gateway_id)[scene_id]; + if (entry.brightness == brightness && entry.color_mode == color_mode && entry.data1 == data1 && + entry.data2 == data2 && entry.data3 == data3) { + return true; + } + entry.brightness = brightness; + entry.color_mode = color_mode; + entry.data1 = data1; + entry.data2 = data2; + entry.data3 = data3; + dirty_ = true; + return true; +} + +bool GatewayCache::setSceneName(uint8_t gateway_id, uint8_t scene_id, std::string_view name) { + LockGuard guard(lock_); + if (scene_id >= 16) { + return false; + } + auto& entry = ensureSceneStoreLocked(gateway_id)[scene_id]; + const auto normalized = NormalizeName(name); + if (entry.name == normalized) { + return true; + } + entry.name = normalized; + dirty_ = true; + return true; +} + +bool GatewayCache::deleteScene(uint8_t gateway_id, uint8_t scene_id) { + LockGuard guard(lock_); + if (scene_id >= 16) { + return false; + } + auto& entry = ensureSceneStoreLocked(gateway_id)[scene_id]; + if (IsDefaultScene(entry) && entry.name.empty()) { + return true; + } + entry = SceneEntry{}; + dirty_ = true; + return true; +} + +std::pair GatewayCache::sceneMask(uint8_t gateway_id) { + LockGuard guard(lock_); + const auto& store = ensureSceneStoreLocked(gateway_id); + uint16_t mask = 0; + for (size_t index = 0; index < store.size(); ++index) { + if (store[index].enabled) { + mask |= static_cast(1U << index); + } + } + return {static_cast(mask & 0xff), static_cast((mask >> 8) & 0xff)}; +} + +bool GatewayCache::setGroupEnabled(uint8_t gateway_id, uint8_t group_id, bool enabled) { + LockGuard guard(lock_); + if (group_id >= 16) { + return false; + } + auto& entry = ensureGroupStoreLocked(gateway_id)[group_id]; + if (entry.enabled == enabled) { + return true; + } + entry.enabled = enabled; + dirty_ = true; + return true; +} + +bool GatewayCache::setGroupDetail(uint8_t gateway_id, uint8_t group_id, uint8_t target_type, + uint8_t target_value) { + LockGuard guard(lock_); + if (group_id >= 16) { + return false; + } + auto& entry = ensureGroupStoreLocked(gateway_id)[group_id]; + if (entry.target_type == target_type && entry.target_value == target_value) { + return true; + } + entry.target_type = target_type; + entry.target_value = target_value; + dirty_ = true; + return true; +} + +bool GatewayCache::setGroupName(uint8_t gateway_id, uint8_t group_id, std::string_view name) { + LockGuard guard(lock_); + if (group_id >= 16) { + return false; + } + auto& entry = ensureGroupStoreLocked(gateway_id)[group_id]; + const auto normalized = NormalizeName(name); + if (entry.name == normalized) { + return true; + } + entry.name = normalized; + dirty_ = true; + return true; +} + +bool GatewayCache::deleteGroup(uint8_t gateway_id, uint8_t group_id) { + LockGuard guard(lock_); + if (group_id >= 16) { + return false; + } + auto& entry = ensureGroupStoreLocked(gateway_id)[group_id]; + if (IsDefaultGroup(entry) && entry.name.empty()) { + return true; + } + entry = GroupEntry{}; + dirty_ = true; + return true; +} + +std::pair GatewayCache::groupMask(uint8_t gateway_id) { + LockGuard guard(lock_); + const auto& store = ensureGroupStoreLocked(gateway_id); + uint16_t mask = 0; + for (size_t index = 0; index < store.size(); ++index) { + if (store[index].enabled) { + mask |= static_cast(1U << index); + } + } + return {static_cast(mask & 0xff), static_cast((mask >> 8) & 0xff)}; +} + +GatewayCacheChannelFlags GatewayCache::channelFlags(uint8_t gateway_id) { + LockGuard guard(lock_); + return channel_flags_[gateway_id]; +} + +void GatewayCache::markGroupUpdateNeeded(uint8_t gateway_id, bool needed) { + LockGuard guard(lock_); + channel_flags_[gateway_id].need_update_group = needed; +} + +void GatewayCache::markSceneUpdateNeeded(uint8_t gateway_id, bool needed) { + LockGuard guard(lock_); + channel_flags_[gateway_id].need_update_scene = needed; +} + +void GatewayCache::markSettingsUpdateNeeded(uint8_t gateway_id, bool needed) { + LockGuard guard(lock_); + channel_flags_[gateway_id].need_update_settings = needed; +} + +GatewayCachePriorityMode GatewayCache::priorityMode() { + LockGuard guard(lock_); + return priority_mode_; +} + +void GatewayCache::setPriorityMode(GatewayCachePriorityMode mode) { + LockGuard guard(lock_); + priority_mode_ = mode; +} + +void GatewayCache::TaskEntry(void* arg) { + static_cast(arg)->taskLoop(); +} + +void GatewayCache::taskLoop() { + const TickType_t interval_ticks = + std::max(1, pdMS_TO_TICKS(config_.flush_interval_ms)); + while (true) { + vTaskDelay(interval_ticks); + flushDirty(); + } +} + +bool GatewayCache::flushDirty() { + LockGuard guard(lock_); + if (!dirty_) { + return true; + } + if (!openStorageLocked()) { + return false; + } + + for (const auto& [gateway_id, store] : scenes_) { + for (uint8_t scene_id = 0; scene_id < store.size(); ++scene_id) { + const auto& entry = store[scene_id]; + if (!IsDefaultScene(entry)) { + if (!writeStringLocked(ShortKey("sc", gateway_id, scene_id), BuildScenePayload(entry))) { + return false; + } + } else if (!eraseKeyLocked(ShortKey("sc", gateway_id, scene_id))) { + return false; + } + + if (!entry.name.empty()) { + if (!writeStringLocked(ShortKey("sn", gateway_id, scene_id), entry.name)) { + return false; + } + } else if (!eraseKeyLocked(ShortKey("sn", gateway_id, scene_id))) { + return false; + } + } + } + + for (const auto& [gateway_id, store] : groups_) { + for (uint8_t group_id = 0; group_id < store.size(); ++group_id) { + const auto& entry = store[group_id]; + if (!IsDefaultGroup(entry)) { + if (!writeStringLocked(ShortKey("gr", gateway_id, group_id), BuildGroupPayload(entry))) { + return false; + } + } else if (!eraseKeyLocked(ShortKey("gr", gateway_id, group_id))) { + return false; + } + + if (!entry.name.empty()) { + if (!writeStringLocked(ShortKey("gn", gateway_id, group_id), entry.name)) { + return false; + } + } else if (!eraseKeyLocked(ShortKey("gn", gateway_id, group_id))) { + return false; + } + } + } + + const esp_err_t commit_err = nvs_commit(storage_); + if (commit_err != ESP_OK) { + ESP_LOGE(kTag, "cache commit failed: %s", esp_err_to_name(commit_err)); + return false; + } + + dirty_ = false; + return true; +} + +bool GatewayCache::openStorageLocked() { + if (storage_ != 0) { + return true; + } + const esp_err_t err = nvs_open(config_.storage_namespace.c_str(), NVS_READWRITE, &storage_); + if (err != ESP_OK) { + ESP_LOGE(kTag, "failed to open cache storage: %s", esp_err_to_name(err)); + return false; + } + return true; +} + +void GatewayCache::closeStorageLocked() { + if (storage_ != 0) { + nvs_close(storage_); + storage_ = 0; + } +} + +GatewayCache::SceneStore& GatewayCache::ensureSceneStoreLocked(uint8_t gateway_id) { + auto [it, inserted] = scenes_.try_emplace(gateway_id); + if (inserted) { + loadSceneStoreLocked(gateway_id, it->second); + } + return it->second; +} + +GatewayCache::GroupStore& GatewayCache::ensureGroupStoreLocked(uint8_t gateway_id) { + auto [it, inserted] = groups_.try_emplace(gateway_id); + if (inserted) { + loadGroupStoreLocked(gateway_id, it->second); + } + return it->second; +} + +void GatewayCache::loadSceneStoreLocked(uint8_t gateway_id, SceneStore& scenes) { + for (uint8_t scene_id = 0; scene_id < scenes.size(); ++scene_id) { + const auto raw = readStringLocked(ShortKey("sc", gateway_id, scene_id)); + const auto values = ParseCsv(raw); + if (values.size() >= 6) { + scenes[scene_id].enabled = values[0] != 0; + scenes[scene_id].brightness = static_cast(std::clamp(values[1], 0, 254)); + scenes[scene_id].color_mode = static_cast(std::clamp(values[2], 0, 2)); + scenes[scene_id].data1 = static_cast(std::clamp(values[3], 0, 255)); + scenes[scene_id].data2 = static_cast(std::clamp(values[4], 0, 255)); + scenes[scene_id].data3 = static_cast(std::clamp(values[5], 0, 255)); + } + scenes[scene_id].name = NormalizeName(readStringLocked(ShortKey("sn", gateway_id, scene_id))); + } +} + +void GatewayCache::loadGroupStoreLocked(uint8_t gateway_id, GroupStore& groups) { + for (uint8_t group_id = 0; group_id < groups.size(); ++group_id) { + const auto raw = readStringLocked(ShortKey("gr", gateway_id, group_id)); + const auto values = ParseCsv(raw); + if (values.size() >= 2) { + groups[group_id].enabled = values[0] != 0; + uint8_t target_type = static_cast(std::clamp(values[1], 0, 2)); + uint8_t target_value = values.size() >= 3 ? static_cast(std::clamp(values[2], 0, 255)) + : target_type; + if (values.size() < 3) { + target_type = 2; + } + groups[group_id].target_type = target_type; + groups[group_id].target_value = target_type == 0 + ? static_cast(std::min(target_value, 63)) + : target_type == 1 + ? static_cast(std::min(target_value, 15)) + : 0; + } + groups[group_id].name = NormalizeName(readStringLocked(ShortKey("gn", gateway_id, group_id))); + } +} + +std::string GatewayCache::readStringLocked(std::string_view key) { + if (!openStorageLocked()) { + return {}; + } + size_t required_size = 0; + const esp_err_t err = nvs_get_str(storage_, std::string(key).c_str(), nullptr, &required_size); + if (err != ESP_OK || required_size == 0) { + return {}; + } + std::string value(required_size - 1, '\0'); + if (nvs_get_str(storage_, std::string(key).c_str(), value.data(), &required_size) != ESP_OK) { + return {}; + } + return value; +} + +bool GatewayCache::writeStringLocked(std::string_view key, std::string_view value) { + if (storage_ == 0) { + return false; + } + const esp_err_t err = nvs_set_str(storage_, std::string(key).c_str(), std::string(value).c_str()); + if (err != ESP_OK) { + ESP_LOGE(kTag, "failed to write cache key=%s err=%s", std::string(key).c_str(), + esp_err_to_name(err)); + return false; + } + return true; +} + +bool GatewayCache::eraseKeyLocked(std::string_view key) { + if (storage_ == 0) { + return false; + } + const esp_err_t err = nvs_erase_key(storage_, std::string(key).c_str()); + if (err == ESP_ERR_NVS_NOT_FOUND) { + return true; + } + if (err != ESP_OK) { + ESP_LOGE(kTag, "failed to erase cache key=%s err=%s", std::string(key).c_str(), + esp_err_to_name(err)); + return false; + } + return true; +} + +} // namespace gateway \ No newline at end of file diff --git a/components/gateway_controller/CMakeLists.txt b/components/gateway_controller/CMakeLists.txt index 423d791..f27fe1b 100644 --- a/components/gateway_controller/CMakeLists.txt +++ b/components/gateway_controller/CMakeLists.txt @@ -1,7 +1,7 @@ idf_component_register( SRCS "src/gateway_controller.cpp" INCLUDE_DIRS "include" - REQUIRES dali_domain gateway_runtime freertos log nvs_flash + REQUIRES dali_domain gateway_runtime gateway_cache freertos log ) set_property(TARGET ${COMPONENT_LIB} PROPERTY CXX_STANDARD 17) diff --git a/components/gateway_controller/include/gateway_controller.hpp b/components/gateway_controller/include/gateway_controller.hpp index 5950233..97759de 100644 --- a/components/gateway_controller/include/gateway_controller.hpp +++ b/components/gateway_controller/include/gateway_controller.hpp @@ -3,15 +3,14 @@ #include #include #include -#include #include #include #include +#include "gateway_cache.hpp" #include "esp_err.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" -#include "nvs.h" namespace gateway { @@ -64,6 +63,7 @@ class GatewayController { using GatewayNameSink = std::function; GatewayController(GatewayRuntime& runtime, DaliDomainService& dali_domain, + GatewayCache& cache, GatewayControllerConfig config = {}); ~GatewayController(); @@ -83,26 +83,6 @@ class GatewayController { GatewayControllerSnapshot snapshot(); private: - struct InternalScene { - bool enabled{false}; - uint8_t brightness{254}; - uint8_t color_mode{2}; - uint8_t data1{0}; - uint8_t data2{0}; - uint8_t data3{0}; - std::string name; - }; - - struct InternalGroup { - bool enabled{false}; - uint8_t target_type{2}; - uint8_t target_value{0}; - std::string name; - }; - - using SceneStore = std::array; - using GroupStore = std::array; - static void TaskEntry(void* arg); void taskLoop(); void dispatchCommand(const std::vector& command); @@ -124,11 +104,6 @@ class GatewayController { static int shortAddressFromRaw(uint8_t raw_addr); static int reverseInRange(int value, int min_value, int max_value); - SceneStore& sceneStore(uint8_t gateway_id); - GroupStore& groupStore(uint8_t gateway_id); - InternalScene* scene(uint8_t gateway_id, uint8_t scene_id); - InternalGroup* group(uint8_t gateway_id, uint8_t group_id); - bool setSceneEnabled(uint8_t gateway_id, uint8_t scene_id, bool enabled); bool setSceneDetail(uint8_t gateway_id, uint8_t scene_id, uint8_t brightness, uint8_t color_mode, uint8_t data1, uint8_t data2, uint8_t data3); @@ -151,33 +126,15 @@ class GatewayController { void handleInternalSceneCommand(uint8_t gateway_id, const std::vector& command); void handleInternalGroupCommand(uint8_t gateway_id, const std::vector& command); - bool openStorage(); - void closeStorage(); - void loadSceneStore(uint8_t gateway_id, SceneStore& scenes); - void loadGroupStore(uint8_t gateway_id, GroupStore& groups); - bool saveScene(uint8_t gateway_id, uint8_t scene_id); - bool deleteSceneStorage(uint8_t gateway_id, uint8_t scene_id); - bool saveSceneName(uint8_t gateway_id, uint8_t scene_id, std::string_view name); - bool deleteSceneNameStorage(uint8_t gateway_id, uint8_t scene_id); - bool saveGroup(uint8_t gateway_id, uint8_t group_id); - bool deleteGroupStorage(uint8_t gateway_id, uint8_t group_id); - bool saveGroupName(uint8_t gateway_id, uint8_t group_id, std::string_view name); - bool deleteGroupNameStorage(uint8_t gateway_id, uint8_t group_id); - std::string readString(std::string_view key) const; - bool writeString(std::string_view key, std::string_view value); - bool eraseKey(std::string_view key); - GatewayRuntime& runtime_; DaliDomainService& dali_domain_; + GatewayCache& cache_; GatewayControllerConfig config_; TaskHandle_t task_handle_{nullptr}; - nvs_handle_t storage_{0}; std::vector notification_sinks_; std::vector ble_state_sinks_; std::vector wifi_state_sinks_; std::vector gateway_name_sinks_; - std::map scenes_; - std::map groups_; bool setup_mode_{false}; bool wireless_setup_mode_{false}; bool ble_enabled_{false}; diff --git a/components/gateway_controller/src/gateway_controller.cpp b/components/gateway_controller/src/gateway_controller.cpp index e0b5d30..cf18679 100644 --- a/components/gateway_controller/src/gateway_controller.cpp +++ b/components/gateway_controller/src/gateway_controller.cpp @@ -15,33 +15,8 @@ namespace gateway { namespace { constexpr const char* kTag = "gateway_controller"; -constexpr const char* kStorageNamespace = "gateway_rt"; constexpr size_t kMaxNameBytes = 32; -std::string ShortKey(const char* prefix, uint8_t gateway_id, uint8_t slot) { - char key[16] = {0}; - std::snprintf(key, sizeof(key), "%s%u_%u", prefix, gateway_id, slot); - return std::string(key); -} - -std::vector ParseCsv(std::string_view raw) { - std::vector values; - size_t start = 0; - while (start < raw.size()) { - const size_t comma = raw.find(',', start); - const size_t end = comma == std::string_view::npos ? raw.size() : comma; - if (end > start) { - values.push_back(static_cast(std::strtol(std::string(raw.substr(start, end - start)).c_str(), - nullptr, 10))); - } - if (comma == std::string_view::npos) { - break; - } - start = comma + 1; - } - return values; -} - std::string NormalizeName(std::string_view name) { std::string normalized(name); if (normalized.size() > kMaxNameBytes) { @@ -95,19 +70,12 @@ const char* PhyKindToString(DaliPhyKind phy_kind) { } // namespace GatewayController::GatewayController(GatewayRuntime& runtime, DaliDomainService& dali_domain, - GatewayControllerConfig config) - : runtime_(runtime), dali_domain_(dali_domain), config_(config) {} + GatewayCache& cache, GatewayControllerConfig config) + : runtime_(runtime), dali_domain_(dali_domain), cache_(cache), config_(config) {} -GatewayController::~GatewayController() { - closeStorage(); -} +GatewayController::~GatewayController() = default; esp_err_t GatewayController::start() { - const esp_err_t err = openStorage() ? ESP_OK : ESP_FAIL; - if (err != ESP_OK) { - return err; - } - const auto device_info = runtime_.deviceInfo(); ble_enabled_ = device_info.ble_enabled; refreshRuntimeGatewayNames(); @@ -117,8 +85,7 @@ esp_err_t GatewayController::start() { dali_domain_.addRawFrameSink([this](const DaliRawFrame& frame) { handleDaliRawFrame(frame); }); for (const auto& channel : dali_domain_.channelInfo()) { - sceneStore(channel.gateway_id); - groupStore(channel.gateway_id); + cache_.preloadChannel(channel.gateway_id); dali_domain_.resetBus(channel.gateway_id); publishPayload(channel.gateway_id, {0x02, channel.gateway_id, 0x88}); } @@ -539,11 +506,11 @@ uint8_t GatewayController::resolveInternalGroupRawAddress(uint8_t gateway_id, ui return raw_addr; } const uint8_t slot = static_cast((raw_addr - 0x80) / 2); - auto* group_data = group(gateway_id, slot); - if (group_data == nullptr || !group_data->enabled) { + const auto group_data = cache_.group(gateway_id, slot); + if (!group_data.enabled) { return raw_addr; } - return internalGroupRawTargetAddress(group_data->target_type, group_data->target_value, raw_addr); + return internalGroupRawTargetAddress(group_data.target_type, group_data.target_value, raw_addr); } uint8_t GatewayController::normalizeGroupTargetType(uint8_t target_type) { @@ -595,55 +562,13 @@ int GatewayController::reverseInRange(int value, int min_value, int max_value) { return min_value + max_value - value; } -GatewayController::SceneStore& GatewayController::sceneStore(uint8_t gateway_id) { - auto [it, inserted] = scenes_.try_emplace(gateway_id); - if (inserted) { - loadSceneStore(gateway_id, it->second); - } - return it->second; -} - -GatewayController::GroupStore& GatewayController::groupStore(uint8_t gateway_id) { - auto [it, inserted] = groups_.try_emplace(gateway_id); - if (inserted) { - loadGroupStore(gateway_id, it->second); - } - return it->second; -} - -GatewayController::InternalScene* GatewayController::scene(uint8_t gateway_id, uint8_t scene_id) { - if (scene_id >= 16) { - return nullptr; - } - return &sceneStore(gateway_id)[scene_id]; -} - -GatewayController::InternalGroup* GatewayController::group(uint8_t gateway_id, uint8_t group_id) { - if (group_id >= 16) { - return nullptr; - } - return &groupStore(gateway_id)[group_id]; -} - bool GatewayController::setSceneEnabled(uint8_t gateway_id, uint8_t scene_id, bool enabled) { - auto* scene_data = scene(gateway_id, scene_id); - if (scene_data == nullptr) { - return false; - } - if (scene_data->enabled == enabled) { - return true; - } - scene_data->enabled = enabled; - return saveScene(gateway_id, scene_id); + return cache_.setSceneEnabled(gateway_id, scene_id, enabled); } bool GatewayController::setSceneDetail(uint8_t gateway_id, uint8_t scene_id, uint8_t brightness, uint8_t color_mode, uint8_t data1, uint8_t data2, uint8_t data3) { - auto* scene_data = scene(gateway_id, scene_id); - if (scene_data == nullptr) { - return false; - } const uint8_t next_brightness = std::min(brightness, 254); const uint8_t next_color_mode = std::min(color_mode, 2); uint8_t next_data1 = 0; @@ -657,73 +582,35 @@ bool GatewayController::setSceneDetail(uint8_t gateway_id, uint8_t scene_id, uin next_data2 = data2; next_data3 = data3; } - if (scene_data->brightness == next_brightness && scene_data->color_mode == next_color_mode && - scene_data->data1 == next_data1 && scene_data->data2 == next_data2 && - scene_data->data3 == next_data3) { - return true; - } - scene_data->brightness = next_brightness; - scene_data->color_mode = next_color_mode; - scene_data->data1 = next_data1; - scene_data->data2 = next_data2; - scene_data->data3 = next_data3; - return saveScene(gateway_id, scene_id); + return cache_.setSceneDetail(gateway_id, scene_id, next_brightness, next_color_mode, + next_data1, next_data2, next_data3); } bool GatewayController::setSceneName(uint8_t gateway_id, uint8_t scene_id, std::string_view name) { - auto* scene_data = scene(gateway_id, scene_id); - if (scene_data == nullptr) { - return false; - } const auto normalized = NormalizeName(name); - if (scene_data->name == normalized) { - return true; - } - scene_data->name = normalized; - return saveSceneName(gateway_id, scene_id, scene_data->name); + return cache_.setSceneName(gateway_id, scene_id, normalized); } bool GatewayController::deleteScene(uint8_t gateway_id, uint8_t scene_id) { - auto* scene_data = scene(gateway_id, scene_id); - if (scene_data == nullptr) { - return false; - } - const bool already_default = !scene_data->enabled && scene_data->brightness == 254 && - scene_data->color_mode == 2 && scene_data->data1 == 0 && - scene_data->data2 == 0 && scene_data->data3 == 0 && - scene_data->name.empty(); - if (already_default) { - return true; - } - *scene_data = InternalScene{}; - deleteSceneStorage(gateway_id, scene_id); - deleteSceneNameStorage(gateway_id, scene_id); - return true; + return cache_.deleteScene(gateway_id, scene_id); } std::pair GatewayController::sceneMask(uint8_t gateway_id) { - const auto& scenes = sceneStore(gateway_id); - uint16_t mask = 0; - for (size_t index = 0; index < scenes.size(); ++index) { - if (scenes[index].enabled) { - mask |= static_cast(1U << index); - } - } - return {static_cast(mask & 0xff), static_cast((mask >> 8) & 0xff)}; + return cache_.sceneMask(gateway_id); } bool GatewayController::executeScene(uint8_t gateway_id, int short_address, uint8_t scene_id) { - auto* scene_data = scene(gateway_id, scene_id); - if (scene_data == nullptr || !scene_data->enabled) { + const auto scene_data = cache_.scene(gateway_id, scene_id); + if (!scene_data.enabled) { return false; } - if (scene_data->brightness <= 0) { + if (scene_data.brightness <= 0) { dali_domain_.off(gateway_id, short_address); } else { - dali_domain_.setBright(gateway_id, short_address, scene_data->brightness); + dali_domain_.setBright(gateway_id, short_address, scene_data.brightness); } - if (scene_data->color_mode == 0) { - int kelvin = scene_data->data1 * 256 + scene_data->data2; + if (scene_data.color_mode == 0) { + int kelvin = scene_data.data1 * 256 + scene_data.data2; if (kelvin > 0) { if (config_.color_temperature_max < config_.color_temperature_min) { kelvin = reverseInRange(kelvin, config_.color_temperature_min, @@ -731,11 +618,11 @@ bool GatewayController::executeScene(uint8_t gateway_id, int short_address, uint } dali_domain_.setColTemp(gateway_id, short_address, kelvin); } - } else if (scene_data->color_mode == 1) { - if (scene_data->data1 != 0 || scene_data->data2 != 0 || scene_data->data3 != 0) { - dali_domain_.setColourRGB(gateway_id, short_address, scene_data->data1, scene_data->data2, - scene_data->data3); - } else if (scene_data->brightness <= 0) { + } else if (scene_data.color_mode == 1) { + if (scene_data.data1 != 0 || scene_data.data2 != 0 || scene_data.data3 != 0) { + dali_domain_.setColourRGB(gateway_id, short_address, scene_data.data1, scene_data.data2, + scene_data.data3); + } else if (scene_data.brightness <= 0) { dali_domain_.off(gateway_id, short_address); } } @@ -743,81 +630,37 @@ bool GatewayController::executeScene(uint8_t gateway_id, int short_address, uint } bool GatewayController::setGroupEnabled(uint8_t gateway_id, uint8_t group_id, bool enabled) { - auto* group_data = group(gateway_id, group_id); - if (group_data == nullptr) { - return false; - } - if (group_data->enabled == enabled) { - return true; - } - group_data->enabled = enabled; - return saveGroup(gateway_id, group_id); + return cache_.setGroupEnabled(gateway_id, group_id, enabled); } bool GatewayController::setGroupDetail(uint8_t gateway_id, uint8_t group_id, uint8_t target_type, uint8_t target_value) { - auto* group_data = group(gateway_id, group_id); - if (group_data == nullptr) { - return false; - } const uint8_t next_target_type = normalizeGroupTargetType(target_type); const uint8_t next_target_value = normalizeGroupTargetValue(next_target_type, target_value); - if (group_data->target_type == next_target_type && group_data->target_value == next_target_value) { - return true; - } - group_data->target_type = next_target_type; - group_data->target_value = next_target_value; - return saveGroup(gateway_id, group_id); + return cache_.setGroupDetail(gateway_id, group_id, next_target_type, next_target_value); } bool GatewayController::setGroupName(uint8_t gateway_id, uint8_t group_id, std::string_view name) { - auto* group_data = group(gateway_id, group_id); - if (group_data == nullptr) { - return false; - } const auto normalized = NormalizeName(name); - if (group_data->name == normalized) { - return true; - } - group_data->name = normalized; - return saveGroupName(gateway_id, group_id, group_data->name); + return cache_.setGroupName(gateway_id, group_id, normalized); } bool GatewayController::deleteGroup(uint8_t gateway_id, uint8_t group_id) { - auto* group_data = group(gateway_id, group_id); - if (group_data == nullptr) { - return false; - } - const bool already_default = !group_data->enabled && group_data->target_type == 2 && - group_data->target_value == 0 && group_data->name.empty(); - if (already_default) { - return true; - } - *group_data = InternalGroup{}; - deleteGroupStorage(gateway_id, group_id); - deleteGroupNameStorage(gateway_id, group_id); - return true; + return cache_.deleteGroup(gateway_id, group_id); } std::pair GatewayController::groupMask(uint8_t gateway_id) { - const auto& groups = groupStore(gateway_id); - uint16_t mask = 0; - for (size_t index = 0; index < groups.size(); ++index) { - if (groups[index].enabled) { - mask |= static_cast(1U << index); - } - } - return {static_cast(mask & 0xff), static_cast((mask >> 8) & 0xff)}; + return cache_.groupMask(gateway_id); } bool GatewayController::executeGroup(uint8_t gateway_id, uint8_t group_id) { - auto* group_data = group(gateway_id, group_id); - if (group_data == nullptr || !group_data->enabled) { + const auto group_data = cache_.group(gateway_id, group_id); + if (!group_data.enabled) { return false; } return dali_domain_.on(gateway_id, - internalGroupDecTargetAddress(group_data->target_type, - group_data->target_value)); + internalGroupDecTargetAddress(group_data.target_type, + group_data.target_value)); } void GatewayController::handleGatewayNameCommand(uint8_t gateway_id, @@ -907,7 +750,6 @@ void GatewayController::handleInternalSceneCommand(uint8_t gateway_id, return; } - auto* scene_data = scene(gateway_id, scene_id); switch (op) { case 0x00: publishPayload(gateway_id, setSceneEnabled(gateway_id, scene_id, true) @@ -921,7 +763,7 @@ void GatewayController::handleInternalSceneCommand(uint8_t gateway_id, break; case 0x02: publishPayload(gateway_id, {0xA0, gateway_id, op, scene_id, - static_cast(scene_data->enabled ? 1 : 0)}); + static_cast(cache_.scene(gateway_id, scene_id).enabled ? 1 : 0)}); break; case 0x03: { const auto [low, high] = sceneMask(gateway_id); @@ -929,10 +771,13 @@ void GatewayController::handleInternalSceneCommand(uint8_t gateway_id, break; } case 0x04: - publishPayload(gateway_id, {0xA0, gateway_id, op, scene_id, scene_data->brightness, - scene_data->color_mode, scene_data->data1, scene_data->data2, - scene_data->data3}); - break; + { + const auto scene_data = cache_.scene(gateway_id, scene_id); + publishPayload(gateway_id, {0xA0, gateway_id, op, scene_id, scene_data.brightness, + scene_data.color_mode, scene_data.data1, scene_data.data2, + scene_data.data3}); + break; + } case 0x05: if (command.size() >= 11 && setSceneDetail(gateway_id, scene_id, command[6], command[7], command[8], command[9], command[10])) { @@ -947,8 +792,9 @@ void GatewayController::handleInternalSceneCommand(uint8_t gateway_id, : std::vector{0xA1, gateway_id, op, scene_id, 0x02}); break; case 0x07: { + const auto scene_data = cache_.scene(gateway_id, scene_id); std::vector payload{0xA0, gateway_id, op, scene_id}; - AppendPaddedName(payload, scene_data->name); + AppendPaddedName(payload, scene_data.name); publishPayload(gateway_id, payload); break; } @@ -989,7 +835,6 @@ void GatewayController::handleInternalGroupCommand(uint8_t gateway_id, return; } - auto* group_data = group(gateway_id, group_id); switch (op) { case 0x00: publishPayload(gateway_id, setGroupEnabled(gateway_id, group_id, true) @@ -1003,7 +848,7 @@ void GatewayController::handleInternalGroupCommand(uint8_t gateway_id, break; case 0x02: publishPayload(gateway_id, {0xA2, gateway_id, op, group_id, - static_cast(group_data->enabled ? 1 : 0)}); + static_cast(cache_.group(gateway_id, group_id).enabled ? 1 : 0)}); break; case 0x03: { const auto [low, high] = groupMask(gateway_id); @@ -1011,11 +856,14 @@ void GatewayController::handleInternalGroupCommand(uint8_t gateway_id, break; } case 0x04: - publishPayload(gateway_id, {0xA2, gateway_id, op, group_id, - normalizeGroupTargetType(group_data->target_type), - normalizeGroupTargetValue(group_data->target_type, - group_data->target_value)}); - break; + { + const auto group_data = cache_.group(gateway_id, group_id); + publishPayload(gateway_id, {0xA2, gateway_id, op, group_id, + normalizeGroupTargetType(group_data.target_type), + normalizeGroupTargetValue(group_data.target_type, + group_data.target_value)}); + break; + } case 0x05: if (command.size() >= 9 && setGroupDetail(gateway_id, group_id, command[6], command[7])) { publishPayload(gateway_id, {0xA2, gateway_id, op, group_id, 0x01}); @@ -1029,8 +877,9 @@ void GatewayController::handleInternalGroupCommand(uint8_t gateway_id, : std::vector{0xA3, gateway_id, op, group_id, 0x02}); break; case 0x07: { + const auto group_data = cache_.group(gateway_id, group_id); std::vector payload{0xA2, gateway_id, op, group_id}; - AppendPaddedName(payload, group_data->name); + AppendPaddedName(payload, group_data.name); publishPayload(gateway_id, payload); break; } @@ -1063,151 +912,4 @@ void GatewayController::handleInternalGroupCommand(uint8_t gateway_id, } } -bool GatewayController::openStorage() { - if (storage_ != 0) { - return true; - } - const esp_err_t err = nvs_open(kStorageNamespace, NVS_READWRITE, &storage_); - if (err != ESP_OK) { - ESP_LOGE(kTag, "failed to open controller storage: %s", esp_err_to_name(err)); - return false; - } - return true; -} - -void GatewayController::closeStorage() { - if (storage_ != 0) { - nvs_close(storage_); - storage_ = 0; - } -} - -void GatewayController::loadSceneStore(uint8_t gateway_id, SceneStore& scenes) { - for (uint8_t scene_id = 0; scene_id < scenes.size(); ++scene_id) { - const auto raw = readString(ShortKey("sc", gateway_id, scene_id)); - const auto values = ParseCsv(raw); - if (values.size() >= 6) { - scenes[scene_id].enabled = values[0] != 0; - scenes[scene_id].brightness = static_cast(std::clamp(values[1], 0, 254)); - scenes[scene_id].color_mode = static_cast(std::clamp(values[2], 0, 2)); - scenes[scene_id].data1 = static_cast(std::clamp(values[3], 0, 255)); - scenes[scene_id].data2 = static_cast(std::clamp(values[4], 0, 255)); - scenes[scene_id].data3 = static_cast(std::clamp(values[5], 0, 255)); - } - scenes[scene_id].name = NormalizeName(readString(ShortKey("sn", gateway_id, scene_id))); - } -} - -void GatewayController::loadGroupStore(uint8_t gateway_id, GroupStore& groups) { - for (uint8_t group_id = 0; group_id < groups.size(); ++group_id) { - const auto raw = readString(ShortKey("gr", gateway_id, group_id)); - const auto values = ParseCsv(raw); - if (values.size() >= 2) { - groups[group_id].enabled = values[0] != 0; - uint8_t target_type = static_cast(std::clamp(values[1], 0, 255)); - uint8_t target_value = values.size() >= 3 ? static_cast(std::clamp(values[2], 0, 255)) - : target_type; - if (values.size() < 3) { - target_type = 2; - } - groups[group_id].target_type = normalizeGroupTargetType(target_type); - groups[group_id].target_value = normalizeGroupTargetValue(groups[group_id].target_type, - target_value); - } - groups[group_id].name = NormalizeName(readString(ShortKey("gn", gateway_id, group_id))); - } -} - -bool GatewayController::saveScene(uint8_t gateway_id, uint8_t scene_id) { - const auto* scene_data = scene(gateway_id, scene_id); - if (scene_data == nullptr) { - return false; - } - char payload[32] = {0}; - std::snprintf(payload, sizeof(payload), "%u,%u,%u,%u,%u,%u", scene_data->enabled ? 1 : 0, - scene_data->brightness, scene_data->color_mode, scene_data->data1, - scene_data->data2, scene_data->data3); - return writeString(ShortKey("sc", gateway_id, scene_id), payload); -} - -bool GatewayController::deleteSceneStorage(uint8_t gateway_id, uint8_t scene_id) { - return eraseKey(ShortKey("sc", gateway_id, scene_id)); -} - -bool GatewayController::saveSceneName(uint8_t gateway_id, uint8_t scene_id, std::string_view name) { - if (name.empty()) { - return deleteSceneNameStorage(gateway_id, scene_id); - } - return writeString(ShortKey("sn", gateway_id, scene_id), NormalizeName(name)); -} - -bool GatewayController::deleteSceneNameStorage(uint8_t gateway_id, uint8_t scene_id) { - return eraseKey(ShortKey("sn", gateway_id, scene_id)); -} - -bool GatewayController::saveGroup(uint8_t gateway_id, uint8_t group_id) { - const auto* group_data = group(gateway_id, group_id); - if (group_data == nullptr) { - return false; - } - char payload[24] = {0}; - std::snprintf(payload, sizeof(payload), "%u,%u,%u", group_data->enabled ? 1 : 0, - normalizeGroupTargetType(group_data->target_type), - normalizeGroupTargetValue(group_data->target_type, group_data->target_value)); - return writeString(ShortKey("gr", gateway_id, group_id), payload); -} - -bool GatewayController::deleteGroupStorage(uint8_t gateway_id, uint8_t group_id) { - return eraseKey(ShortKey("gr", gateway_id, group_id)); -} - -bool GatewayController::saveGroupName(uint8_t gateway_id, uint8_t group_id, std::string_view name) { - if (name.empty()) { - return deleteGroupNameStorage(gateway_id, group_id); - } - return writeString(ShortKey("gn", gateway_id, group_id), NormalizeName(name)); -} - -bool GatewayController::deleteGroupNameStorage(uint8_t gateway_id, uint8_t group_id) { - return eraseKey(ShortKey("gn", gateway_id, group_id)); -} - -std::string GatewayController::readString(std::string_view key) const { - if (storage_ == 0) { - return {}; - } - size_t required_size = 0; - if (nvs_get_str(storage_, std::string(key).c_str(), nullptr, &required_size) != ESP_OK || - required_size == 0) { - return {}; - } - std::string value(required_size - 1, '\0'); - if (nvs_get_str(storage_, std::string(key).c_str(), value.data(), &required_size) != ESP_OK) { - return {}; - } - return value; -} - -bool GatewayController::writeString(std::string_view key, std::string_view value) { - if (storage_ == 0) { - return false; - } - return nvs_set_str(storage_, std::string(key).c_str(), std::string(value).c_str()) == ESP_OK && - nvs_commit(storage_) == ESP_OK; -} - -bool GatewayController::eraseKey(std::string_view key) { - if (storage_ == 0) { - return false; - } - const esp_err_t err = nvs_erase_key(storage_, std::string(key).c_str()); - if (err == ESP_ERR_NVS_NOT_FOUND) { - return true; - } - if (err != ESP_OK) { - return false; - } - return nvs_commit(storage_) == ESP_OK; -} - } // namespace gateway