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
@@ -15,33 +15,8 @@ namespace gateway {
namespace {
constexpr const char* kTag = "gateway_controller";
constexpr const char* kStorageNamespace = "gateway_rt";
constexpr size_t kMaxNameBytes = 32;
std::string ShortKey(const char* prefix, uint8_t gateway_id, uint8_t slot) {
char key[16] = {0};
std::snprintf(key, sizeof(key), "%s%u_%u", prefix, gateway_id, slot);
return std::string(key);
}
std::vector<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) {
@@ -95,19 +70,12 @@ const char* PhyKindToString(DaliPhyKind phy_kind) {
} // namespace
GatewayController::GatewayController(GatewayRuntime& runtime, DaliDomainService& dali_domain,
GatewayControllerConfig config)
: runtime_(runtime), dali_domain_(dali_domain), config_(config) {}
GatewayCache& cache, GatewayControllerConfig config)
: runtime_(runtime), dali_domain_(dali_domain), cache_(cache), config_(config) {}
GatewayController::~GatewayController() {
closeStorage();
}
GatewayController::~GatewayController() = default;
esp_err_t GatewayController::start() {
const esp_err_t err = openStorage() ? ESP_OK : ESP_FAIL;
if (err != ESP_OK) {
return err;
}
const auto device_info = runtime_.deviceInfo();
ble_enabled_ = device_info.ble_enabled;
refreshRuntimeGatewayNames();
@@ -117,8 +85,7 @@ esp_err_t GatewayController::start() {
dali_domain_.addRawFrameSink([this](const DaliRawFrame& frame) { handleDaliRawFrame(frame); });
for (const auto& channel : dali_domain_.channelInfo()) {
sceneStore(channel.gateway_id);
groupStore(channel.gateway_id);
cache_.preloadChannel(channel.gateway_id);
dali_domain_.resetBus(channel.gateway_id);
publishPayload(channel.gateway_id, {0x02, channel.gateway_id, 0x88});
}
@@ -539,11 +506,11 @@ uint8_t GatewayController::resolveInternalGroupRawAddress(uint8_t gateway_id, ui
return raw_addr;
}
const uint8_t slot = static_cast<uint8_t>((raw_addr - 0x80) / 2);
auto* group_data = group(gateway_id, slot);
if (group_data == nullptr || !group_data->enabled) {
const auto group_data = cache_.group(gateway_id, slot);
if (!group_data.enabled) {
return raw_addr;
}
return internalGroupRawTargetAddress(group_data->target_type, group_data->target_value, raw_addr);
return internalGroupRawTargetAddress(group_data.target_type, group_data.target_value, raw_addr);
}
uint8_t GatewayController::normalizeGroupTargetType(uint8_t target_type) {
@@ -595,55 +562,13 @@ int GatewayController::reverseInRange(int value, int min_value, int max_value) {
return min_value + max_value - value;
}
GatewayController::SceneStore& GatewayController::sceneStore(uint8_t gateway_id) {
auto [it, inserted] = scenes_.try_emplace(gateway_id);
if (inserted) {
loadSceneStore(gateway_id, it->second);
}
return it->second;
}
GatewayController::GroupStore& GatewayController::groupStore(uint8_t gateway_id) {
auto [it, inserted] = groups_.try_emplace(gateway_id);
if (inserted) {
loadGroupStore(gateway_id, it->second);
}
return it->second;
}
GatewayController::InternalScene* GatewayController::scene(uint8_t gateway_id, uint8_t scene_id) {
if (scene_id >= 16) {
return nullptr;
}
return &sceneStore(gateway_id)[scene_id];
}
GatewayController::InternalGroup* GatewayController::group(uint8_t gateway_id, uint8_t group_id) {
if (group_id >= 16) {
return nullptr;
}
return &groupStore(gateway_id)[group_id];
}
bool GatewayController::setSceneEnabled(uint8_t gateway_id, uint8_t scene_id, bool enabled) {
auto* scene_data = scene(gateway_id, scene_id);
if (scene_data == nullptr) {
return false;
}
if (scene_data->enabled == enabled) {
return true;
}
scene_data->enabled = enabled;
return saveScene(gateway_id, scene_id);
return cache_.setSceneEnabled(gateway_id, scene_id, enabled);
}
bool GatewayController::setSceneDetail(uint8_t gateway_id, uint8_t scene_id, uint8_t brightness,
uint8_t color_mode, uint8_t data1, uint8_t data2,
uint8_t data3) {
auto* scene_data = scene(gateway_id, scene_id);
if (scene_data == nullptr) {
return false;
}
const uint8_t next_brightness = std::min<uint8_t>(brightness, 254);
const uint8_t next_color_mode = std::min<uint8_t>(color_mode, 2);
uint8_t next_data1 = 0;
@@ -657,73 +582,35 @@ bool GatewayController::setSceneDetail(uint8_t gateway_id, uint8_t scene_id, uin
next_data2 = data2;
next_data3 = data3;
}
if (scene_data->brightness == next_brightness && scene_data->color_mode == next_color_mode &&
scene_data->data1 == next_data1 && scene_data->data2 == next_data2 &&
scene_data->data3 == next_data3) {
return true;
}
scene_data->brightness = next_brightness;
scene_data->color_mode = next_color_mode;
scene_data->data1 = next_data1;
scene_data->data2 = next_data2;
scene_data->data3 = next_data3;
return saveScene(gateway_id, scene_id);
return cache_.setSceneDetail(gateway_id, scene_id, next_brightness, next_color_mode,
next_data1, next_data2, next_data3);
}
bool GatewayController::setSceneName(uint8_t gateway_id, uint8_t scene_id, std::string_view name) {
auto* scene_data = scene(gateway_id, scene_id);
if (scene_data == nullptr) {
return false;
}
const auto normalized = NormalizeName(name);
if (scene_data->name == normalized) {
return true;
}
scene_data->name = normalized;
return saveSceneName(gateway_id, scene_id, scene_data->name);
return cache_.setSceneName(gateway_id, scene_id, normalized);
}
bool GatewayController::deleteScene(uint8_t gateway_id, uint8_t scene_id) {
auto* scene_data = scene(gateway_id, scene_id);
if (scene_data == nullptr) {
return false;
}
const bool already_default = !scene_data->enabled && scene_data->brightness == 254 &&
scene_data->color_mode == 2 && scene_data->data1 == 0 &&
scene_data->data2 == 0 && scene_data->data3 == 0 &&
scene_data->name.empty();
if (already_default) {
return true;
}
*scene_data = InternalScene{};
deleteSceneStorage(gateway_id, scene_id);
deleteSceneNameStorage(gateway_id, scene_id);
return true;
return cache_.deleteScene(gateway_id, scene_id);
}
std::pair<uint8_t, uint8_t> GatewayController::sceneMask(uint8_t gateway_id) {
const auto& scenes = sceneStore(gateway_id);
uint16_t mask = 0;
for (size_t index = 0; index < scenes.size(); ++index) {
if (scenes[index].enabled) {
mask |= static_cast<uint16_t>(1U << index);
}
}
return {static_cast<uint8_t>(mask & 0xff), static_cast<uint8_t>((mask >> 8) & 0xff)};
return cache_.sceneMask(gateway_id);
}
bool GatewayController::executeScene(uint8_t gateway_id, int short_address, uint8_t scene_id) {
auto* scene_data = scene(gateway_id, scene_id);
if (scene_data == nullptr || !scene_data->enabled) {
const auto scene_data = cache_.scene(gateway_id, scene_id);
if (!scene_data.enabled) {
return false;
}
if (scene_data->brightness <= 0) {
if (scene_data.brightness <= 0) {
dali_domain_.off(gateway_id, short_address);
} else {
dali_domain_.setBright(gateway_id, short_address, scene_data->brightness);
dali_domain_.setBright(gateway_id, short_address, scene_data.brightness);
}
if (scene_data->color_mode == 0) {
int kelvin = scene_data->data1 * 256 + scene_data->data2;
if (scene_data.color_mode == 0) {
int kelvin = scene_data.data1 * 256 + scene_data.data2;
if (kelvin > 0) {
if (config_.color_temperature_max < config_.color_temperature_min) {
kelvin = reverseInRange(kelvin, config_.color_temperature_min,
@@ -731,11 +618,11 @@ bool GatewayController::executeScene(uint8_t gateway_id, int short_address, uint
}
dali_domain_.setColTemp(gateway_id, short_address, kelvin);
}
} else if (scene_data->color_mode == 1) {
if (scene_data->data1 != 0 || scene_data->data2 != 0 || scene_data->data3 != 0) {
dali_domain_.setColourRGB(gateway_id, short_address, scene_data->data1, scene_data->data2,
scene_data->data3);
} else if (scene_data->brightness <= 0) {
} else if (scene_data.color_mode == 1) {
if (scene_data.data1 != 0 || scene_data.data2 != 0 || scene_data.data3 != 0) {
dali_domain_.setColourRGB(gateway_id, short_address, scene_data.data1, scene_data.data2,
scene_data.data3);
} else if (scene_data.brightness <= 0) {
dali_domain_.off(gateway_id, short_address);
}
}
@@ -743,81 +630,37 @@ bool GatewayController::executeScene(uint8_t gateway_id, int short_address, uint
}
bool GatewayController::setGroupEnabled(uint8_t gateway_id, uint8_t group_id, bool enabled) {
auto* group_data = group(gateway_id, group_id);
if (group_data == nullptr) {
return false;
}
if (group_data->enabled == enabled) {
return true;
}
group_data->enabled = enabled;
return saveGroup(gateway_id, group_id);
return cache_.setGroupEnabled(gateway_id, group_id, enabled);
}
bool GatewayController::setGroupDetail(uint8_t gateway_id, uint8_t group_id, uint8_t target_type,
uint8_t target_value) {
auto* group_data = group(gateway_id, group_id);
if (group_data == nullptr) {
return false;
}
const uint8_t next_target_type = normalizeGroupTargetType(target_type);
const uint8_t next_target_value = normalizeGroupTargetValue(next_target_type, target_value);
if (group_data->target_type == next_target_type && group_data->target_value == next_target_value) {
return true;
}
group_data->target_type = next_target_type;
group_data->target_value = next_target_value;
return saveGroup(gateway_id, group_id);
return cache_.setGroupDetail(gateway_id, group_id, next_target_type, next_target_value);
}
bool GatewayController::setGroupName(uint8_t gateway_id, uint8_t group_id, std::string_view name) {
auto* group_data = group(gateway_id, group_id);
if (group_data == nullptr) {
return false;
}
const auto normalized = NormalizeName(name);
if (group_data->name == normalized) {
return true;
}
group_data->name = normalized;
return saveGroupName(gateway_id, group_id, group_data->name);
return cache_.setGroupName(gateway_id, group_id, normalized);
}
bool GatewayController::deleteGroup(uint8_t gateway_id, uint8_t group_id) {
auto* group_data = group(gateway_id, group_id);
if (group_data == nullptr) {
return false;
}
const bool already_default = !group_data->enabled && group_data->target_type == 2 &&
group_data->target_value == 0 && group_data->name.empty();
if (already_default) {
return true;
}
*group_data = InternalGroup{};
deleteGroupStorage(gateway_id, group_id);
deleteGroupNameStorage(gateway_id, group_id);
return true;
return cache_.deleteGroup(gateway_id, group_id);
}
std::pair<uint8_t, uint8_t> GatewayController::groupMask(uint8_t gateway_id) {
const auto& groups = groupStore(gateway_id);
uint16_t mask = 0;
for (size_t index = 0; index < groups.size(); ++index) {
if (groups[index].enabled) {
mask |= static_cast<uint16_t>(1U << index);
}
}
return {static_cast<uint8_t>(mask & 0xff), static_cast<uint8_t>((mask >> 8) & 0xff)};
return cache_.groupMask(gateway_id);
}
bool GatewayController::executeGroup(uint8_t gateway_id, uint8_t group_id) {
auto* group_data = group(gateway_id, group_id);
if (group_data == nullptr || !group_data->enabled) {
const auto group_data = cache_.group(gateway_id, group_id);
if (!group_data.enabled) {
return false;
}
return dali_domain_.on(gateway_id,
internalGroupDecTargetAddress(group_data->target_type,
group_data->target_value));
internalGroupDecTargetAddress(group_data.target_type,
group_data.target_value));
}
void GatewayController::handleGatewayNameCommand(uint8_t gateway_id,
@@ -907,7 +750,6 @@ void GatewayController::handleInternalSceneCommand(uint8_t gateway_id,
return;
}
auto* scene_data = scene(gateway_id, scene_id);
switch (op) {
case 0x00:
publishPayload(gateway_id, setSceneEnabled(gateway_id, scene_id, true)
@@ -921,7 +763,7 @@ void GatewayController::handleInternalSceneCommand(uint8_t gateway_id,
break;
case 0x02:
publishPayload(gateway_id, {0xA0, gateway_id, op, scene_id,
static_cast<uint8_t>(scene_data->enabled ? 1 : 0)});
static_cast<uint8_t>(cache_.scene(gateway_id, scene_id).enabled ? 1 : 0)});
break;
case 0x03: {
const auto [low, high] = sceneMask(gateway_id);
@@ -929,10 +771,13 @@ void GatewayController::handleInternalSceneCommand(uint8_t gateway_id,
break;
}
case 0x04:
publishPayload(gateway_id, {0xA0, gateway_id, op, scene_id, scene_data->brightness,
scene_data->color_mode, scene_data->data1, scene_data->data2,
scene_data->data3});
break;
{
const auto scene_data = cache_.scene(gateway_id, scene_id);
publishPayload(gateway_id, {0xA0, gateway_id, op, scene_id, scene_data.brightness,
scene_data.color_mode, scene_data.data1, scene_data.data2,
scene_data.data3});
break;
}
case 0x05:
if (command.size() >= 11 && setSceneDetail(gateway_id, scene_id, command[6], command[7],
command[8], command[9], command[10])) {
@@ -947,8 +792,9 @@ void GatewayController::handleInternalSceneCommand(uint8_t gateway_id,
: std::vector<uint8_t>{0xA1, gateway_id, op, scene_id, 0x02});
break;
case 0x07: {
const auto scene_data = cache_.scene(gateway_id, 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);
break;
}
@@ -989,7 +835,6 @@ void GatewayController::handleInternalGroupCommand(uint8_t gateway_id,
return;
}
auto* group_data = group(gateway_id, group_id);
switch (op) {
case 0x00:
publishPayload(gateway_id, setGroupEnabled(gateway_id, group_id, true)
@@ -1003,7 +848,7 @@ void GatewayController::handleInternalGroupCommand(uint8_t gateway_id,
break;
case 0x02:
publishPayload(gateway_id, {0xA2, gateway_id, op, group_id,
static_cast<uint8_t>(group_data->enabled ? 1 : 0)});
static_cast<uint8_t>(cache_.group(gateway_id, group_id).enabled ? 1 : 0)});
break;
case 0x03: {
const auto [low, high] = groupMask(gateway_id);
@@ -1011,11 +856,14 @@ void GatewayController::handleInternalGroupCommand(uint8_t gateway_id,
break;
}
case 0x04:
publishPayload(gateway_id, {0xA2, gateway_id, op, group_id,
normalizeGroupTargetType(group_data->target_type),
normalizeGroupTargetValue(group_data->target_type,
group_data->target_value)});
break;
{
const auto group_data = cache_.group(gateway_id, group_id);
publishPayload(gateway_id, {0xA2, gateway_id, op, group_id,
normalizeGroupTargetType(group_data.target_type),
normalizeGroupTargetValue(group_data.target_type,
group_data.target_value)});
break;
}
case 0x05:
if (command.size() >= 9 && setGroupDetail(gateway_id, group_id, command[6], command[7])) {
publishPayload(gateway_id, {0xA2, gateway_id, op, group_id, 0x01});
@@ -1029,8 +877,9 @@ void GatewayController::handleInternalGroupCommand(uint8_t gateway_id,
: std::vector<uint8_t>{0xA3, gateway_id, op, group_id, 0x02});
break;
case 0x07: {
const auto group_data = cache_.group(gateway_id, 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);
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