feat(gateway): implement reconciliation mechanism and command prioritization
- Introduced a reconciliation job structure to manage the reconciliation process for gateway channels. - Added methods to schedule and run reconciliation steps, including group, scene, and settings reconciliation. - Implemented a locking mechanism to ensure thread safety during reconciliation operations. - Enhanced command handling in GatewayRuntime to classify commands by priority (control, normal, maintenance). - Updated command enqueueing and processing to respect command priorities, ensuring maintenance commands are handled appropriately. - Added configuration options for enabling/disabling cache functionality in GatewayRuntime. - Improved logging to include cache status during runtime initialization. Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
@@ -14,6 +14,26 @@ namespace {
|
||||
|
||||
constexpr const char* kTag = "gateway_cache";
|
||||
constexpr size_t kMaxNameBytes = 32;
|
||||
constexpr uint8_t kDaliCmdReset = 0x20;
|
||||
constexpr uint8_t kDaliCmdStoreDtrAsMaxLevel = 0x2A;
|
||||
constexpr uint8_t kDaliCmdStoreDtrAsMinLevel = 0x2B;
|
||||
constexpr uint8_t kDaliCmdStoreDtrAsSystemFailureLevel = 0x2C;
|
||||
constexpr uint8_t kDaliCmdStoreDtrAsPowerOnLevel = 0x2D;
|
||||
constexpr uint8_t kDaliCmdStoreDtrAsFadeTime = 0x2E;
|
||||
constexpr uint8_t kDaliCmdStoreDtrAsFadeRate = 0x2F;
|
||||
constexpr uint8_t kDaliCmdSetSceneMin = 0x40;
|
||||
constexpr uint8_t kDaliCmdRemoveSceneMax = 0x5F;
|
||||
constexpr uint8_t kDaliCmdAddToGroupMin = 0x60;
|
||||
constexpr uint8_t kDaliCmdRemoveFromGroupMax = 0x7F;
|
||||
constexpr uint8_t kDaliCmdStoreDtrAsShortAddress = 0x80;
|
||||
constexpr uint8_t kDaliCmdSetDtr0 = 0xA3;
|
||||
constexpr uint8_t kDaliCmdSpecialProgramShortAddress = 0xB7;
|
||||
constexpr uint8_t kDaliCmdSetDtr1 = 0xC3;
|
||||
constexpr uint8_t kDaliCmdSetDtr2 = 0xC5;
|
||||
constexpr uint8_t kDaliCmdDt8StoreDtrAsColorX = 0xE0;
|
||||
constexpr uint8_t kDaliCmdDt8StoreDtrAsColorY = 0xE1;
|
||||
constexpr uint8_t kDaliCmdDt8StorePrimaryMin = 0xF0;
|
||||
constexpr uint8_t kDaliCmdDt8StartAutoCalibration = 0xF6;
|
||||
|
||||
class LockGuard {
|
||||
public:
|
||||
@@ -74,6 +94,95 @@ bool IsDefaultGroup(const GatewayCache::GroupEntry& group) {
|
||||
return !group.enabled && group.target_type == 2 && group.target_value == 0;
|
||||
}
|
||||
|
||||
bool SameFlags(const GatewayCacheChannelFlags& lhs, const GatewayCacheChannelFlags& rhs) {
|
||||
return lhs.need_update_group == rhs.need_update_group &&
|
||||
lhs.need_update_scene == rhs.need_update_scene &&
|
||||
lhs.need_update_settings == rhs.need_update_settings;
|
||||
}
|
||||
|
||||
bool AnyFlagSet(const GatewayCacheChannelFlags& flags) {
|
||||
return flags.need_update_group || flags.need_update_scene || flags.need_update_settings;
|
||||
}
|
||||
|
||||
std::optional<uint8_t> DecodeShortAddress(uint8_t raw_addr) {
|
||||
if (raw_addr <= 0x7F && (raw_addr & 0x01) != 0) {
|
||||
return static_cast<uint8_t>(raw_addr >> 1);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
bool ShouldMirrorObservedMutation(GatewayCacheRawFrameOrigin origin,
|
||||
GatewayCachePriorityMode priority_mode) {
|
||||
return origin == GatewayCacheRawFrameOrigin::kLocalGateway ||
|
||||
priority_mode == GatewayCachePriorityMode::kOutsideBusFirst;
|
||||
}
|
||||
|
||||
void ClearDaliState(GatewayCacheDaliAddressState& state) {
|
||||
state.group_mask_known = false;
|
||||
state.group_mask = 0;
|
||||
state.scene_levels.fill(std::nullopt);
|
||||
state.settings = {};
|
||||
}
|
||||
|
||||
void ApplyObservedSettingsValue(GatewayCacheDaliSettingsSnapshot& settings, uint8_t command,
|
||||
uint8_t value) {
|
||||
switch (command) {
|
||||
case kDaliCmdStoreDtrAsMaxLevel:
|
||||
settings.max_level = value;
|
||||
break;
|
||||
case kDaliCmdStoreDtrAsMinLevel:
|
||||
settings.min_level = value;
|
||||
break;
|
||||
case kDaliCmdStoreDtrAsSystemFailureLevel:
|
||||
settings.system_failure_level = value;
|
||||
break;
|
||||
case kDaliCmdStoreDtrAsPowerOnLevel:
|
||||
settings.power_on_level = value;
|
||||
break;
|
||||
case kDaliCmdStoreDtrAsFadeTime:
|
||||
settings.fade_time = value;
|
||||
break;
|
||||
case kDaliCmdStoreDtrAsFadeRate:
|
||||
settings.fade_rate = value;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
GatewayCacheChannelFlags ClassifyDaliMutation(uint8_t raw_addr, uint8_t command) {
|
||||
GatewayCacheChannelFlags flags;
|
||||
if (raw_addr == kDaliCmdSpecialProgramShortAddress) {
|
||||
flags.need_update_settings = true;
|
||||
return flags;
|
||||
}
|
||||
|
||||
const bool special_command = raw_addr >= 0xA1 && raw_addr <= 0xC5 && (raw_addr & 0x01) != 0;
|
||||
if (special_command || (raw_addr & 0x01) == 0) {
|
||||
return flags;
|
||||
}
|
||||
|
||||
if (command == kDaliCmdReset) {
|
||||
flags.need_update_group = true;
|
||||
flags.need_update_scene = true;
|
||||
flags.need_update_settings = true;
|
||||
} else if (command >= kDaliCmdStoreDtrAsMaxLevel && command <= kDaliCmdStoreDtrAsFadeRate) {
|
||||
flags.need_update_settings = true;
|
||||
} else if (command >= kDaliCmdSetSceneMin && command <= kDaliCmdRemoveSceneMax) {
|
||||
flags.need_update_scene = true;
|
||||
} else if (command >= kDaliCmdAddToGroupMin && command <= kDaliCmdRemoveFromGroupMax) {
|
||||
flags.need_update_group = true;
|
||||
} else if (command == kDaliCmdStoreDtrAsShortAddress) {
|
||||
flags.need_update_settings = true;
|
||||
} else if (command == kDaliCmdDt8StoreDtrAsColorX || command == kDaliCmdDt8StoreDtrAsColorY ||
|
||||
(command >= kDaliCmdDt8StorePrimaryMin &&
|
||||
command <= kDaliCmdDt8StartAutoCalibration)) {
|
||||
flags.need_update_settings = true;
|
||||
}
|
||||
|
||||
return flags;
|
||||
}
|
||||
|
||||
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,
|
||||
@@ -103,7 +212,9 @@ GatewayCache::~GatewayCache() {
|
||||
|
||||
{
|
||||
LockGuard guard(lock_);
|
||||
flushDirty();
|
||||
if (config_.cache_enabled) {
|
||||
flushDirty();
|
||||
}
|
||||
closeStorageLocked();
|
||||
}
|
||||
|
||||
@@ -121,6 +232,12 @@ esp_err_t GatewayCache::start() {
|
||||
}
|
||||
}
|
||||
|
||||
if (!config_.cache_enabled) {
|
||||
ESP_LOGI(kTag, "cache disabled namespace=%s persistence=direct-nvs",
|
||||
config_.storage_namespace.c_str());
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
if (task_handle_ != nullptr) {
|
||||
return ESP_OK;
|
||||
}
|
||||
@@ -134,24 +251,38 @@ esp_err_t GatewayCache::start() {
|
||||
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));
|
||||
ESP_LOGI(kTag, "cache started namespace=%s flush_interval_ms=%u reconciliation=%d full_mirror=%d",
|
||||
config_.storage_namespace.c_str(), static_cast<unsigned>(config_.flush_interval_ms),
|
||||
config_.reconciliation_enabled, config_.full_state_mirror_enabled);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void GatewayCache::preloadChannel(uint8_t gateway_id) {
|
||||
LockGuard guard(lock_);
|
||||
if (!config_.cache_enabled) {
|
||||
return;
|
||||
}
|
||||
ensureSceneStoreLocked(gateway_id);
|
||||
ensureGroupStoreLocked(gateway_id);
|
||||
}
|
||||
|
||||
GatewayCache::SceneStore GatewayCache::scenes(uint8_t gateway_id) {
|
||||
LockGuard guard(lock_);
|
||||
if (!config_.cache_enabled) {
|
||||
SceneStore store;
|
||||
loadSceneStoreLocked(gateway_id, store);
|
||||
return store;
|
||||
}
|
||||
return ensureSceneStoreLocked(gateway_id);
|
||||
}
|
||||
|
||||
GatewayCache::GroupStore GatewayCache::groups(uint8_t gateway_id) {
|
||||
LockGuard guard(lock_);
|
||||
if (!config_.cache_enabled) {
|
||||
GroupStore store;
|
||||
loadGroupStoreLocked(gateway_id, store);
|
||||
return store;
|
||||
}
|
||||
return ensureGroupStoreLocked(gateway_id);
|
||||
}
|
||||
|
||||
@@ -160,6 +291,11 @@ GatewayCache::SceneEntry GatewayCache::scene(uint8_t gateway_id, uint8_t scene_i
|
||||
if (scene_id >= 16) {
|
||||
return {};
|
||||
}
|
||||
if (!config_.cache_enabled) {
|
||||
SceneStore store;
|
||||
loadSceneStoreLocked(gateway_id, store);
|
||||
return store[scene_id];
|
||||
}
|
||||
return ensureSceneStoreLocked(gateway_id)[scene_id];
|
||||
}
|
||||
|
||||
@@ -168,6 +304,11 @@ GatewayCache::GroupEntry GatewayCache::group(uint8_t gateway_id, uint8_t group_i
|
||||
if (group_id >= 16) {
|
||||
return {};
|
||||
}
|
||||
if (!config_.cache_enabled) {
|
||||
GroupStore store;
|
||||
loadGroupStoreLocked(gateway_id, store);
|
||||
return store[group_id];
|
||||
}
|
||||
return ensureGroupStoreLocked(gateway_id)[group_id];
|
||||
}
|
||||
|
||||
@@ -176,6 +317,16 @@ bool GatewayCache::setSceneEnabled(uint8_t gateway_id, uint8_t scene_id, bool en
|
||||
if (scene_id >= 16) {
|
||||
return false;
|
||||
}
|
||||
if (!config_.cache_enabled) {
|
||||
SceneStore store;
|
||||
loadSceneStoreLocked(gateway_id, store);
|
||||
auto& entry = store[scene_id];
|
||||
if (entry.enabled == enabled) {
|
||||
return true;
|
||||
}
|
||||
entry.enabled = enabled;
|
||||
return persistSceneLocked(gateway_id, scene_id, entry);
|
||||
}
|
||||
auto& entry = ensureSceneStoreLocked(gateway_id)[scene_id];
|
||||
if (entry.enabled == enabled) {
|
||||
return true;
|
||||
@@ -192,6 +343,21 @@ bool GatewayCache::setSceneDetail(uint8_t gateway_id, uint8_t scene_id, uint8_t
|
||||
if (scene_id >= 16) {
|
||||
return false;
|
||||
}
|
||||
if (!config_.cache_enabled) {
|
||||
SceneStore store;
|
||||
loadSceneStoreLocked(gateway_id, store);
|
||||
auto& entry = store[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;
|
||||
return persistSceneLocked(gateway_id, scene_id, entry);
|
||||
}
|
||||
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) {
|
||||
@@ -211,6 +377,17 @@ bool GatewayCache::setSceneName(uint8_t gateway_id, uint8_t scene_id, std::strin
|
||||
if (scene_id >= 16) {
|
||||
return false;
|
||||
}
|
||||
if (!config_.cache_enabled) {
|
||||
SceneStore store;
|
||||
loadSceneStoreLocked(gateway_id, store);
|
||||
auto& entry = store[scene_id];
|
||||
const auto normalized = NormalizeName(name);
|
||||
if (entry.name == normalized) {
|
||||
return true;
|
||||
}
|
||||
entry.name = normalized;
|
||||
return persistSceneLocked(gateway_id, scene_id, entry);
|
||||
}
|
||||
auto& entry = ensureSceneStoreLocked(gateway_id)[scene_id];
|
||||
const auto normalized = NormalizeName(name);
|
||||
if (entry.name == normalized) {
|
||||
@@ -226,6 +403,16 @@ bool GatewayCache::deleteScene(uint8_t gateway_id, uint8_t scene_id) {
|
||||
if (scene_id >= 16) {
|
||||
return false;
|
||||
}
|
||||
if (!config_.cache_enabled) {
|
||||
SceneStore store;
|
||||
loadSceneStoreLocked(gateway_id, store);
|
||||
auto& entry = store[scene_id];
|
||||
if (IsDefaultScene(entry) && entry.name.empty()) {
|
||||
return true;
|
||||
}
|
||||
entry = SceneEntry{};
|
||||
return persistSceneLocked(gateway_id, scene_id, entry);
|
||||
}
|
||||
auto& entry = ensureSceneStoreLocked(gateway_id)[scene_id];
|
||||
if (IsDefaultScene(entry) && entry.name.empty()) {
|
||||
return true;
|
||||
@@ -237,7 +424,11 @@ bool GatewayCache::deleteScene(uint8_t gateway_id, uint8_t scene_id) {
|
||||
|
||||
std::pair<uint8_t, uint8_t> GatewayCache::sceneMask(uint8_t gateway_id) {
|
||||
LockGuard guard(lock_);
|
||||
const auto& store = ensureSceneStoreLocked(gateway_id);
|
||||
SceneStore direct_store;
|
||||
if (!config_.cache_enabled) {
|
||||
loadSceneStoreLocked(gateway_id, direct_store);
|
||||
}
|
||||
const auto& store = config_.cache_enabled ? ensureSceneStoreLocked(gateway_id) : direct_store;
|
||||
uint16_t mask = 0;
|
||||
for (size_t index = 0; index < store.size(); ++index) {
|
||||
if (store[index].enabled) {
|
||||
@@ -252,6 +443,16 @@ bool GatewayCache::setGroupEnabled(uint8_t gateway_id, uint8_t group_id, bool en
|
||||
if (group_id >= 16) {
|
||||
return false;
|
||||
}
|
||||
if (!config_.cache_enabled) {
|
||||
GroupStore store;
|
||||
loadGroupStoreLocked(gateway_id, store);
|
||||
auto& entry = store[group_id];
|
||||
if (entry.enabled == enabled) {
|
||||
return true;
|
||||
}
|
||||
entry.enabled = enabled;
|
||||
return persistGroupLocked(gateway_id, group_id, entry);
|
||||
}
|
||||
auto& entry = ensureGroupStoreLocked(gateway_id)[group_id];
|
||||
if (entry.enabled == enabled) {
|
||||
return true;
|
||||
@@ -267,6 +468,17 @@ bool GatewayCache::setGroupDetail(uint8_t gateway_id, uint8_t group_id, uint8_t
|
||||
if (group_id >= 16) {
|
||||
return false;
|
||||
}
|
||||
if (!config_.cache_enabled) {
|
||||
GroupStore store;
|
||||
loadGroupStoreLocked(gateway_id, store);
|
||||
auto& entry = store[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;
|
||||
return persistGroupLocked(gateway_id, group_id, entry);
|
||||
}
|
||||
auto& entry = ensureGroupStoreLocked(gateway_id)[group_id];
|
||||
if (entry.target_type == target_type && entry.target_value == target_value) {
|
||||
return true;
|
||||
@@ -282,6 +494,17 @@ bool GatewayCache::setGroupName(uint8_t gateway_id, uint8_t group_id, std::strin
|
||||
if (group_id >= 16) {
|
||||
return false;
|
||||
}
|
||||
if (!config_.cache_enabled) {
|
||||
GroupStore store;
|
||||
loadGroupStoreLocked(gateway_id, store);
|
||||
auto& entry = store[group_id];
|
||||
const auto normalized = NormalizeName(name);
|
||||
if (entry.name == normalized) {
|
||||
return true;
|
||||
}
|
||||
entry.name = normalized;
|
||||
return persistGroupLocked(gateway_id, group_id, entry);
|
||||
}
|
||||
auto& entry = ensureGroupStoreLocked(gateway_id)[group_id];
|
||||
const auto normalized = NormalizeName(name);
|
||||
if (entry.name == normalized) {
|
||||
@@ -297,6 +520,16 @@ bool GatewayCache::deleteGroup(uint8_t gateway_id, uint8_t group_id) {
|
||||
if (group_id >= 16) {
|
||||
return false;
|
||||
}
|
||||
if (!config_.cache_enabled) {
|
||||
GroupStore store;
|
||||
loadGroupStoreLocked(gateway_id, store);
|
||||
auto& entry = store[group_id];
|
||||
if (IsDefaultGroup(entry) && entry.name.empty()) {
|
||||
return true;
|
||||
}
|
||||
entry = GroupEntry{};
|
||||
return persistGroupLocked(gateway_id, group_id, entry);
|
||||
}
|
||||
auto& entry = ensureGroupStoreLocked(gateway_id)[group_id];
|
||||
if (IsDefaultGroup(entry) && entry.name.empty()) {
|
||||
return true;
|
||||
@@ -308,7 +541,11 @@ bool GatewayCache::deleteGroup(uint8_t gateway_id, uint8_t group_id) {
|
||||
|
||||
std::pair<uint8_t, uint8_t> GatewayCache::groupMask(uint8_t gateway_id) {
|
||||
LockGuard guard(lock_);
|
||||
const auto& store = ensureGroupStoreLocked(gateway_id);
|
||||
GroupStore direct_store;
|
||||
if (!config_.cache_enabled) {
|
||||
loadGroupStoreLocked(gateway_id, direct_store);
|
||||
}
|
||||
const auto& store = config_.cache_enabled ? ensureGroupStoreLocked(gateway_id) : direct_store;
|
||||
uint16_t mask = 0;
|
||||
for (size_t index = 0; index < store.size(); ++index) {
|
||||
if (store[index].enabled) {
|
||||
@@ -320,24 +557,191 @@ std::pair<uint8_t, uint8_t> GatewayCache::groupMask(uint8_t gateway_id) {
|
||||
|
||||
GatewayCacheChannelFlags GatewayCache::channelFlags(uint8_t gateway_id) {
|
||||
LockGuard guard(lock_);
|
||||
if (!shouldTrackUpdateFlagsLocked()) {
|
||||
return {};
|
||||
}
|
||||
return channel_flags_[gateway_id];
|
||||
}
|
||||
|
||||
GatewayCacheChannelFlags GatewayCache::pendingChannelFlags(uint8_t gateway_id) {
|
||||
LockGuard guard(lock_);
|
||||
return shouldTrackUpdateFlagsLocked() ? channel_flags_[gateway_id] : GatewayCacheChannelFlags{};
|
||||
}
|
||||
|
||||
GatewayCacheDaliAddressState GatewayCache::daliAddressState(uint8_t gateway_id,
|
||||
uint8_t short_address) {
|
||||
LockGuard guard(lock_);
|
||||
if (short_address >= 64) {
|
||||
return {};
|
||||
}
|
||||
return ensureDaliAddressStateLocked(gateway_id, short_address);
|
||||
}
|
||||
|
||||
bool GatewayCache::setDaliGroupMask(uint8_t gateway_id, uint8_t short_address,
|
||||
std::optional<uint16_t> group_mask) {
|
||||
LockGuard guard(lock_);
|
||||
if (short_address >= 64) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto& state = ensureDaliAddressStateLocked(gateway_id, short_address);
|
||||
state.group_mask_known = group_mask.has_value();
|
||||
state.group_mask = group_mask.value_or(0);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GatewayCache::setDaliSceneLevel(uint8_t gateway_id, uint8_t short_address, uint8_t scene_id,
|
||||
std::optional<uint8_t> level) {
|
||||
LockGuard guard(lock_);
|
||||
if (short_address >= 64 || scene_id >= 16) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto& state = ensureDaliAddressStateLocked(gateway_id, short_address);
|
||||
state.scene_levels[scene_id] = level;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GatewayCache::setDaliSettings(uint8_t gateway_id, uint8_t short_address,
|
||||
std::optional<GatewayCacheDaliSettingsSnapshot> settings) {
|
||||
LockGuard guard(lock_);
|
||||
if (short_address >= 64) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto& state = ensureDaliAddressStateLocked(gateway_id, short_address);
|
||||
state.settings = settings.value_or(GatewayCacheDaliSettingsSnapshot{});
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GatewayCache::clearChannelFlagsIfMatched(uint8_t gateway_id,
|
||||
const GatewayCacheChannelFlags& flags) {
|
||||
LockGuard guard(lock_);
|
||||
if (!shouldTrackUpdateFlagsLocked()) {
|
||||
return true;
|
||||
}
|
||||
auto& current = channel_flags_[gateway_id];
|
||||
if (!SameFlags(current, flags)) {
|
||||
return false;
|
||||
}
|
||||
current = {};
|
||||
return true;
|
||||
}
|
||||
|
||||
void GatewayCache::markGroupUpdateNeeded(uint8_t gateway_id, bool needed) {
|
||||
LockGuard guard(lock_);
|
||||
if (!shouldTrackUpdateFlagsLocked()) {
|
||||
return;
|
||||
}
|
||||
channel_flags_[gateway_id].need_update_group = needed;
|
||||
}
|
||||
|
||||
void GatewayCache::markSceneUpdateNeeded(uint8_t gateway_id, bool needed) {
|
||||
LockGuard guard(lock_);
|
||||
if (!shouldTrackUpdateFlagsLocked()) {
|
||||
return;
|
||||
}
|
||||
channel_flags_[gateway_id].need_update_scene = needed;
|
||||
}
|
||||
|
||||
void GatewayCache::markSettingsUpdateNeeded(uint8_t gateway_id, bool needed) {
|
||||
LockGuard guard(lock_);
|
||||
if (!shouldTrackUpdateFlagsLocked()) {
|
||||
return;
|
||||
}
|
||||
channel_flags_[gateway_id].need_update_settings = needed;
|
||||
}
|
||||
|
||||
bool GatewayCache::cacheEnabled() const {
|
||||
return config_.cache_enabled;
|
||||
}
|
||||
|
||||
bool GatewayCache::reconciliationEnabled() const {
|
||||
return config_.cache_enabled && config_.reconciliation_enabled;
|
||||
}
|
||||
|
||||
bool GatewayCache::fullStateMirrorEnabled() const {
|
||||
return reconciliationEnabled() && config_.full_state_mirror_enabled;
|
||||
}
|
||||
|
||||
bool GatewayCache::observeDaliCommand(uint8_t gateway_id, uint8_t raw_addr, uint8_t command,
|
||||
GatewayCacheRawFrameOrigin origin) {
|
||||
LockGuard guard(lock_);
|
||||
if (!shouldTrackUpdateFlagsLocked()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto& dtr_state = dtr_states_[gateway_id];
|
||||
if (raw_addr == kDaliCmdSetDtr0) {
|
||||
dtr_state.dtr0 = command;
|
||||
return false;
|
||||
}
|
||||
if (raw_addr == kDaliCmdSetDtr1) {
|
||||
dtr_state.dtr1 = command;
|
||||
return false;
|
||||
}
|
||||
if (raw_addr == kDaliCmdSetDtr2) {
|
||||
dtr_state.dtr2 = command;
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto detected = ClassifyDaliMutation(raw_addr, command);
|
||||
if (!AnyFlagSet(detected)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ShouldMirrorObservedMutation(origin, priority_mode_)) {
|
||||
if (command == kDaliCmdReset) {
|
||||
if (const auto short_address = DecodeShortAddress(raw_addr); short_address.has_value()) {
|
||||
ClearDaliState(ensureDaliAddressStateLocked(gateway_id, *short_address));
|
||||
} else if (auto states = dali_states_.find(gateway_id); states != dali_states_.end()) {
|
||||
for (auto& state : states->second) {
|
||||
ClearDaliState(state);
|
||||
}
|
||||
}
|
||||
} else if (const auto short_address = DecodeShortAddress(raw_addr); short_address.has_value()) {
|
||||
auto& state = ensureDaliAddressStateLocked(gateway_id, *short_address);
|
||||
|
||||
if (command >= kDaliCmdAddToGroupMin && command <= kDaliCmdRemoveFromGroupMax &&
|
||||
state.group_mask_known) {
|
||||
const uint16_t bit = static_cast<uint16_t>(1U << (command & 0x0F));
|
||||
if (command < (kDaliCmdAddToGroupMin + 16)) {
|
||||
state.group_mask |= bit;
|
||||
} else {
|
||||
state.group_mask &= static_cast<uint16_t>(~bit);
|
||||
}
|
||||
} else if (command >= kDaliCmdSetSceneMin && command < (kDaliCmdSetSceneMin + 16) &&
|
||||
dtr_state.dtr0.has_value()) {
|
||||
state.scene_levels[command - kDaliCmdSetSceneMin] = *dtr_state.dtr0;
|
||||
} else if (command >= (kDaliCmdSetSceneMin + 16) && command <= kDaliCmdRemoveSceneMax) {
|
||||
state.scene_levels[command - (kDaliCmdSetSceneMin + 16)] = 255U;
|
||||
} else if (command >= kDaliCmdStoreDtrAsMaxLevel && command <= kDaliCmdStoreDtrAsFadeRate &&
|
||||
dtr_state.dtr0.has_value()) {
|
||||
ApplyObservedSettingsValue(state.settings, command, *dtr_state.dtr0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (origin != GatewayCacheRawFrameOrigin::kOutsideBus) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto& current = channel_flags_[gateway_id];
|
||||
const bool changed = (!current.need_update_group && detected.need_update_group) ||
|
||||
(!current.need_update_scene && detected.need_update_scene) ||
|
||||
(!current.need_update_settings && detected.need_update_settings);
|
||||
current.need_update_group = current.need_update_group || detected.need_update_group;
|
||||
current.need_update_scene = current.need_update_scene || detected.need_update_scene;
|
||||
current.need_update_settings = current.need_update_settings || detected.need_update_settings;
|
||||
|
||||
if (changed) {
|
||||
ESP_LOGI(kTag, "outside DALI mutation gateway=%u addr=0x%02x cmd=0x%02x flags g=%d s=%d cfg=%d",
|
||||
gateway_id, raw_addr, command, current.need_update_group, current.need_update_scene,
|
||||
current.need_update_settings);
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
GatewayCachePriorityMode GatewayCache::priorityMode() {
|
||||
LockGuard guard(lock_);
|
||||
return priority_mode_;
|
||||
@@ -363,6 +767,9 @@ void GatewayCache::taskLoop() {
|
||||
|
||||
bool GatewayCache::flushDirty() {
|
||||
LockGuard guard(lock_);
|
||||
if (!config_.cache_enabled) {
|
||||
return true;
|
||||
}
|
||||
if (!dirty_) {
|
||||
return true;
|
||||
}
|
||||
@@ -441,6 +848,79 @@ void GatewayCache::closeStorageLocked() {
|
||||
}
|
||||
}
|
||||
|
||||
bool GatewayCache::persistSceneLocked(uint8_t gateway_id, uint8_t scene_id,
|
||||
const SceneEntry& scene) {
|
||||
if (!openStorageLocked()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!IsDefaultScene(scene)) {
|
||||
if (!writeStringLocked(ShortKey("sc", gateway_id, scene_id), BuildScenePayload(scene))) {
|
||||
return false;
|
||||
}
|
||||
} else if (!eraseKeyLocked(ShortKey("sc", gateway_id, scene_id))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!scene.name.empty()) {
|
||||
if (!writeStringLocked(ShortKey("sn", gateway_id, scene_id), scene.name)) {
|
||||
return false;
|
||||
}
|
||||
} else if (!eraseKeyLocked(ShortKey("sn", gateway_id, scene_id))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return commitStorageLocked();
|
||||
}
|
||||
|
||||
bool GatewayCache::persistGroupLocked(uint8_t gateway_id, uint8_t group_id,
|
||||
const GroupEntry& group) {
|
||||
if (!openStorageLocked()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!IsDefaultGroup(group)) {
|
||||
if (!writeStringLocked(ShortKey("gr", gateway_id, group_id), BuildGroupPayload(group))) {
|
||||
return false;
|
||||
}
|
||||
} else if (!eraseKeyLocked(ShortKey("gr", gateway_id, group_id))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!group.name.empty()) {
|
||||
if (!writeStringLocked(ShortKey("gn", gateway_id, group_id), group.name)) {
|
||||
return false;
|
||||
}
|
||||
} else if (!eraseKeyLocked(ShortKey("gn", gateway_id, group_id))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return commitStorageLocked();
|
||||
}
|
||||
|
||||
bool GatewayCache::commitStorageLocked() {
|
||||
if (storage_ == 0) {
|
||||
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;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GatewayCache::shouldTrackUpdateFlagsLocked() const {
|
||||
return config_.cache_enabled && config_.reconciliation_enabled;
|
||||
}
|
||||
|
||||
GatewayCacheDaliAddressState& GatewayCache::ensureDaliAddressStateLocked(uint8_t gateway_id,
|
||||
uint8_t short_address) {
|
||||
auto [it, inserted] = dali_states_.try_emplace(gateway_id);
|
||||
(void)inserted;
|
||||
return it->second[short_address];
|
||||
}
|
||||
|
||||
GatewayCache::SceneStore& GatewayCache::ensureSceneStoreLocked(uint8_t gateway_id) {
|
||||
auto [it, inserted] = scenes_.try_emplace(gateway_id);
|
||||
if (inserted) {
|
||||
|
||||
Reference in New Issue
Block a user