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:
@@ -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
|
||||
Reference in New Issue
Block a user