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:
@@ -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)
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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());
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -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,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
|
||||||
|
|||||||
Reference in New Issue
Block a user