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 <copilot@github.com>
This commit is contained in:
Tony
2026-05-01 04:39:58 +08:00
parent d16c289626
commit 70c39ea1e1
10 changed files with 781 additions and 400 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
idf_component_register( idf_component_register(
SRCS "app_main.cpp" 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) set_property(TARGET ${COMPONENT_LIB} PROPERTY CXX_STANDARD 17)
+26
View File
@@ -219,6 +219,32 @@ config GATEWAY_CHANNEL2_SERIAL_QUERY_TIMEOUT_MS
endmenu 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 config GATEWAY_ENABLE_DALI_BUS
bool "Legacy single local DALI bus switch" bool "Legacy single local DALI bus switch"
default n default n
+21
View File
@@ -1,6 +1,7 @@
#include "dali_domain.hpp" #include "dali_domain.hpp"
#include "gateway_ble.hpp" #include "gateway_ble.hpp"
#include "gateway_bridge.hpp" #include "gateway_bridge.hpp"
#include "gateway_cache.hpp"
#include "gateway_controller.hpp" #include "gateway_controller.hpp"
#include "gateway_core.hpp" #include "gateway_core.hpp"
#include "gateway_network.hpp" #include "gateway_network.hpp"
@@ -74,6 +75,10 @@
#define CONFIG_GATEWAY_BRIDGE_BACNET_TASK_PRIORITY 5 #define CONFIG_GATEWAY_BRIDGE_BACNET_TASK_PRIORITY 5
#endif #endif
#ifndef CONFIG_GATEWAY_CACHE_FLUSH_INTERVAL_MS
#define CONFIG_GATEWAY_CACHE_FLUSH_INTERVAL_MS 5000
#endif
namespace { namespace {
constexpr const char* kProjectName = "DALI_485_Gateway"; constexpr const char* kProjectName = "DALI_485_Gateway";
constexpr const char* kProjectVersion = "0.1.0"; constexpr const char* kProjectVersion = "0.1.0";
@@ -175,8 +180,17 @@ constexpr bool kCloudBridgeStartupEnabled = true;
constexpr bool kCloudBridgeStartupEnabled = false; constexpr bool kCloudBridgeStartupEnabled = false;
#endif #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<gateway::DaliDomainService> s_dali_domain; std::unique_ptr<gateway::DaliDomainService> s_dali_domain;
std::unique_ptr<gateway::GatewayRuntime> s_runtime; std::unique_ptr<gateway::GatewayRuntime> s_runtime;
std::unique_ptr<gateway::GatewayCache> s_cache;
std::unique_ptr<gateway::GatewayController> s_controller; std::unique_ptr<gateway::GatewayController> s_controller;
std::unique_ptr<gateway::GatewayBridgeService> s_bridge; std::unique_ptr<gateway::GatewayBridgeService> s_bridge;
std::unique_ptr<gateway::GatewayNetworkService> s_network; std::unique_ptr<gateway::GatewayNetworkService> s_network;
@@ -409,6 +423,12 @@ extern "C" void app_main(void) {
s_runtime->setGatewayCount(CONFIG_GATEWAY_CHANNEL_COUNT); s_runtime->setGatewayCount(CONFIG_GATEWAY_CHANNEL_COUNT);
ESP_ERROR_CHECK(BindConfiguredChannels(*s_dali_domain, *s_runtime)); ESP_ERROR_CHECK(BindConfiguredChannels(*s_dali_domain, *s_runtime));
gateway::GatewayCacheConfig cache_config;
cache_config.flush_interval_ms = static_cast<uint32_t>(CONFIG_GATEWAY_CACHE_FLUSH_INTERVAL_MS);
cache_config.default_priority_mode = kCachePriorityMode;
s_cache = std::make_unique<gateway::GatewayCache>(cache_config);
ESP_ERROR_CHECK(s_cache->start());
gateway::GatewayControllerConfig controller_config; gateway::GatewayControllerConfig controller_config;
controller_config.setup_supported = true; controller_config.setup_supported = true;
controller_config.ble_supported = profile.enable_ble; controller_config.ble_supported = profile.enable_ble;
@@ -418,6 +438,7 @@ extern "C" void app_main(void) {
controller_config.internal_group_supported = true; controller_config.internal_group_supported = true;
s_controller = std::make_unique<gateway::GatewayController>(*s_runtime, *s_dali_domain, s_controller = std::make_unique<gateway::GatewayController>(*s_runtime, *s_dali_domain,
*s_cache,
controller_config); controller_config);
ESP_ERROR_CHECK(s_controller->start()); ESP_ERROR_CHECK(s_controller->start());
+8
View File
@@ -618,6 +618,14 @@ CONFIG_GATEWAY_CHANNEL2_PHY_DISABLED=y
# CONFIG_GATEWAY_CHANNEL2_PHY_UART2 is not set # CONFIG_GATEWAY_CHANNEL2_PHY_UART2 is not set
# end of Gateway Channel 2 # 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 # CONFIG_GATEWAY_ENABLE_DALI_BUS is not set
# #
+7
View File
@@ -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)
@@ -0,0 +1,116 @@
#pragma once
#include <array>
#include <cstdint>
#include <map>
#include <string>
#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<SceneEntry, 16>;
using GroupStore = std::array<GroupEntry, 16>;
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<uint8_t, uint8_t> 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<uint8_t, uint8_t> 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<uint8_t, SceneStore> scenes_;
std::map<uint8_t, GroupStore> groups_;
std::map<uint8_t, GatewayCacheChannelFlags> channel_flags_;
bool dirty_{false};
};
} // namespace gateway
@@ -0,0 +1,544 @@
#include "gateway_cache.hpp"
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <utility>
#include <vector>
#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<int> ParseCsv(std::string_view raw) {
std::vector<int> 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<int>(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<unsigned>(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<uint8_t, uint8_t> 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<uint16_t>(1U << index);
}
}
return {static_cast<uint8_t>(mask & 0xff), static_cast<uint8_t>((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<uint8_t, uint8_t> 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<uint16_t>(1U << index);
}
}
return {static_cast<uint8_t>(mask & 0xff), static_cast<uint8_t>((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<GatewayCache*>(arg)->taskLoop();
}
void GatewayCache::taskLoop() {
const TickType_t interval_ticks =
std::max<TickType_t>(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<uint8_t>(std::clamp(values[1], 0, 254));
scenes[scene_id].color_mode = static_cast<uint8_t>(std::clamp(values[2], 0, 2));
scenes[scene_id].data1 = static_cast<uint8_t>(std::clamp(values[3], 0, 255));
scenes[scene_id].data2 = static_cast<uint8_t>(std::clamp(values[4], 0, 255));
scenes[scene_id].data3 = static_cast<uint8_t>(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<uint8_t>(std::clamp(values[1], 0, 2));
uint8_t target_value = values.size() >= 3 ? static_cast<uint8_t>(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<uint8_t>(std::min<int>(target_value, 63))
: target_type == 1
? static_cast<uint8_t>(std::min<int>(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
+1 -1
View File
@@ -1,7 +1,7 @@
idf_component_register( idf_component_register(
SRCS "src/gateway_controller.cpp" SRCS "src/gateway_controller.cpp"
INCLUDE_DIRS "include" 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) set_property(TARGET ${COMPONENT_LIB} PROPERTY CXX_STANDARD 17)
@@ -3,15 +3,14 @@
#include <array> #include <array>
#include <cstdint> #include <cstdint>
#include <functional> #include <functional>
#include <map>
#include <string> #include <string>
#include <string_view> #include <string_view>
#include <vector> #include <vector>
#include "gateway_cache.hpp"
#include "esp_err.h" #include "esp_err.h"
#include "freertos/FreeRTOS.h" #include "freertos/FreeRTOS.h"
#include "freertos/task.h" #include "freertos/task.h"
#include "nvs.h"
namespace gateway { namespace gateway {
@@ -64,6 +63,7 @@ class GatewayController {
using GatewayNameSink = std::function<void(uint8_t gateway_id)>; using GatewayNameSink = std::function<void(uint8_t gateway_id)>;
GatewayController(GatewayRuntime& runtime, DaliDomainService& dali_domain, GatewayController(GatewayRuntime& runtime, DaliDomainService& dali_domain,
GatewayCache& cache,
GatewayControllerConfig config = {}); GatewayControllerConfig config = {});
~GatewayController(); ~GatewayController();
@@ -83,26 +83,6 @@ class GatewayController {
GatewayControllerSnapshot snapshot(); GatewayControllerSnapshot snapshot();
private: 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<InternalScene, 16>;
using GroupStore = std::array<InternalGroup, 16>;
static void TaskEntry(void* arg); static void TaskEntry(void* arg);
void taskLoop(); void taskLoop();
void dispatchCommand(const std::vector<uint8_t>& command); void dispatchCommand(const std::vector<uint8_t>& command);
@@ -124,11 +104,6 @@ class GatewayController {
static int shortAddressFromRaw(uint8_t raw_addr); static int shortAddressFromRaw(uint8_t raw_addr);
static int reverseInRange(int value, int min_value, int max_value); 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 setSceneEnabled(uint8_t gateway_id, uint8_t scene_id, bool enabled);
bool setSceneDetail(uint8_t gateway_id, uint8_t scene_id, uint8_t brightness, 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); 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<uint8_t>& command); void handleInternalSceneCommand(uint8_t gateway_id, const std::vector<uint8_t>& command);
void handleInternalGroupCommand(uint8_t gateway_id, const std::vector<uint8_t>& command); void handleInternalGroupCommand(uint8_t gateway_id, const std::vector<uint8_t>& 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_; GatewayRuntime& runtime_;
DaliDomainService& dali_domain_; DaliDomainService& dali_domain_;
GatewayCache& cache_;
GatewayControllerConfig config_; GatewayControllerConfig config_;
TaskHandle_t task_handle_{nullptr}; TaskHandle_t task_handle_{nullptr};
nvs_handle_t storage_{0};
std::vector<NotificationSink> notification_sinks_; std::vector<NotificationSink> notification_sinks_;
std::vector<BleStateSink> ble_state_sinks_; std::vector<BleStateSink> ble_state_sinks_;
std::vector<WifiStateSink> wifi_state_sinks_; std::vector<WifiStateSink> wifi_state_sinks_;
std::vector<GatewayNameSink> gateway_name_sinks_; std::vector<GatewayNameSink> gateway_name_sinks_;
std::map<uint8_t, SceneStore> scenes_;
std::map<uint8_t, GroupStore> groups_;
bool setup_mode_{false}; bool setup_mode_{false};
bool wireless_setup_mode_{false}; bool wireless_setup_mode_{false};
bool ble_enabled_{false}; bool ble_enabled_{false};
@@ -15,33 +15,8 @@ namespace gateway {
namespace { namespace {
constexpr const char* kTag = "gateway_controller"; constexpr const char* kTag = "gateway_controller";
constexpr const char* kStorageNamespace = "gateway_rt";
constexpr size_t kMaxNameBytes = 32; 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<int> ParseCsv(std::string_view raw) {
std::vector<int> 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<int>(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 NormalizeName(std::string_view name) {
std::string normalized(name); std::string normalized(name);
if (normalized.size() > kMaxNameBytes) { if (normalized.size() > kMaxNameBytes) {
@@ -95,19 +70,12 @@ const char* PhyKindToString(DaliPhyKind phy_kind) {
} // namespace } // namespace
GatewayController::GatewayController(GatewayRuntime& runtime, DaliDomainService& dali_domain, GatewayController::GatewayController(GatewayRuntime& runtime, DaliDomainService& dali_domain,
GatewayControllerConfig config) GatewayCache& cache, GatewayControllerConfig config)
: runtime_(runtime), dali_domain_(dali_domain), config_(config) {} : runtime_(runtime), dali_domain_(dali_domain), cache_(cache), config_(config) {}
GatewayController::~GatewayController() { GatewayController::~GatewayController() = default;
closeStorage();
}
esp_err_t GatewayController::start() { 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(); const auto device_info = runtime_.deviceInfo();
ble_enabled_ = device_info.ble_enabled; ble_enabled_ = device_info.ble_enabled;
refreshRuntimeGatewayNames(); refreshRuntimeGatewayNames();
@@ -117,8 +85,7 @@ esp_err_t GatewayController::start() {
dali_domain_.addRawFrameSink([this](const DaliRawFrame& frame) { handleDaliRawFrame(frame); }); dali_domain_.addRawFrameSink([this](const DaliRawFrame& frame) { handleDaliRawFrame(frame); });
for (const auto& channel : dali_domain_.channelInfo()) { for (const auto& channel : dali_domain_.channelInfo()) {
sceneStore(channel.gateway_id); cache_.preloadChannel(channel.gateway_id);
groupStore(channel.gateway_id);
dali_domain_.resetBus(channel.gateway_id); dali_domain_.resetBus(channel.gateway_id);
publishPayload(channel.gateway_id, {0x02, channel.gateway_id, 0x88}); 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; return raw_addr;
} }
const uint8_t slot = static_cast<uint8_t>((raw_addr - 0x80) / 2); const uint8_t slot = static_cast<uint8_t>((raw_addr - 0x80) / 2);
auto* group_data = group(gateway_id, slot); const auto group_data = cache_.group(gateway_id, slot);
if (group_data == nullptr || !group_data->enabled) { if (!group_data.enabled) {
return raw_addr; 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) { 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; 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) { bool GatewayController::setSceneEnabled(uint8_t gateway_id, uint8_t scene_id, bool enabled) {
auto* scene_data = scene(gateway_id, scene_id); return cache_.setSceneEnabled(gateway_id, scene_id, enabled);
if (scene_data == nullptr) {
return false;
}
if (scene_data->enabled == enabled) {
return true;
}
scene_data->enabled = enabled;
return saveScene(gateway_id, scene_id);
} }
bool GatewayController::setSceneDetail(uint8_t gateway_id, uint8_t scene_id, uint8_t brightness, 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 color_mode, uint8_t data1, uint8_t data2,
uint8_t data3) { uint8_t data3) {
auto* scene_data = scene(gateway_id, scene_id);
if (scene_data == nullptr) {
return false;
}
const uint8_t next_brightness = std::min<uint8_t>(brightness, 254); const uint8_t next_brightness = std::min<uint8_t>(brightness, 254);
const uint8_t next_color_mode = std::min<uint8_t>(color_mode, 2); const uint8_t next_color_mode = std::min<uint8_t>(color_mode, 2);
uint8_t next_data1 = 0; 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_data2 = data2;
next_data3 = data3; next_data3 = data3;
} }
if (scene_data->brightness == next_brightness && scene_data->color_mode == next_color_mode && return cache_.setSceneDetail(gateway_id, scene_id, next_brightness, next_color_mode,
scene_data->data1 == next_data1 && scene_data->data2 == next_data2 && next_data1, next_data2, next_data3);
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);
} }
bool GatewayController::setSceneName(uint8_t gateway_id, uint8_t scene_id, std::string_view name) { 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); const auto normalized = NormalizeName(name);
if (scene_data->name == normalized) { return cache_.setSceneName(gateway_id, scene_id, normalized);
return true;
}
scene_data->name = normalized;
return saveSceneName(gateway_id, scene_id, scene_data->name);
} }
bool GatewayController::deleteScene(uint8_t gateway_id, uint8_t scene_id) { bool GatewayController::deleteScene(uint8_t gateway_id, uint8_t scene_id) {
auto* scene_data = scene(gateway_id, scene_id); return cache_.deleteScene(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;
} }
std::pair<uint8_t, uint8_t> GatewayController::sceneMask(uint8_t gateway_id) { std::pair<uint8_t, uint8_t> GatewayController::sceneMask(uint8_t gateway_id) {
const auto& scenes = sceneStore(gateway_id); return cache_.sceneMask(gateway_id);
uint16_t mask = 0;
for (size_t index = 0; index < scenes.size(); ++index) {
if (scenes[index].enabled) {
mask |= static_cast<uint16_t>(1U << index);
}
}
return {static_cast<uint8_t>(mask & 0xff), static_cast<uint8_t>((mask >> 8) & 0xff)};
} }
bool GatewayController::executeScene(uint8_t gateway_id, int short_address, uint8_t scene_id) { bool GatewayController::executeScene(uint8_t gateway_id, int short_address, uint8_t scene_id) {
auto* scene_data = scene(gateway_id, scene_id); const auto scene_data = cache_.scene(gateway_id, scene_id);
if (scene_data == nullptr || !scene_data->enabled) { if (!scene_data.enabled) {
return false; return false;
} }
if (scene_data->brightness <= 0) { if (scene_data.brightness <= 0) {
dali_domain_.off(gateway_id, short_address); dali_domain_.off(gateway_id, short_address);
} else { } 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) { if (scene_data.color_mode == 0) {
int kelvin = scene_data->data1 * 256 + scene_data->data2; int kelvin = scene_data.data1 * 256 + scene_data.data2;
if (kelvin > 0) { if (kelvin > 0) {
if (config_.color_temperature_max < config_.color_temperature_min) { if (config_.color_temperature_max < config_.color_temperature_min) {
kelvin = reverseInRange(kelvin, 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); dali_domain_.setColTemp(gateway_id, short_address, kelvin);
} }
} else if (scene_data->color_mode == 1) { } else if (scene_data.color_mode == 1) {
if (scene_data->data1 != 0 || scene_data->data2 != 0 || scene_data->data3 != 0) { 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, dali_domain_.setColourRGB(gateway_id, short_address, scene_data.data1, scene_data.data2,
scene_data->data3); scene_data.data3);
} else if (scene_data->brightness <= 0) { } else if (scene_data.brightness <= 0) {
dali_domain_.off(gateway_id, short_address); 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) { bool GatewayController::setGroupEnabled(uint8_t gateway_id, uint8_t group_id, bool enabled) {
auto* group_data = group(gateway_id, group_id); return cache_.setGroupEnabled(gateway_id, group_id, enabled);
if (group_data == nullptr) {
return false;
}
if (group_data->enabled == enabled) {
return true;
}
group_data->enabled = enabled;
return saveGroup(gateway_id, group_id);
} }
bool GatewayController::setGroupDetail(uint8_t gateway_id, uint8_t group_id, uint8_t target_type, bool GatewayController::setGroupDetail(uint8_t gateway_id, uint8_t group_id, uint8_t target_type,
uint8_t target_value) { 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_type = normalizeGroupTargetType(target_type);
const uint8_t next_target_value = normalizeGroupTargetValue(next_target_type, target_value); 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 cache_.setGroupDetail(gateway_id, group_id, next_target_type, 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);
} }
bool GatewayController::setGroupName(uint8_t gateway_id, uint8_t group_id, std::string_view name) { 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); const auto normalized = NormalizeName(name);
if (group_data->name == normalized) { return cache_.setGroupName(gateway_id, group_id, normalized);
return true;
}
group_data->name = normalized;
return saveGroupName(gateway_id, group_id, group_data->name);
} }
bool GatewayController::deleteGroup(uint8_t gateway_id, uint8_t group_id) { bool GatewayController::deleteGroup(uint8_t gateway_id, uint8_t group_id) {
auto* group_data = group(gateway_id, group_id); return cache_.deleteGroup(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;
} }
std::pair<uint8_t, uint8_t> GatewayController::groupMask(uint8_t gateway_id) { std::pair<uint8_t, uint8_t> GatewayController::groupMask(uint8_t gateway_id) {
const auto& groups = groupStore(gateway_id); return cache_.groupMask(gateway_id);
uint16_t mask = 0;
for (size_t index = 0; index < groups.size(); ++index) {
if (groups[index].enabled) {
mask |= static_cast<uint16_t>(1U << index);
}
}
return {static_cast<uint8_t>(mask & 0xff), static_cast<uint8_t>((mask >> 8) & 0xff)};
} }
bool GatewayController::executeGroup(uint8_t gateway_id, uint8_t group_id) { bool GatewayController::executeGroup(uint8_t gateway_id, uint8_t group_id) {
auto* group_data = group(gateway_id, group_id); const auto group_data = cache_.group(gateway_id, group_id);
if (group_data == nullptr || !group_data->enabled) { if (!group_data.enabled) {
return false; return false;
} }
return dali_domain_.on(gateway_id, return dali_domain_.on(gateway_id,
internalGroupDecTargetAddress(group_data->target_type, internalGroupDecTargetAddress(group_data.target_type,
group_data->target_value)); group_data.target_value));
} }
void GatewayController::handleGatewayNameCommand(uint8_t gateway_id, void GatewayController::handleGatewayNameCommand(uint8_t gateway_id,
@@ -907,7 +750,6 @@ void GatewayController::handleInternalSceneCommand(uint8_t gateway_id,
return; return;
} }
auto* scene_data = scene(gateway_id, scene_id);
switch (op) { switch (op) {
case 0x00: case 0x00:
publishPayload(gateway_id, setSceneEnabled(gateway_id, scene_id, true) publishPayload(gateway_id, setSceneEnabled(gateway_id, scene_id, true)
@@ -921,7 +763,7 @@ void GatewayController::handleInternalSceneCommand(uint8_t gateway_id,
break; break;
case 0x02: case 0x02:
publishPayload(gateway_id, {0xA0, gateway_id, op, scene_id, publishPayload(gateway_id, {0xA0, gateway_id, op, scene_id,
static_cast<uint8_t>(scene_data->enabled ? 1 : 0)}); static_cast<uint8_t>(cache_.scene(gateway_id, scene_id).enabled ? 1 : 0)});
break; break;
case 0x03: { case 0x03: {
const auto [low, high] = sceneMask(gateway_id); const auto [low, high] = sceneMask(gateway_id);
@@ -929,10 +771,13 @@ void GatewayController::handleInternalSceneCommand(uint8_t gateway_id,
break; break;
} }
case 0x04: case 0x04:
publishPayload(gateway_id, {0xA0, gateway_id, op, scene_id, scene_data->brightness, {
scene_data->color_mode, scene_data->data1, scene_data->data2, const auto scene_data = cache_.scene(gateway_id, scene_id);
scene_data->data3}); publishPayload(gateway_id, {0xA0, gateway_id, op, scene_id, scene_data.brightness,
break; scene_data.color_mode, scene_data.data1, scene_data.data2,
scene_data.data3});
break;
}
case 0x05: case 0x05:
if (command.size() >= 11 && setSceneDetail(gateway_id, scene_id, command[6], command[7], if (command.size() >= 11 && setSceneDetail(gateway_id, scene_id, command[6], command[7],
command[8], command[9], command[10])) { command[8], command[9], command[10])) {
@@ -947,8 +792,9 @@ void GatewayController::handleInternalSceneCommand(uint8_t gateway_id,
: std::vector<uint8_t>{0xA1, gateway_id, op, scene_id, 0x02}); : std::vector<uint8_t>{0xA1, gateway_id, op, scene_id, 0x02});
break; break;
case 0x07: { case 0x07: {
const auto scene_data = cache_.scene(gateway_id, scene_id);
std::vector<uint8_t> payload{0xA0, gateway_id, op, scene_id}; std::vector<uint8_t> payload{0xA0, gateway_id, op, scene_id};
AppendPaddedName(payload, scene_data->name); AppendPaddedName(payload, scene_data.name);
publishPayload(gateway_id, payload); publishPayload(gateway_id, payload);
break; break;
} }
@@ -989,7 +835,6 @@ void GatewayController::handleInternalGroupCommand(uint8_t gateway_id,
return; return;
} }
auto* group_data = group(gateway_id, group_id);
switch (op) { switch (op) {
case 0x00: case 0x00:
publishPayload(gateway_id, setGroupEnabled(gateway_id, group_id, true) publishPayload(gateway_id, setGroupEnabled(gateway_id, group_id, true)
@@ -1003,7 +848,7 @@ void GatewayController::handleInternalGroupCommand(uint8_t gateway_id,
break; break;
case 0x02: case 0x02:
publishPayload(gateway_id, {0xA2, gateway_id, op, group_id, publishPayload(gateway_id, {0xA2, gateway_id, op, group_id,
static_cast<uint8_t>(group_data->enabled ? 1 : 0)}); static_cast<uint8_t>(cache_.group(gateway_id, group_id).enabled ? 1 : 0)});
break; break;
case 0x03: { case 0x03: {
const auto [low, high] = groupMask(gateway_id); const auto [low, high] = groupMask(gateway_id);
@@ -1011,11 +856,14 @@ void GatewayController::handleInternalGroupCommand(uint8_t gateway_id,
break; break;
} }
case 0x04: case 0x04:
publishPayload(gateway_id, {0xA2, gateway_id, op, group_id, {
normalizeGroupTargetType(group_data->target_type), const auto group_data = cache_.group(gateway_id, group_id);
normalizeGroupTargetValue(group_data->target_type, publishPayload(gateway_id, {0xA2, gateway_id, op, group_id,
group_data->target_value)}); normalizeGroupTargetType(group_data.target_type),
break; normalizeGroupTargetValue(group_data.target_type,
group_data.target_value)});
break;
}
case 0x05: case 0x05:
if (command.size() >= 9 && setGroupDetail(gateway_id, group_id, command[6], command[7])) { if (command.size() >= 9 && setGroupDetail(gateway_id, group_id, command[6], command[7])) {
publishPayload(gateway_id, {0xA2, gateway_id, op, group_id, 0x01}); publishPayload(gateway_id, {0xA2, gateway_id, op, group_id, 0x01});
@@ -1029,8 +877,9 @@ void GatewayController::handleInternalGroupCommand(uint8_t gateway_id,
: std::vector<uint8_t>{0xA3, gateway_id, op, group_id, 0x02}); : std::vector<uint8_t>{0xA3, gateway_id, op, group_id, 0x02});
break; break;
case 0x07: { case 0x07: {
const auto group_data = cache_.group(gateway_id, group_id);
std::vector<uint8_t> payload{0xA2, gateway_id, op, group_id}; std::vector<uint8_t> payload{0xA2, gateway_id, op, group_id};
AppendPaddedName(payload, group_data->name); AppendPaddedName(payload, group_data.name);
publishPayload(gateway_id, payload); publishPayload(gateway_id, payload);
break; 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<uint8_t>(std::clamp(values[1], 0, 254));
scenes[scene_id].color_mode = static_cast<uint8_t>(std::clamp(values[2], 0, 2));
scenes[scene_id].data1 = static_cast<uint8_t>(std::clamp(values[3], 0, 255));
scenes[scene_id].data2 = static_cast<uint8_t>(std::clamp(values[4], 0, 255));
scenes[scene_id].data3 = static_cast<uint8_t>(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<uint8_t>(std::clamp(values[1], 0, 255));
uint8_t target_value = values.size() >= 3 ? static_cast<uint8_t>(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 } // namespace gateway