diff --git a/components/gateway_cache/include/gateway_cache.hpp b/components/gateway_cache/include/gateway_cache.hpp index 7e86d3f..1ec6800 100644 --- a/components/gateway_cache/include/gateway_cache.hpp +++ b/components/gateway_cache/include/gateway_cache.hpp @@ -35,6 +35,17 @@ enum class GatewayCacheRawFrameOrigin : uint8_t { kOutsideBus = 1, }; +enum class GatewayCacheDaliTargetKind : uint8_t { + kShortAddress = 0, + kGroup = 1, + kBroadcast = 2, +}; + +struct GatewayCacheDaliTarget { + GatewayCacheDaliTargetKind kind{GatewayCacheDaliTargetKind::kShortAddress}; + uint8_t value{0}; +}; + struct GatewayCacheChannelFlags { bool need_update_group{false}; bool need_update_scene{false}; @@ -56,11 +67,23 @@ struct GatewayCacheDaliSettingsSnapshot { } }; +struct GatewayCacheDaliRuntimeStatus { + std::optional actual_level; + std::optional scene_id; + bool use_min_level{false}; + uint32_t revision{0}; + + bool anyKnown() const { + return actual_level.has_value() || scene_id.has_value() || use_min_level; + } +}; + struct GatewayCacheDaliAddressState { bool group_mask_known{false}; uint16_t group_mask{0}; std::array, 16> scene_levels{}; GatewayCacheDaliSettingsSnapshot settings; + GatewayCacheDaliRuntimeStatus status; }; class GatewayCache { @@ -113,6 +136,8 @@ class GatewayCache { GatewayCacheChannelFlags channelFlags(uint8_t gateway_id); GatewayCacheChannelFlags pendingChannelFlags(uint8_t gateway_id); GatewayCacheDaliAddressState daliAddressState(uint8_t gateway_id, uint8_t short_address); + GatewayCacheDaliRuntimeStatus daliGroupStatus(uint8_t gateway_id, uint8_t group_id); + GatewayCacheDaliRuntimeStatus daliBroadcastStatus(uint8_t gateway_id); bool setDaliGroupMask(uint8_t gateway_id, uint8_t short_address, std::optional group_mask); bool setDaliSceneLevel(uint8_t gateway_id, uint8_t short_address, uint8_t scene_id, @@ -127,6 +152,7 @@ class GatewayCache { bool cacheEnabled() const; bool reconciliationEnabled() const; bool fullStateMirrorEnabled() const; + bool mirrorDaliCommand(uint8_t gateway_id, uint8_t raw_addr, uint8_t command); bool observeDaliCommand(uint8_t gateway_id, uint8_t raw_addr, uint8_t command, GatewayCacheRawFrameOrigin origin); @@ -150,8 +176,31 @@ class GatewayCache { bool persistGroupLocked(uint8_t gateway_id, uint8_t group_id, const GroupEntry& group); bool commitStorageLocked(); bool shouldTrackUpdateFlagsLocked() const; + uint32_t nextDaliRuntimeRevisionLocked(); + bool mirrorDaliCommandLocked(uint8_t gateway_id, uint8_t raw_addr, uint8_t command); + void clearDaliTargetStateLocked(uint8_t gateway_id, const GatewayCacheDaliTarget& target, + uint32_t revision); + void applyDaliTargetRuntimeStatusLocked(uint8_t gateway_id, + const GatewayCacheDaliTarget& target, + const GatewayCacheDaliRuntimeStatus& status); + void applyDaliRuntimeStatusToAddressLocked(GatewayCacheDaliAddressState& state, + const GatewayCacheDaliRuntimeStatus& status); + void applyDaliTargetGroupMutationLocked(uint8_t gateway_id, + const GatewayCacheDaliTarget& target, uint8_t group_id, + bool add_to_group); + void applyDaliTargetSceneLevelLocked(uint8_t gateway_id, + const GatewayCacheDaliTarget& target, uint8_t scene_id, + std::optional level); + void applyDaliTargetSettingsLocked(uint8_t gateway_id, + const GatewayCacheDaliTarget& target, uint8_t command, + uint8_t value); + void refreshDaliAddressAggregateStatusLocked(uint8_t gateway_id, + GatewayCacheDaliAddressState& state); GatewayCacheDaliAddressState& ensureDaliAddressStateLocked(uint8_t gateway_id, uint8_t short_address); + GatewayCacheDaliRuntimeStatus& ensureDaliGroupStatusLocked(uint8_t gateway_id, + uint8_t group_id); + GatewayCacheDaliRuntimeStatus& ensureDaliBroadcastStatusLocked(uint8_t gateway_id); SceneStore& ensureSceneStoreLocked(uint8_t gateway_id); GroupStore& ensureGroupStoreLocked(uint8_t gateway_id); void loadSceneStoreLocked(uint8_t gateway_id, SceneStore& scenes); @@ -168,8 +217,11 @@ class GatewayCache { std::map scenes_; std::map groups_; std::map> dali_states_; + std::map> dali_group_status_; + std::map dali_broadcast_status_; std::map dtr_states_; std::map channel_flags_; + uint32_t dali_runtime_revision_{0}; bool dirty_{false}; }; diff --git a/components/gateway_cache/src/gateway_cache.cpp b/components/gateway_cache/src/gateway_cache.cpp index ef88c0f..c9e41d7 100644 --- a/components/gateway_cache/src/gateway_cache.cpp +++ b/components/gateway_cache/src/gateway_cache.cpp @@ -14,6 +14,15 @@ namespace { constexpr const char* kTag = "gateway_cache"; constexpr size_t kMaxNameBytes = 32; +constexpr uint8_t kDaliRawBroadcastArc = 0xFE; +constexpr uint8_t kDaliRawBroadcastCommand = 0xFF; +constexpr uint8_t kDaliGroupRawMin = 0x80; +constexpr uint8_t kDaliGroupRawMax = 0x9F; +constexpr uint8_t kDaliCmdOff = 0x00; +constexpr uint8_t kDaliCmdRecallMax = 0x05; +constexpr uint8_t kDaliCmdRecallMin = 0x06; +constexpr uint8_t kDaliCmdGoToSceneMin = 0x10; +constexpr uint8_t kDaliCmdGoToSceneMax = 0x1F; constexpr uint8_t kDaliCmdReset = 0x20; constexpr uint8_t kDaliCmdStoreDtrAsMaxLevel = 0x2A; constexpr uint8_t kDaliCmdStoreDtrAsMinLevel = 0x2B; @@ -104,9 +113,17 @@ bool AnyFlagSet(const GatewayCacheChannelFlags& flags) { return flags.need_update_group || flags.need_update_scene || flags.need_update_settings; } -std::optional DecodeShortAddress(uint8_t raw_addr) { - if (raw_addr <= 0x7F && (raw_addr & 0x01) != 0) { - return static_cast(raw_addr >> 1); +std::optional DecodeDaliTarget(uint8_t raw_addr) { + if (raw_addr <= 0x7F) { + return GatewayCacheDaliTarget{GatewayCacheDaliTargetKind::kShortAddress, + static_cast(raw_addr >> 1)}; + } + if (raw_addr >= kDaliGroupRawMin && raw_addr <= kDaliGroupRawMax) { + return GatewayCacheDaliTarget{GatewayCacheDaliTargetKind::kGroup, + static_cast((raw_addr - kDaliGroupRawMin) >> 1)}; + } + if (raw_addr == kDaliRawBroadcastArc || raw_addr == kDaliRawBroadcastCommand) { + return GatewayCacheDaliTarget{GatewayCacheDaliTargetKind::kBroadcast, 0}; } return std::nullopt; } @@ -122,6 +139,7 @@ void ClearDaliState(GatewayCacheDaliAddressState& state) { state.group_mask = 0; state.scene_levels.fill(std::nullopt); state.settings = {}; + state.status = {}; } void ApplyObservedSettingsValue(GatewayCacheDaliSettingsSnapshot& settings, uint8_t command, @@ -577,6 +595,20 @@ GatewayCacheDaliAddressState GatewayCache::daliAddressState(uint8_t gateway_id, return ensureDaliAddressStateLocked(gateway_id, short_address); } +GatewayCacheDaliRuntimeStatus GatewayCache::daliGroupStatus(uint8_t gateway_id, + uint8_t group_id) { + LockGuard guard(lock_); + if (group_id >= 16) { + return {}; + } + return ensureDaliGroupStatusLocked(gateway_id, group_id); +} + +GatewayCacheDaliRuntimeStatus GatewayCache::daliBroadcastStatus(uint8_t gateway_id) { + LockGuard guard(lock_); + return ensureDaliBroadcastStatusLocked(gateway_id); +} + bool GatewayCache::setDaliGroupMask(uint8_t gateway_id, uint8_t short_address, std::optional group_mask) { LockGuard guard(lock_); @@ -587,6 +619,7 @@ bool GatewayCache::setDaliGroupMask(uint8_t gateway_id, uint8_t short_address, auto& state = ensureDaliAddressStateLocked(gateway_id, short_address); state.group_mask_known = group_mask.has_value(); state.group_mask = group_mask.value_or(0); + refreshDaliAddressAggregateStatusLocked(gateway_id, state); return true; } @@ -664,62 +697,31 @@ bool GatewayCache::fullStateMirrorEnabled() const { return reconciliationEnabled() && config_.full_state_mirror_enabled; } +bool GatewayCache::mirrorDaliCommand(uint8_t gateway_id, uint8_t raw_addr, uint8_t command) { + LockGuard guard(lock_); + if (!config_.cache_enabled) { + return false; + } + return mirrorDaliCommandLocked(gateway_id, raw_addr, command); +} + 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)) { + if (!config_.cache_enabled) { 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); + mirrorDaliCommandLocked(gateway_id, raw_addr, command); + } - if (command >= kDaliCmdAddToGroupMin && command <= kDaliCmdRemoveFromGroupMax && - state.group_mask_known) { - const uint16_t bit = static_cast(1U << (command & 0x0F)); - if (command < (kDaliCmdAddToGroupMin + 16)) { - state.group_mask |= bit; - } else { - state.group_mask &= static_cast(~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 (!shouldTrackUpdateFlagsLocked()) { + return false; + } + const auto detected = ClassifyDaliMutation(raw_addr, command); + if (!AnyFlagSet(detected)) { + return false; } if (origin != GatewayCacheRawFrameOrigin::kOutsideBus) { @@ -752,6 +754,394 @@ void GatewayCache::setPriorityMode(GatewayCachePriorityMode mode) { priority_mode_ = mode; } +uint32_t GatewayCache::nextDaliRuntimeRevisionLocked() { + ++dali_runtime_revision_; + if (dali_runtime_revision_ == 0) { + ++dali_runtime_revision_; + } + return dali_runtime_revision_; +} + +bool GatewayCache::mirrorDaliCommandLocked(uint8_t gateway_id, uint8_t raw_addr, + uint8_t command) { + 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 target = DecodeDaliTarget(raw_addr); + if (!target.has_value()) { + return false; + } + + const bool arc_power_frame = (raw_addr & 0x01) == 0; + if (arc_power_frame) { + if (command > 254) { + return false; + } + GatewayCacheDaliRuntimeStatus status; + status.actual_level = command; + status.revision = nextDaliRuntimeRevisionLocked(); + applyDaliTargetRuntimeStatusLocked(gateway_id, *target, status); + return true; + } + + if (command == kDaliCmdReset) { + clearDaliTargetStateLocked(gateway_id, *target, nextDaliRuntimeRevisionLocked()); + return true; + } + + if (command == kDaliCmdOff || command == kDaliCmdRecallMax) { + GatewayCacheDaliRuntimeStatus status; + status.actual_level = command == kDaliCmdOff ? 0 : 254; + status.revision = nextDaliRuntimeRevisionLocked(); + applyDaliTargetRuntimeStatusLocked(gateway_id, *target, status); + return true; + } + + if (command == kDaliCmdRecallMin) { + GatewayCacheDaliRuntimeStatus status; + status.use_min_level = true; + status.revision = nextDaliRuntimeRevisionLocked(); + applyDaliTargetRuntimeStatusLocked(gateway_id, *target, status); + return true; + } + + if (command >= kDaliCmdGoToSceneMin && command <= kDaliCmdGoToSceneMax) { + GatewayCacheDaliRuntimeStatus status; + status.scene_id = static_cast(command - kDaliCmdGoToSceneMin); + status.revision = nextDaliRuntimeRevisionLocked(); + applyDaliTargetRuntimeStatusLocked(gateway_id, *target, status); + return true; + } + + if (command >= kDaliCmdAddToGroupMin && command <= kDaliCmdRemoveFromGroupMax) { + applyDaliTargetGroupMutationLocked(gateway_id, *target, + static_cast(command & 0x0F), + command < (kDaliCmdAddToGroupMin + 16)); + return true; + } + + if (command >= kDaliCmdSetSceneMin && command < (kDaliCmdSetSceneMin + 16) && + dtr_state.dtr0.has_value()) { + applyDaliTargetSceneLevelLocked(gateway_id, *target, + static_cast(command - kDaliCmdSetSceneMin), + *dtr_state.dtr0); + return true; + } + + if (command >= (kDaliCmdSetSceneMin + 16) && command <= kDaliCmdRemoveSceneMax) { + applyDaliTargetSceneLevelLocked( + gateway_id, *target, static_cast(command - (kDaliCmdSetSceneMin + 16)), + static_cast(255U)); + return true; + } + + if (command >= kDaliCmdStoreDtrAsMaxLevel && command <= kDaliCmdStoreDtrAsFadeRate && + dtr_state.dtr0.has_value()) { + applyDaliTargetSettingsLocked(gateway_id, *target, command, *dtr_state.dtr0); + return true; + } + + return false; +} + +void GatewayCache::clearDaliTargetStateLocked(uint8_t gateway_id, + const GatewayCacheDaliTarget& target, + uint32_t revision) { + auto clear_state = [revision](GatewayCacheDaliAddressState& state) { + ClearDaliState(state); + state.status.revision = revision; + }; + + switch (target.kind) { + case GatewayCacheDaliTargetKind::kShortAddress: + if (target.value < 64) { + clear_state(ensureDaliAddressStateLocked(gateway_id, target.value)); + } + break; + case GatewayCacheDaliTargetKind::kGroup: { + if (target.value >= 16) { + break; + } + auto& group_status = ensureDaliGroupStatusLocked(gateway_id, target.value); + group_status = {}; + group_status.revision = revision; + const uint16_t bit = static_cast(1U << target.value); + if (auto states = dali_states_.find(gateway_id); states != dali_states_.end()) { + for (auto& state : states->second) { + if (state.group_mask_known && (state.group_mask & bit) != 0) { + clear_state(state); + } + } + } + break; + } + case GatewayCacheDaliTargetKind::kBroadcast: { + auto& broadcast_status = ensureDaliBroadcastStatusLocked(gateway_id); + broadcast_status = {}; + broadcast_status.revision = revision; + auto& group_statuses = dali_group_status_[gateway_id]; + for (auto& group_status : group_statuses) { + group_status = {}; + group_status.revision = revision; + } + auto& states = dali_states_[gateway_id]; + for (auto& state : states) { + clear_state(state); + } + break; + } + default: + break; + } +} + +void GatewayCache::applyDaliTargetRuntimeStatusLocked( + uint8_t gateway_id, const GatewayCacheDaliTarget& target, + const GatewayCacheDaliRuntimeStatus& status) { + if (!status.anyKnown()) { + return; + } + + switch (target.kind) { + case GatewayCacheDaliTargetKind::kShortAddress: + if (target.value < 64) { + applyDaliRuntimeStatusToAddressLocked(ensureDaliAddressStateLocked(gateway_id, + target.value), + status); + } + break; + case GatewayCacheDaliTargetKind::kGroup: { + if (target.value >= 16) { + break; + } + ensureDaliGroupStatusLocked(gateway_id, target.value) = status; + const uint16_t bit = static_cast(1U << target.value); + if (auto states = dali_states_.find(gateway_id); states != dali_states_.end()) { + for (auto& state : states->second) { + if (state.group_mask_known && (state.group_mask & bit) != 0) { + applyDaliRuntimeStatusToAddressLocked(state, status); + } + } + } + break; + } + case GatewayCacheDaliTargetKind::kBroadcast: { + ensureDaliBroadcastStatusLocked(gateway_id) = status; + auto& states = dali_states_[gateway_id]; + for (auto& state : states) { + applyDaliRuntimeStatusToAddressLocked(state, status); + } + break; + } + default: + break; + } +} + +void GatewayCache::applyDaliRuntimeStatusToAddressLocked( + GatewayCacheDaliAddressState& state, const GatewayCacheDaliRuntimeStatus& status) { + if (!status.anyKnown() || status.revision <= state.status.revision) { + return; + } + + if (status.scene_id.has_value()) { + state.status.scene_id = status.scene_id; + state.status.use_min_level = false; + const uint8_t scene_id = *status.scene_id; + if (scene_id < state.scene_levels.size()) { + const auto scene_level = state.scene_levels[scene_id]; + if (scene_level.has_value() && *scene_level != 255U) { + state.status.actual_level = *scene_level; + } else if (status.actual_level.has_value()) { + state.status.actual_level = status.actual_level; + } + } + } else { + state.status.scene_id.reset(); + state.status.use_min_level = status.use_min_level; + if (status.use_min_level) { + state.status.actual_level = state.settings.min_level; + } else { + state.status.actual_level = status.actual_level; + } + } + state.status.revision = status.revision; +} + +void GatewayCache::applyDaliTargetGroupMutationLocked(uint8_t gateway_id, + const GatewayCacheDaliTarget& target, + uint8_t group_id, bool add_to_group) { + if (group_id >= 16) { + return; + } + const uint16_t bit = static_cast(1U << group_id); + auto apply = [&](GatewayCacheDaliAddressState& state) { + if (!state.group_mask_known) { + return; + } + if (add_to_group) { + state.group_mask |= bit; + } else { + state.group_mask &= static_cast(~bit); + } + refreshDaliAddressAggregateStatusLocked(gateway_id, state); + }; + + switch (target.kind) { + case GatewayCacheDaliTargetKind::kShortAddress: + if (target.value < 64) { + apply(ensureDaliAddressStateLocked(gateway_id, target.value)); + } + break; + case GatewayCacheDaliTargetKind::kGroup: { + if (target.value >= 16) { + break; + } + const uint16_t target_bit = static_cast(1U << target.value); + if (auto states = dali_states_.find(gateway_id); states != dali_states_.end()) { + for (auto& state : states->second) { + if (state.group_mask_known && (state.group_mask & target_bit) != 0) { + apply(state); + } + } + } + break; + } + case GatewayCacheDaliTargetKind::kBroadcast: { + auto& states = dali_states_[gateway_id]; + for (auto& state : states) { + apply(state); + } + break; + } + default: + break; + } +} + +void GatewayCache::applyDaliTargetSceneLevelLocked(uint8_t gateway_id, + const GatewayCacheDaliTarget& target, + uint8_t scene_id, + std::optional level) { + if (scene_id >= 16) { + return; + } + auto apply = [scene_id, level](GatewayCacheDaliAddressState& state) { + state.scene_levels[scene_id] = level; + if (state.status.scene_id.has_value() && *state.status.scene_id == scene_id && + level.has_value() && *level != 255U) { + state.status.actual_level = *level; + } + }; + + switch (target.kind) { + case GatewayCacheDaliTargetKind::kShortAddress: + if (target.value < 64) { + apply(ensureDaliAddressStateLocked(gateway_id, target.value)); + } + break; + case GatewayCacheDaliTargetKind::kGroup: { + if (target.value >= 16) { + break; + } + const uint16_t bit = static_cast(1U << target.value); + if (auto states = dali_states_.find(gateway_id); states != dali_states_.end()) { + for (auto& state : states->second) { + if (state.group_mask_known && (state.group_mask & bit) != 0) { + apply(state); + } + } + } + break; + } + case GatewayCacheDaliTargetKind::kBroadcast: { + auto& states = dali_states_[gateway_id]; + for (auto& state : states) { + apply(state); + } + break; + } + default: + break; + } +} + +void GatewayCache::applyDaliTargetSettingsLocked(uint8_t gateway_id, + const GatewayCacheDaliTarget& target, + uint8_t command, uint8_t value) { + auto apply = [command, value](GatewayCacheDaliAddressState& state) { + ApplyObservedSettingsValue(state.settings, command, value); + if (command == kDaliCmdStoreDtrAsMinLevel && state.status.use_min_level) { + state.status.actual_level = value; + } + }; + + switch (target.kind) { + case GatewayCacheDaliTargetKind::kShortAddress: + if (target.value < 64) { + apply(ensureDaliAddressStateLocked(gateway_id, target.value)); + } + break; + case GatewayCacheDaliTargetKind::kGroup: { + if (target.value >= 16) { + break; + } + const uint16_t bit = static_cast(1U << target.value); + if (auto states = dali_states_.find(gateway_id); states != dali_states_.end()) { + for (auto& state : states->second) { + if (state.group_mask_known && (state.group_mask & bit) != 0) { + apply(state); + } + } + } + break; + } + case GatewayCacheDaliTargetKind::kBroadcast: { + auto& states = dali_states_[gateway_id]; + for (auto& state : states) { + apply(state); + } + break; + } + default: + break; + } +} + +void GatewayCache::refreshDaliAddressAggregateStatusLocked(uint8_t gateway_id, + GatewayCacheDaliAddressState& state) { + if (!state.group_mask_known) { + return; + } + + if (const auto broadcast = dali_broadcast_status_.find(gateway_id); + broadcast != dali_broadcast_status_.end()) { + applyDaliRuntimeStatusToAddressLocked(state, broadcast->second); + } + + const auto groups = dali_group_status_.find(gateway_id); + if (groups == dali_group_status_.end()) { + return; + } + for (uint8_t group_id = 0; group_id < groups->second.size(); ++group_id) { + const uint16_t bit = static_cast(1U << group_id); + if ((state.group_mask & bit) != 0) { + applyDaliRuntimeStatusToAddressLocked(state, groups->second[group_id]); + } + } +} + void GatewayCache::TaskEntry(void* arg) { static_cast(arg)->taskLoop(); } @@ -921,6 +1311,17 @@ GatewayCacheDaliAddressState& GatewayCache::ensureDaliAddressStateLocked(uint8_t return it->second[short_address]; } +GatewayCacheDaliRuntimeStatus& GatewayCache::ensureDaliGroupStatusLocked(uint8_t gateway_id, + uint8_t group_id) { + auto [it, inserted] = dali_group_status_.try_emplace(gateway_id); + (void)inserted; + return it->second[group_id]; +} + +GatewayCacheDaliRuntimeStatus& GatewayCache::ensureDaliBroadcastStatusLocked(uint8_t gateway_id) { + return dali_broadcast_status_[gateway_id]; +} + GatewayCache::SceneStore& GatewayCache::ensureSceneStoreLocked(uint8_t gateway_id) { auto [it, inserted] = scenes_.try_emplace(gateway_id); if (inserted) { diff --git a/components/gateway_controller/include/gateway_controller.hpp b/components/gateway_controller/include/gateway_controller.hpp index 65a735a..f55807b 100644 --- a/components/gateway_controller/include/gateway_controller.hpp +++ b/components/gateway_controller/include/gateway_controller.hpp @@ -119,12 +119,19 @@ class GatewayController { void publishFrame(const std::vector& frame); void handleDaliRawFrame(const DaliRawFrame& frame); + bool sendRawAndMirror(uint8_t gateway_id, uint8_t raw_addr, uint8_t command); + bool sendExtRawAndMirror(uint8_t gateway_id, uint8_t raw_addr, uint8_t command); + bool setBrightAndMirror(uint8_t gateway_id, int dec_address, uint8_t level); + bool offAndMirror(uint8_t gateway_id, int dec_address); + bool onAndMirror(uint8_t gateway_id, int dec_address); uint8_t resolveInternalGroupRawAddress(uint8_t gateway_id, uint8_t raw_addr); static uint8_t normalizeGroupTargetType(uint8_t target_type); static uint8_t normalizeGroupTargetValue(uint8_t target_type, uint8_t target_value); static uint8_t internalGroupRawTargetAddress(uint8_t target_type, uint8_t target_value, uint8_t raw_addr); static int internalGroupDecTargetAddress(uint8_t target_type, uint8_t target_value); + static uint8_t rawArcAddressFromDec(int dec_address); + static uint8_t rawCommandAddressFromDec(int dec_address); static int shortAddressFromRaw(uint8_t raw_addr); static int reverseInRange(int value, int min_value, int max_value); diff --git a/components/gateway_controller/src/gateway_controller.cpp b/components/gateway_controller/src/gateway_controller.cpp index 4911ba9..19edb33 100644 --- a/components/gateway_controller/src/gateway_controller.cpp +++ b/components/gateway_controller/src/gateway_controller.cpp @@ -18,6 +18,8 @@ constexpr const char* kTag = "gateway_controller"; constexpr size_t kMaxNameBytes = 32; constexpr uint8_t kDaliShortAddressCount = 64; constexpr uint8_t kDaliSceneCount = 16; +constexpr uint8_t kDaliCmdOff = 0x00; +constexpr uint8_t kDaliCmdRecallMax = 0x05; constexpr TickType_t kMaintenancePollTicks = pdMS_TO_TICKS(20); class LockGuard { @@ -558,7 +560,7 @@ void GatewayController::dispatchCommand(const std::vector& command) { } case 0x07: case 0x08: - dali_domain_.sendRaw(gateway_id, addr, data); + sendRawAndMirror(gateway_id, addr, data); break; case 0x09: { const auto ids = gatewayIds(); @@ -575,20 +577,20 @@ void GatewayController::dispatchCommand(const std::vector& command) { break; case 0x10: case 0x11: - dali_domain_.sendRaw(gateway_id, resolveInternalGroupRawAddress(gateway_id, addr), data); + sendRawAndMirror(gateway_id, resolveInternalGroupRawAddress(gateway_id, addr), data); break; case 0x12: if (addr == 0xff && data >= 0x10 && data <= 0x1f) { const uint8_t scene_id = static_cast(data - 0x10); if (!executeScene(gateway_id, shortAddressFromRaw(addr), scene_id)) { - dali_domain_.sendRaw(gateway_id, addr, data); + sendRawAndMirror(gateway_id, addr, data); } } else { - dali_domain_.sendRaw(gateway_id, resolveInternalGroupRawAddress(gateway_id, addr), data); + sendRawAndMirror(gateway_id, resolveInternalGroupRawAddress(gateway_id, addr), data); } break; case 0x13: - dali_domain_.sendExtRaw(gateway_id, resolveInternalGroupRawAddress(gateway_id, addr), data); + sendExtRawAndMirror(gateway_id, resolveInternalGroupRawAddress(gateway_id, addr), data); break; case 0x14: { const auto result = dali_domain_.queryRaw(gateway_id, resolveInternalGroupRawAddress(gateway_id, addr), data); @@ -645,7 +647,7 @@ void GatewayController::dispatchCommand(const std::vector& command) { const uint8_t b = command[7]; const int target = shortAddressFromRaw(resolveInternalGroupRawAddress(gateway_id, addr)); if (r == 0 && g == 0 && b == 0) { - dali_domain_.off(gateway_id, target); + offAndMirror(gateway_id, target); } else { dali_domain_.setColourRGB(gateway_id, target, r, g, b); } @@ -755,6 +757,48 @@ void GatewayController::handleDaliRawFrame(const DaliRawFrame& frame) { publishPayload(frame.gateway_id, {0x01, frame.gateway_id, addr, data}); } +bool GatewayController::sendRawAndMirror(uint8_t gateway_id, uint8_t raw_addr, uint8_t command) { + const bool sent = dali_domain_.sendRaw(gateway_id, raw_addr, command); + if (sent) { + cache_.mirrorDaliCommand(gateway_id, raw_addr, command); + } + return sent; +} + +bool GatewayController::sendExtRawAndMirror(uint8_t gateway_id, uint8_t raw_addr, + uint8_t command) { + const bool sent = dali_domain_.sendExtRaw(gateway_id, raw_addr, command); + if (sent) { + cache_.mirrorDaliCommand(gateway_id, raw_addr, command); + } + return sent; +} + +bool GatewayController::setBrightAndMirror(uint8_t gateway_id, int dec_address, uint8_t level) { + const bool sent = dali_domain_.setBright(gateway_id, dec_address, level); + if (sent) { + cache_.mirrorDaliCommand(gateway_id, rawArcAddressFromDec(dec_address), level); + } + return sent; +} + +bool GatewayController::offAndMirror(uint8_t gateway_id, int dec_address) { + const bool sent = dali_domain_.off(gateway_id, dec_address); + if (sent) { + cache_.mirrorDaliCommand(gateway_id, rawCommandAddressFromDec(dec_address), kDaliCmdOff); + } + return sent; +} + +bool GatewayController::onAndMirror(uint8_t gateway_id, int dec_address) { + const bool sent = dali_domain_.on(gateway_id, dec_address); + if (sent) { + cache_.mirrorDaliCommand(gateway_id, rawCommandAddressFromDec(dec_address), + kDaliCmdRecallMax); + } + return sent; +} + uint8_t GatewayController::resolveInternalGroupRawAddress(uint8_t gateway_id, uint8_t raw_addr) { if (raw_addr < 0x80 || raw_addr > 0x9f) { return raw_addr; @@ -808,6 +852,26 @@ int GatewayController::internalGroupDecTargetAddress(uint8_t target_type, uint8_ return 127; } +uint8_t GatewayController::rawArcAddressFromDec(int dec_address) { + if (dec_address >= 0 && dec_address < 64) { + return static_cast(dec_address * 2); + } + if (dec_address >= 64 && dec_address < 80) { + return static_cast(0x80 + (dec_address - 64) * 2); + } + return 0xfe; +} + +uint8_t GatewayController::rawCommandAddressFromDec(int dec_address) { + if (dec_address >= 0 && dec_address < 64) { + return static_cast(dec_address * 2 + 1); + } + if (dec_address >= 64 && dec_address < 80) { + return static_cast(0x80 + (dec_address - 64) * 2 + 1); + } + return 0xff; +} + int GatewayController::shortAddressFromRaw(uint8_t raw_addr) { return raw_addr / 2; } @@ -859,9 +923,9 @@ bool GatewayController::executeScene(uint8_t gateway_id, int short_address, uint return false; } if (scene_data.brightness <= 0) { - dali_domain_.off(gateway_id, short_address); + offAndMirror(gateway_id, short_address); } else { - dali_domain_.setBright(gateway_id, short_address, scene_data.brightness); + setBrightAndMirror(gateway_id, short_address, scene_data.brightness); } if (scene_data.color_mode == 0) { int kelvin = scene_data.data1 * 256 + scene_data.data2; @@ -877,7 +941,7 @@ bool GatewayController::executeScene(uint8_t gateway_id, int short_address, uint 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); + offAndMirror(gateway_id, short_address); } } return true; @@ -912,9 +976,9 @@ bool GatewayController::executeGroup(uint8_t gateway_id, uint8_t group_id) { if (!group_data.enabled) { return false; } - return dali_domain_.on(gateway_id, - internalGroupDecTargetAddress(group_data.target_type, - group_data.target_value)); + return onAndMirror(gateway_id, + internalGroupDecTargetAddress(group_data.target_type, + group_data.target_value)); } void GatewayController::handleGatewayNameCommand(uint8_t gateway_id,