feat(gateway): enhance DALI command handling with mirroring and new target types

This commit is contained in:
Tony
2026-05-02 03:19:02 +08:00
parent 639fdd860e
commit fa2eae87cf
4 changed files with 587 additions and 63 deletions
@@ -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<uint8_t> actual_level;
std::optional<uint8_t> 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<std::optional<uint8_t>, 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<uint16_t> 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<uint8_t> 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<uint8_t, SceneStore> scenes_;
std::map<uint8_t, GroupStore> groups_;
std::map<uint8_t, std::array<GatewayCacheDaliAddressState, 64>> dali_states_;
std::map<uint8_t, std::array<GatewayCacheDaliRuntimeStatus, 16>> dali_group_status_;
std::map<uint8_t, GatewayCacheDaliRuntimeStatus> dali_broadcast_status_;
std::map<uint8_t, DtrState> dtr_states_;
std::map<uint8_t, GatewayCacheChannelFlags> channel_flags_;
uint32_t dali_runtime_revision_{0};
bool dirty_{false};
};
+450 -49
View File
@@ -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<uint8_t> DecodeShortAddress(uint8_t raw_addr) {
if (raw_addr <= 0x7F && (raw_addr & 0x01) != 0) {
return static_cast<uint8_t>(raw_addr >> 1);
std::optional<GatewayCacheDaliTarget> DecodeDaliTarget(uint8_t raw_addr) {
if (raw_addr <= 0x7F) {
return GatewayCacheDaliTarget{GatewayCacheDaliTargetKind::kShortAddress,
static_cast<uint8_t>(raw_addr >> 1)};
}
if (raw_addr >= kDaliGroupRawMin && raw_addr <= kDaliGroupRawMax) {
return GatewayCacheDaliTarget{GatewayCacheDaliTargetKind::kGroup,
static_cast<uint8_t>((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<uint16_t> 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);
mirrorDaliCommandLocked(gateway_id, raw_addr, command);
}
}
} 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 (!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<uint8_t>(command - kDaliCmdGoToSceneMin);
status.revision = nextDaliRuntimeRevisionLocked();
applyDaliTargetRuntimeStatusLocked(gateway_id, *target, status);
return true;
}
if (command >= kDaliCmdAddToGroupMin && command <= kDaliCmdRemoveFromGroupMax) {
applyDaliTargetGroupMutationLocked(gateway_id, *target,
static_cast<uint8_t>(command & 0x0F),
command < (kDaliCmdAddToGroupMin + 16));
return true;
}
if (command >= kDaliCmdSetSceneMin && command < (kDaliCmdSetSceneMin + 16) &&
dtr_state.dtr0.has_value()) {
applyDaliTargetSceneLevelLocked(gateway_id, *target,
static_cast<uint8_t>(command - kDaliCmdSetSceneMin),
*dtr_state.dtr0);
return true;
}
if (command >= (kDaliCmdSetSceneMin + 16) && command <= kDaliCmdRemoveSceneMax) {
applyDaliTargetSceneLevelLocked(
gateway_id, *target, static_cast<uint8_t>(command - (kDaliCmdSetSceneMin + 16)),
static_cast<uint8_t>(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<uint16_t>(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<uint16_t>(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<uint16_t>(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<uint16_t>(~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<uint16_t>(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<uint8_t> 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<uint16_t>(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<uint16_t>(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<uint16_t>(1U << group_id);
if ((state.group_mask & bit) != 0) {
applyDaliRuntimeStatusToAddressLocked(state, groups->second[group_id]);
}
}
}
void GatewayCache::TaskEntry(void* arg) {
static_cast<GatewayCache*>(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) {
@@ -119,12 +119,19 @@ class GatewayController {
void publishFrame(const std::vector<uint8_t>& 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);
@@ -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<uint8_t>& 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<uint8_t>& 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<uint8_t>(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<uint8_t>& 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<uint8_t>(dec_address * 2);
}
if (dec_address >= 64 && dec_address < 80) {
return static_cast<uint8_t>(0x80 + (dec_address - 64) * 2);
}
return 0xfe;
}
uint8_t GatewayController::rawCommandAddressFromDec(int dec_address) {
if (dec_address >= 0 && dec_address < 64) {
return static_cast<uint8_t>(dec_address * 2 + 1);
}
if (dec_address >= 64 && dec_address < 80) {
return static_cast<uint8_t>(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,7 +976,7 @@ bool GatewayController::executeGroup(uint8_t gateway_id, uint8_t group_id) {
if (!group_data.enabled) {
return false;
}
return dali_domain_.on(gateway_id,
return onAndMirror(gateway_id,
internalGroupDecTargetAddress(group_data.target_type,
group_data.target_value));
}