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