diff --git a/include/dali_gateway.hpp b/include/dali_gateway.hpp index daf5c8c..7e270d6 100644 --- a/include/dali_gateway.hpp +++ b/include/dali_gateway.hpp @@ -83,6 +83,11 @@ struct DaliGatewayRuntimeStatus { struct DaliGatewayAddressState { bool groupMaskKnown{false}; uint16_t groupMask{0}; + // Capability data is populated by discovery adapters and persisted with the + // rest of the address cache so bridge planners do not need a live bus scan + // on every boot. + std::optional daliDeviceTypeMask; + std::optional dt8ColorTypeFeatures; std::array, 16> sceneLevels{}; DaliGatewaySettingsSnapshot settings; DaliGatewayRuntimeStatus status; @@ -143,6 +148,9 @@ class DaliGatewayCache { void markAddressPresence(uint8_t channel, uint8_t shortAddress, DaliGatewayPresence presence); bool setGroupMask(uint8_t channel, uint8_t shortAddress, std::optional groupMask); + bool setCapabilities(uint8_t channel, uint8_t shortAddress, + std::optional daliDeviceTypeMask, + std::optional dt8ColorTypeFeatures); bool setSceneLevel(uint8_t channel, uint8_t shortAddress, uint8_t scene, std::optional level); bool setSettings(uint8_t channel, uint8_t shortAddress, diff --git a/src/dali_gateway_cache.cpp b/src/dali_gateway_cache.cpp index e22a6c0..2cacbc6 100644 --- a/src/dali_gateway_cache.cpp +++ b/src/dali_gateway_cache.cpp @@ -5,7 +5,7 @@ namespace { -constexpr int kAddressStatePayloadVersion = 1; +constexpr int kAddressStatePayloadVersion = 2; constexpr uint32_t kGroupMaskKnown = 1U << 0; constexpr uint32_t kActualLevelKnown = 1U << 1; constexpr uint32_t kSceneKnown = 1U << 2; @@ -17,6 +17,8 @@ constexpr uint32_t kMinKnown = 1U << 7; constexpr uint32_t kMaxKnown = 1U << 8; constexpr uint32_t kFadeTimeKnown = 1U << 9; constexpr uint32_t kFadeRateKnown = 1U << 10; +constexpr uint32_t kDeviceTypeMaskKnown = 1U << 11; +constexpr uint32_t kDt8ColorTypeFeaturesKnown = 1U << 12; std::vector ParseCsv(std::string_view raw) { std::vector values; @@ -65,12 +67,15 @@ uint32_t StateFlags(const DaliGatewayAddressState& state) { if (state.settings.maxLevel.has_value()) flags |= kMaxKnown; if (state.settings.fadeTime.has_value()) flags |= kFadeTimeKnown; if (state.settings.fadeRate.has_value()) flags |= kFadeRateKnown; + if (state.daliDeviceTypeMask.has_value()) flags |= kDeviceTypeMaskKnown; + if (state.dt8ColorTypeFeatures.has_value()) flags |= kDt8ColorTypeFeaturesKnown; return flags; } bool IsDefaultState(const DaliGatewayAddressState& state) { return !state.groupMaskKnown && state.groupMask == 0 && SceneKnownMask(state) == 0 && - !state.settings.anyKnown() && !state.status.anyKnown(); + !state.settings.anyKnown() && !state.status.anyKnown() && + !state.daliDeviceTypeMask.has_value() && !state.dt8ColorTypeFeatures.has_value(); } } // namespace @@ -217,6 +222,22 @@ void DaliGatewayCache::markAddressPresence(uint8_t channel, uint8_t shortAddress ensurePresence(channel)[shortAddress] = presence; } +bool DaliGatewayCache::setCapabilities( + uint8_t channel, uint8_t shortAddress, std::optional daliDeviceTypeMask, + std::optional dt8ColorTypeFeatures) { + std::lock_guard guard(mutex_); + if (shortAddress >= 64) return false; + auto& state = ensureStates(channel)[shortAddress]; + if (state.daliDeviceTypeMask == daliDeviceTypeMask && + state.dt8ColorTypeFeatures == dt8ColorTypeFeatures) { + return false; + } + state.daliDeviceTypeMask = daliDeviceTypeMask; + state.dt8ColorTypeFeatures = dt8ColorTypeFeatures; + markDirty(channel); + return true; +} + std::array DaliGatewayCache::addressStates(uint8_t channel) const { std::lock_guard guard(mutex_); const auto it = states_.find(channel); @@ -286,13 +307,17 @@ std::optional DaliGatewayCache::encodeAddressState( for (const auto& level : state.sceneLevels) { payload += "," + std::to_string(level.value_or(255)); } + payload += "," + std::to_string(state.daliDeviceTypeMask.value_or(0)); + payload += "," + std::to_string(state.dt8ColorTypeFeatures.value_or(0)); return payload; } std::optional DaliGatewayCache::decodeAddressState( std::string_view payload) { const auto values = ParseCsv(payload); - if (values.size() < 13 || values[0] != kAddressStatePayloadVersion) return std::nullopt; + if (values.size() < 13 || (values[0] != 1 && values[0] != kAddressStatePayloadVersion)) { + return std::nullopt; + } DaliGatewayAddressState state; const uint32_t flags = static_cast(std::max(values[1], 0)); @@ -321,6 +346,14 @@ std::optional DaliGatewayCache::decodeAddressState( state.sceneLevels[scene] = ByteValue(values[valueIndex]); } } + if (values[0] >= 2) { + if ((flags & kDeviceTypeMaskKnown) != 0 && values.size() > 29) { + state.daliDeviceTypeMask = WordValue(values[29]); + } + if ((flags & kDt8ColorTypeFeaturesKnown) != 0 && values.size() > 30) { + state.dt8ColorTypeFeatures = ByteValue(values[30]); + } + } return state; } diff --git a/tests/dali_gateway_test.cpp b/tests/dali_gateway_test.cpp index a5fbb7d..e7a4afe 100644 --- a/tests/dali_gateway_test.cpp +++ b/tests/dali_gateway_test.cpp @@ -40,6 +40,7 @@ int main() { [&](const DaliGatewayStatusUpdate&) { ++observer_updates; }); assert(cache.setGroupMask(7, 4, 0x0001)); + assert(cache.setCapabilities(7, 4, 0x0180, 0x07)); assert(cache.mirrorForwardFrame(7, 0x80, 42)); assert(status_updates == 1); assert(observer_updates == status_updates); @@ -87,6 +88,17 @@ int main() { assert(restored_state.groupMask == 0x0001); assert(restored_state.settings.minLevel == 12); assert(restored_state.status.stale); + assert(restored_state.daliDeviceTypeMask == 0x0180); + assert(restored_state.dt8ColorTypeFeatures == 0x07); + + // Version-one records remain readable and retain their original fields. + persisted[{8, 2}] = "1,129,9,2,111,0,0,0,4,254,0,0,0"; + assert(restored.preloadChannel(8)); + const auto migrated_state = restored.addressState(8, 2); + assert(migrated_state.groupMaskKnown); + assert(migrated_state.groupMask == 2); + assert(migrated_state.settings.minLevel == 4); + assert(!migrated_state.daliDeviceTypeMask.has_value()); DaliApplicationControllerConfig config; config.shortAddress = 3;