feat(gateway_cache): enhance DALI state management and caching

- Increased flush interval to 10 seconds and added a refresh interval of 120 seconds in GatewayCacheConfig.
- Introduced a new boolean `stale` in GatewayCacheDaliRuntimeStatus to track stale states.
- Added methods for setting actual DALI levels and persisting DALI address states.
- Implemented functions to build and apply DALI state payloads, including handling scene levels.
- Enhanced the GatewayCache class to manage DALI states more effectively, including loading and persisting states.
- Updated GatewayController to support cache refresh operations, including handling cache commands and reporting cache status.
- Added mechanisms for periodic cache refresh based on idle time and configured intervals.

Signed-off-by: Tony <tonylu@tony-cloud.com>
This commit is contained in:
Tony
2026-05-21 15:34:26 +08:00
parent 2b8ef31263
commit 0827befb06
10 changed files with 748 additions and 64 deletions
@@ -33,6 +33,9 @@ struct GatewayControllerConfig {
bool ip_router_supported{true};
bool internal_scene_supported{true};
bool internal_group_supported{true};
bool cache_supported{true};
uint32_t cache_refresh_interval_ms{120000};
uint32_t cache_refresh_idle_ms{100};
};
struct GatewayChannelSnapshot {
@@ -109,13 +112,20 @@ class GatewayController {
std::map<uint8_t, std::vector<uint8_t>> chunks;
};
struct CacheRefreshJob {
TickType_t next_due_tick{0};
uint8_t short_address{0};
};
static void TaskEntry(void* arg);
void taskLoop();
void dispatchCommand(const std::vector<uint8_t>& command);
void scheduleReconciliation(uint8_t gateway_id);
bool hasPendingReconciliation() const;
bool cacheRefreshEnabled() const;
bool runMaintenanceStep();
bool runReconciliationStep(uint8_t gateway_id, ReconciliationJob& job);
bool runCacheRefreshStep();
void reconcileGroupStep(uint8_t gateway_id, uint8_t short_address);
void reconcileSceneStep(uint8_t gateway_id, uint8_t short_address, uint8_t scene_id);
void reconcileSettingsStep(uint8_t gateway_id, uint8_t short_address);
@@ -165,6 +175,7 @@ class GatewayController {
void handleAllocationCommand(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 handleGatewayCacheCommand(uint8_t gateway_id, const std::vector<uint8_t>& command);
void handleBridgeTransportCommand(uint8_t gateway_id, const std::vector<uint8_t>& command);
void publishBridgeTransportResponse(uint8_t gateway_id, uint8_t version, uint8_t sequence,
std::string_view response);
@@ -182,6 +193,7 @@ class GatewayController {
std::vector<GatewayNameSink> gateway_name_sinks_;
std::map<uint16_t, BridgeTransportRequestState> bridge_transport_requests_;
std::map<uint8_t, ReconciliationJob> reconciliation_jobs_;
std::map<uint8_t, CacheRefreshJob> cache_refresh_jobs_;
std::atomic<int> maintenance_activity_gateway_{-1};
bool setup_mode_{false};
bool wireless_setup_mode_{false};
@@ -22,10 +22,32 @@ constexpr uint8_t kDaliSceneCount = 16;
constexpr uint8_t kDaliCmdOff = 0x00;
constexpr uint8_t kDaliCmdRecallMax = 0x05;
constexpr TickType_t kMaintenancePollTicks = pdMS_TO_TICKS(20);
constexpr TickType_t kIdleMaintenancePollTicks = pdMS_TO_TICKS(1000);
constexpr uint8_t kBridgeTransportRequestOpcode = 0xB0;
constexpr uint8_t kBridgeTransportResponseOpcode = 0xB1;
constexpr uint8_t kBridgeTransportVersion = 1;
constexpr size_t kBridgeTransportMaxChunkBytes = 120;
constexpr uint8_t kGatewayFeatureCache = 0x40;
constexpr uint8_t kGatewayCacheOpcode = 0x39;
constexpr uint8_t kGatewayCacheProtocolVersion = 1;
constexpr uint8_t kGatewayCacheOpSummary = 0x00;
constexpr uint8_t kGatewayCacheOpShortAddress = 0x01;
constexpr uint8_t kGatewayCacheOpGroup = 0x02;
constexpr uint8_t kGatewayCacheOpBroadcast = 0x03;
constexpr uint8_t kGatewayCacheStatusOk = 0x00;
constexpr uint8_t kGatewayCacheStatusDisabled = 0x01;
constexpr uint8_t kGatewayCacheStatusInvalidArgument = 0x02;
constexpr uint16_t kCacheFlagActualKnown = 1U << 0;
constexpr uint16_t kCacheFlagSceneKnown = 1U << 1;
constexpr uint16_t kCacheFlagUseMinLevel = 1U << 2;
constexpr uint16_t kCacheFlagStatusStale = 1U << 3;
constexpr uint16_t kCacheFlagGroupMaskKnown = 1U << 4;
constexpr uint16_t kCacheFlagPowerOnKnown = 1U << 5;
constexpr uint16_t kCacheFlagSystemFailureKnown = 1U << 6;
constexpr uint16_t kCacheFlagMinKnown = 1U << 7;
constexpr uint16_t kCacheFlagMaxKnown = 1U << 8;
constexpr uint16_t kCacheFlagFadeTimeKnown = 1U << 9;
constexpr uint16_t kCacheFlagFadeRateKnown = 1U << 10;
constexpr const char* kBridgeTransportInvalidFrameResponse =
"{\"statusCode\":400,\"error\":\"invalid bridge transport frame\","
"\"message\":\"invalid bridge transport frame\"}";
@@ -84,6 +106,75 @@ void AppendStringBytes(std::vector<uint8_t>& out, std::string_view value) {
}
}
void AppendLe16(std::vector<uint8_t>& out, uint16_t value) {
out.push_back(static_cast<uint8_t>(value & 0xFF));
out.push_back(static_cast<uint8_t>((value >> 8) & 0xFF));
}
void AppendLe32(std::vector<uint8_t>& out, uint32_t value) {
out.push_back(static_cast<uint8_t>(value & 0xFF));
out.push_back(static_cast<uint8_t>((value >> 8) & 0xFF));
out.push_back(static_cast<uint8_t>((value >> 16) & 0xFF));
out.push_back(static_cast<uint8_t>((value >> 24) & 0xFF));
}
uint8_t CacheByte(std::optional<uint8_t> value) {
return value.value_or(0xFF);
}
uint16_t CacheRuntimeFlags(const GatewayCacheDaliRuntimeStatus& status) {
uint16_t flags = 0;
if (status.actual_level.has_value()) {
flags |= kCacheFlagActualKnown;
}
if (status.scene_id.has_value()) {
flags |= kCacheFlagSceneKnown;
}
if (status.use_min_level) {
flags |= kCacheFlagUseMinLevel;
}
if (status.stale) {
flags |= kCacheFlagStatusStale;
}
return flags;
}
uint16_t CacheAddressFlags(const GatewayCacheDaliAddressState& state) {
uint16_t flags = CacheRuntimeFlags(state.status);
if (state.group_mask_known) {
flags |= kCacheFlagGroupMaskKnown;
}
if (state.settings.power_on_level.has_value()) {
flags |= kCacheFlagPowerOnKnown;
}
if (state.settings.system_failure_level.has_value()) {
flags |= kCacheFlagSystemFailureKnown;
}
if (state.settings.min_level.has_value()) {
flags |= kCacheFlagMinKnown;
}
if (state.settings.max_level.has_value()) {
flags |= kCacheFlagMaxKnown;
}
if (state.settings.fade_time.has_value()) {
flags |= kCacheFlagFadeTimeKnown;
}
if (state.settings.fade_rate.has_value()) {
flags |= kCacheFlagFadeRateKnown;
}
return flags;
}
uint16_t CacheSceneKnownMask(const GatewayCacheDaliAddressState& state) {
uint16_t mask = 0;
for (size_t index = 0; index < state.scene_levels.size(); ++index) {
if (state.scene_levels[index].has_value()) {
mask |= static_cast<uint16_t>(1U << index);
}
}
return mask;
}
uint16_t BridgeTransportRequestKey(uint8_t gateway_id, uint8_t sequence) {
return static_cast<uint16_t>((static_cast<uint16_t>(gateway_id) << 8) | sequence);
}
@@ -275,7 +366,11 @@ void GatewayController::taskLoop() {
worked = runMaintenanceStep();
}
if (!worked) {
ulTaskNotifyTake(pdTRUE, hasPendingReconciliation() ? kMaintenancePollTicks : portMAX_DELAY);
const TickType_t wait_ticks = hasPendingReconciliation()
? kMaintenancePollTicks
: cacheRefreshEnabled() ? kIdleMaintenancePollTicks
: portMAX_DELAY;
ulTaskNotifyTake(pdTRUE, wait_ticks);
}
}
}
@@ -308,42 +403,50 @@ bool GatewayController::hasPendingReconciliation() const {
return !reconciliation_jobs_.empty();
}
bool GatewayController::cacheRefreshEnabled() const {
return config_.cache_supported && cache_.cacheEnabled() &&
config_.cache_refresh_interval_ms > 0;
}
bool GatewayController::runMaintenanceStep() {
if (!cache_.reconciliationEnabled()) {
return false;
}
uint8_t gateway_id = 0;
ReconciliationJob job;
{
LockGuard guard(maintenance_lock_);
if (reconciliation_jobs_.empty()) {
return false;
if (cache_.reconciliationEnabled()) {
bool has_job = false;
uint8_t gateway_id = 0;
ReconciliationJob job;
{
LockGuard guard(maintenance_lock_);
if (!reconciliation_jobs_.empty()) {
const auto it = reconciliation_jobs_.begin();
has_job = true;
gateway_id = it->first;
job = it->second;
}
}
const auto it = reconciliation_jobs_.begin();
gateway_id = it->first;
job = it->second;
}
if (runtime_.shouldYieldMaintenance(gateway_id)) {
return false;
}
if (has_job) {
if (runtime_.shouldYieldMaintenance(gateway_id)) {
return false;
}
const bool keep_job = runReconciliationStep(gateway_id, job);
const bool keep_job = runReconciliationStep(gateway_id, job);
{
LockGuard guard(maintenance_lock_);
auto it = reconciliation_jobs_.find(gateway_id);
if (it == reconciliation_jobs_.end()) {
{
LockGuard guard(maintenance_lock_);
auto it = reconciliation_jobs_.find(gateway_id);
if (it == reconciliation_jobs_.end()) {
return true;
}
if (keep_job) {
it->second = job;
} else {
reconciliation_jobs_.erase(it);
}
}
return true;
}
if (keep_job) {
it->second = job;
} else {
reconciliation_jobs_.erase(it);
}
}
return true;
return runCacheRefreshStep();
}
bool GatewayController::runReconciliationStep(uint8_t gateway_id, ReconciliationJob& job) {
@@ -418,6 +521,49 @@ bool GatewayController::runReconciliationStep(uint8_t gateway_id, Reconciliation
}
}
bool GatewayController::runCacheRefreshStep() {
if (!cacheRefreshEnabled()) {
return false;
}
const TickType_t interval_ticks =
std::max<TickType_t>(1, pdMS_TO_TICKS(config_.cache_refresh_interval_ms));
const auto channels = dali_domain_.channelInfo();
const TickType_t now = xTaskGetTickCount();
for (const auto& channel : channels) {
auto& job = cache_refresh_jobs_[channel.gateway_id];
if (job.next_due_tick == 0) {
job.next_due_tick = now + interval_ticks;
continue;
}
if (now < job.next_due_tick) {
continue;
}
if (runtime_.shouldYieldMaintenance(channel.gateway_id) ||
dali_domain_.isAllocAddr(channel.gateway_id) ||
!dali_domain_.isBusIdle(channel.gateway_id, config_.cache_refresh_idle_ms)) {
continue;
}
maintenance_activity_gateway_.store(channel.gateway_id);
const auto actual_level = dali_domain_.queryActualLevel(channel.gateway_id, job.short_address);
maintenance_activity_gateway_.store(-1);
cache_.setDaliActualLevel(channel.gateway_id, job.short_address, actual_level);
++job.short_address;
if (job.short_address >= kDaliShortAddressCount) {
job.short_address = 0;
job.next_due_tick = xTaskGetTickCount() + interval_ticks;
} else {
job.next_due_tick = xTaskGetTickCount();
}
return true;
}
return false;
}
void GatewayController::reconcileGroupStep(uint8_t gateway_id, uint8_t short_address) {
const auto policy = cache_.priorityMode();
const auto state = cache_.daliAddressState(gateway_id, short_address);
@@ -574,6 +720,9 @@ void GatewayController::dispatchCommand(const std::vector<uint8_t>& command) {
if (config_.internal_group_supported) {
feature |= 0x20;
}
if (config_.cache_supported) {
feature |= kGatewayFeatureCache;
}
publishPayload(gateway_id, {0x03, gateway_id, feature});
break;
}
@@ -672,6 +821,9 @@ void GatewayController::dispatchCommand(const std::vector<uint8_t>& command) {
}
}
break;
case kGatewayCacheOpcode:
handleGatewayCacheCommand(gateway_id, command);
break;
case 0xA0:
handleInternalSceneCommand(gateway_id, command);
break;
@@ -1360,4 +1512,90 @@ void GatewayController::handleInternalGroupCommand(uint8_t gateway_id,
}
}
void GatewayController::handleGatewayCacheCommand(uint8_t gateway_id,
const std::vector<uint8_t>& command) {
const uint8_t op = command.size() > 4 ? command[4] : kGatewayCacheOpSummary;
const uint8_t arg = command.size() > 5 ? command[5] : 0;
const bool enabled = config_.cache_supported && cache_.cacheEnabled();
if (op == kGatewayCacheOpSummary) {
const uint8_t flags = static_cast<uint8_t>((config_.cache_supported ? 0x01 : 0x00) |
(cache_.cacheEnabled() ? 0x02 : 0x00) |
(cacheRefreshEnabled() ? 0x04 : 0x00));
std::vector<uint8_t> payload{kGatewayCacheOpcode,
gateway_id,
op,
kGatewayCacheStatusOk,
kGatewayCacheProtocolVersion,
flags};
AppendLe16(payload, static_cast<uint16_t>(std::min<uint32_t>(
config_.cache_refresh_interval_ms / 1000U, 0xffffU)));
publishPayload(gateway_id, payload);
return;
}
if (!enabled) {
publishPayload(gateway_id, {kGatewayCacheOpcode, gateway_id, op,
kGatewayCacheStatusDisabled, arg});
return;
}
if (op == kGatewayCacheOpShortAddress) {
if (arg >= kDaliShortAddressCount) {
publishPayload(gateway_id, {kGatewayCacheOpcode, gateway_id, op,
kGatewayCacheStatusInvalidArgument, arg});
return;
}
const auto state = cache_.daliAddressState(gateway_id, arg);
std::vector<uint8_t> payload{kGatewayCacheOpcode, gateway_id, op, kGatewayCacheStatusOk, arg};
AppendLe16(payload, CacheAddressFlags(state));
payload.push_back(CacheByte(state.status.actual_level));
payload.push_back(CacheByte(state.status.scene_id));
AppendLe16(payload, state.group_mask_known ? state.group_mask : 0);
payload.push_back(CacheByte(state.settings.power_on_level));
payload.push_back(CacheByte(state.settings.system_failure_level));
payload.push_back(CacheByte(state.settings.min_level));
payload.push_back(CacheByte(state.settings.max_level));
payload.push_back(CacheByte(state.settings.fade_time));
payload.push_back(CacheByte(state.settings.fade_rate));
AppendLe32(payload, state.status.revision);
AppendLe16(payload, CacheSceneKnownMask(state));
for (const auto& level : state.scene_levels) {
payload.push_back(CacheByte(level));
}
publishPayload(gateway_id, payload);
return;
}
if (op == kGatewayCacheOpGroup) {
if (arg >= kDaliSceneCount) {
publishPayload(gateway_id, {kGatewayCacheOpcode, gateway_id, op,
kGatewayCacheStatusInvalidArgument, arg});
return;
}
const auto status = cache_.daliGroupStatus(gateway_id, arg);
std::vector<uint8_t> payload{kGatewayCacheOpcode, gateway_id, op, kGatewayCacheStatusOk, arg};
AppendLe16(payload, CacheRuntimeFlags(status));
payload.push_back(CacheByte(status.actual_level));
payload.push_back(CacheByte(status.scene_id));
AppendLe32(payload, status.revision);
publishPayload(gateway_id, payload);
return;
}
if (op == kGatewayCacheOpBroadcast) {
const auto status = cache_.daliBroadcastStatus(gateway_id);
std::vector<uint8_t> payload{kGatewayCacheOpcode, gateway_id, op, kGatewayCacheStatusOk, 0};
AppendLe16(payload, CacheRuntimeFlags(status));
payload.push_back(CacheByte(status.actual_level));
payload.push_back(CacheByte(status.scene_id));
AppendLe32(payload, status.revision);
publishPayload(gateway_id, payload);
return;
}
publishPayload(gateway_id, {kGatewayCacheOpcode, gateway_id, op,
kGatewayCacheStatusInvalidArgument, arg});
}
} // namespace gateway