diff --git a/apps/gateway/sdkconfig b/apps/gateway/sdkconfig index 51a645a..ee3638d 100644 --- a/apps/gateway/sdkconfig +++ b/apps/gateway/sdkconfig @@ -2781,10 +2781,10 @@ CONFIG_DALI_TIMER_RESOLUTION_HZ=3636363 CONFIG_DALI_CUSTOM_HALF_BIT_TIME_X100_US=0 CONFIG_DALI_TX_STOP_CONDITION_US=0 CONFIG_DALI_RX_STOP_CONDITION_US=0 -CONFIG_DALI_QUERY_RESPONSE_TIMEOUT_MS=25 -CONFIG_DALI_DOUBLE_SEND_DELAY_MS=15 -CONFIG_DALI_FORWARD_ACTIVITY_WAIT_MS=15 -CONFIG_DALI_FORWARD_AFTER_BACKWARD_WAIT_MS=5 +CONFIG_DALI_QUERY_RESPONSE_TIMEOUT_MS=30 +CONFIG_DALI_DOUBLE_SEND_DELAY_MS=20 +CONFIG_DALI_FORWARD_ACTIVITY_WAIT_MS=20 +CONFIG_DALI_FORWARD_AFTER_BACKWARD_WAIT_MS=10 CONFIG_DALI_FORWARD_MAX_WAIT_MS=50 CONFIG_DALI_BACKWARD_IDLE_TIMEOUT_MS=10 CONFIG_DALI_BUS_POWER_CHECK_INTERVAL_MS=500 diff --git a/apps/gateway/sdkconfig.old b/apps/gateway/sdkconfig.old index 1d97f53..ce636b7 100644 --- a/apps/gateway/sdkconfig.old +++ b/apps/gateway/sdkconfig.old @@ -623,7 +623,8 @@ CONFIG_GATEWAY_CHANNEL1_NATIVE_BAUDRATE=1200 CONFIG_GATEWAY_CACHE_SUPPORTED=y CONFIG_GATEWAY_CACHE_START_ENABLED=y # CONFIG_GATEWAY_CACHE_RECONCILIATION_ENABLED is not set -CONFIG_GATEWAY_CACHE_FLUSH_INTERVAL_MS=60000 +CONFIG_GATEWAY_CACHE_FLUSH_INTERVAL_MS=10000 +CONFIG_GATEWAY_CACHE_REFRESH_INTERVAL_MS=120000 # end of Gateway Cache # CONFIG_GATEWAY_ENABLE_DALI_BUS is not set @@ -674,7 +675,7 @@ CONFIG_GATEWAY_KNX_BRIDGE_SUPPORTED=y CONFIG_GATEWAY_START_KNX_BRIDGE_ENABLED=y CONFIG_GATEWAY_KNX_DATA_SECURE_SUPPORTED=y # CONFIG_GATEWAY_KNX_IP_SECURE_SUPPORTED is not set -# CONFIG_GATEWAY_KNX_SECURITY_DEV_ENDPOINTS is not set +CONFIG_GATEWAY_KNX_SECURITY_DEV_ENDPOINTS=y CONFIG_GATEWAY_KNX_SECURITY_PLAIN_NVS=y CONFIG_GATEWAY_KNX_OEM_MANUFACTURER_ID=0x01e5 CONFIG_GATEWAY_KNX_OEM_HARDWARE_ID=0xa401 diff --git a/components/dali/Kconfig b/components/dali/Kconfig index 6cf38d7..05747ca 100644 --- a/components/dali/Kconfig +++ b/components/dali/Kconfig @@ -17,11 +17,11 @@ config DALI_DEFAULT_BAUDRATE config DALI_TIMER_RESOLUTION_HZ int "Native DALI timer resolution Hz" range 1000000 8000000 - default 3000000 + default 3636363 help - GPTimer resolution for native DALI Manchester timing. The default 3 MHz - allows a 1200 bps half-bit period of 416.67 us to be represented as - 1250 timer ticks. + GPTimer resolution for native DALI Manchester timing. The default + 3.636363 MHz timing matches the gateway's tuned 1200 bps behavior for + older control gear that needs more recovery margin between commands. config DALI_CUSTOM_HALF_BIT_TIME_X100_US int "Custom native DALI half-bit time x100 us" @@ -51,7 +51,7 @@ config DALI_RX_STOP_CONDITION_US config DALI_QUERY_RESPONSE_TIMEOUT_MS int "DALI query response timeout ms" range 10 100 - default 25 + default 30 help Time to wait for a complete backward frame after a forward query has finished transmitting. DALI backward frames start 5.5-10.5 ms after the @@ -61,7 +61,7 @@ config DALI_QUERY_RESPONSE_TIMEOUT_MS config DALI_DOUBLE_SEND_DELAY_MS int "Double-send delay ms" range 0 100 - default 10 + default 20 help Delay between the two frames sent by dali_send_double(), measured after the first frame has completed. Exposed for development tuning. @@ -69,7 +69,7 @@ config DALI_DOUBLE_SEND_DELAY_MS config DALI_FORWARD_ACTIVITY_WAIT_MS int "Forward-frame wait after bus activity ms" range 0 1000 - default 25 + default 20 help Minimum delay before sending a 2- to 4-byte forward frame after normal bus activity. This is the native queue anti-collision wait. @@ -77,7 +77,7 @@ config DALI_FORWARD_ACTIVITY_WAIT_MS config DALI_FORWARD_AFTER_BACKWARD_WAIT_MS int "Forward-frame wait after backward frame ms" range 0 1000 - default 5 + default 10 help Minimum delay before sending a 2- to 4-byte forward frame after the last valid 1-byte backward frame received by the native bus. @@ -94,7 +94,7 @@ config DALI_FORWARD_MAX_WAIT_MS config DALI_BACKWARD_IDLE_TIMEOUT_MS int "Backward-frame idle wait timeout ms" range 0 1000 - default 9 + default 10 help Time a 1-byte backward frame waits for an idle bus before being sent anyway. Backward frame sends are not echo-verified because collisions @@ -103,7 +103,7 @@ config DALI_BACKWARD_IDLE_TIMEOUT_MS config DALI_BUS_POWER_CHECK_INTERVAL_MS int "Bus power-down check interval ms" range 10 5000 - default 100 + default 500 help Interval used to resample the RX pin while the native DALI bus is marked power-down. This lets the HAL recover when the bus was already powered @@ -112,7 +112,7 @@ config DALI_BUS_POWER_CHECK_INTERVAL_MS config DALI_BUS_ABNORMAL_REPORT_INTERVAL_MS int "Legacy bus abnormal report interval ms" range 0 60000 - default 1000 + default 3000 help Interval for publishing the legacy two-byte FF FD bus-abnormal raw frame while the native DALI bus is power-down. Set to 0 to disable the report. diff --git a/components/dali/README.md b/components/dali/README.md index 1a7d61a..3fe4a41 100644 --- a/components/dali/README.md +++ b/components/dali/README.md @@ -53,13 +53,13 @@ Use `menuconfig` under `DALI Component` to configure: The native bus monitor uses `CONFIG_DALI_BUS_POWER_CHECK_INTERVAL_MS` to resample RX while power-down and `CONFIG_DALI_BUS_ABNORMAL_REPORT_INTERVAL_MS` to publish legacy `FF FD` -raw frames while down. The report interval defaults to 1000 ms; set it to 0 to disable the +raw frames while down. The report interval defaults to 3000 ms; set it to 0 to disable the compatibility report. Native timing defaults target standard 1200 bps DALI: a 416.67 us half-bit period is -generated by the default 3 MHz timer as 1250 ticks. `CONFIG_DALI_CUSTOM_HALF_BIT_TIME_X100_US` +generated by the default 3.636363 MHz timer. `CONFIG_DALI_CUSTOM_HALF_BIT_TIME_X100_US` can override the half-bit period for development; keep it at 0 for baudrate-derived timing. -Query no-response timeout defaults to 25 ms, which covers the 5.5-10.5 ms backward-frame +Query no-response timeout defaults to 30 ms, which covers the 5.5-10.5 ms backward-frame start window plus the approximately 9.95 ms backward frame duration. Native TX queue arbitration uses frame length as the frame type signal. Two- to four-byte diff --git a/components/dali/src/dali.c b/components/dali/src/dali.c index 8389388..9644709 100644 --- a/components/dali/src/dali.c +++ b/components/dali/src/dali.c @@ -7,11 +7,11 @@ #include "freertos/semphr.h" #ifndef CONFIG_DALI_QUERY_RESPONSE_TIMEOUT_MS -#define CONFIG_DALI_QUERY_RESPONSE_TIMEOUT_MS 25 +#define CONFIG_DALI_QUERY_RESPONSE_TIMEOUT_MS 30 #endif #ifndef CONFIG_DALI_DOUBLE_SEND_DELAY_MS -#define CONFIG_DALI_DOUBLE_SEND_DELAY_MS 10 +#define CONFIG_DALI_DOUBLE_SEND_DELAY_MS 20 #endif static SemaphoreHandle_t s_dali_core_lock; diff --git a/components/dali/src/dali_hal_idf5.c b/components/dali/src/dali_hal_idf5.c index 63e10fa..d79137a 100644 --- a/components/dali/src/dali_hal_idf5.c +++ b/components/dali/src/dali_hal_idf5.c @@ -64,7 +64,7 @@ #endif #ifndef CONFIG_DALI_TIMER_RESOLUTION_HZ -#define CONFIG_DALI_TIMER_RESOLUTION_HZ 3000000 +#define CONFIG_DALI_TIMER_RESOLUTION_HZ 3636363 #endif #ifndef CONFIG_DALI_CUSTOM_HALF_BIT_TIME_X100_US @@ -80,19 +80,19 @@ #endif #ifndef CONFIG_DALI_BUS_POWER_CHECK_INTERVAL_MS -#define CONFIG_DALI_BUS_POWER_CHECK_INTERVAL_MS 100 +#define CONFIG_DALI_BUS_POWER_CHECK_INTERVAL_MS 500 #endif #ifndef CONFIG_DALI_BUS_ABNORMAL_REPORT_INTERVAL_MS -#define CONFIG_DALI_BUS_ABNORMAL_REPORT_INTERVAL_MS 1000 +#define CONFIG_DALI_BUS_ABNORMAL_REPORT_INTERVAL_MS 3000 #endif #ifndef CONFIG_DALI_FORWARD_ACTIVITY_WAIT_MS -#define CONFIG_DALI_FORWARD_ACTIVITY_WAIT_MS 25 +#define CONFIG_DALI_FORWARD_ACTIVITY_WAIT_MS 20 #endif #ifndef CONFIG_DALI_FORWARD_AFTER_BACKWARD_WAIT_MS -#define CONFIG_DALI_FORWARD_AFTER_BACKWARD_WAIT_MS 5 +#define CONFIG_DALI_FORWARD_AFTER_BACKWARD_WAIT_MS 10 #endif #ifndef CONFIG_DALI_FORWARD_MAX_WAIT_MS @@ -100,7 +100,7 @@ #endif #ifndef CONFIG_DALI_BACKWARD_IDLE_TIMEOUT_MS -#define CONFIG_DALI_BACKWARD_IDLE_TIMEOUT_MS 9 +#define CONFIG_DALI_BACKWARD_IDLE_TIMEOUT_MS 10 #endif #define WITHIN_RANGE(x, min, max) ((x) > (min) && (x) < (max)) diff --git a/components/gateway_knx/src/gateway_knx_bridge.cpp b/components/gateway_knx/src/gateway_knx_bridge.cpp index 1142fe5..8990ff2 100644 --- a/components/gateway_knx/src/gateway_knx_bridge.cpp +++ b/components/gateway_knx/src/gateway_knx_bridge.cpp @@ -247,6 +247,121 @@ void GatewayKnxBridge::runCommissioningScanTask() { } while (!is_cancelled()) { + auto log_and_record_ballast = [&](const GatewayKnxCommissioningBallast& ballast) { + record_ballast(ballast); + ESP_LOGI(kTag, "REG1-Dali scan found random=0x%02X%02X%02X short=%u", + ballast.high, ballast.middle, ballast.low, + static_cast(ballast.short_address)); + }; + auto withdraw_selected = [&]() { + return SendRaw(engine_, DALI_CMD_SPECIAL_WITHDRAW, DALI_CMD_OFF, + "knx-function-scan-withdraw"); + }; + auto program_selected_ballast = + [&](uint8_t short_address, + uint32_t fallback_random_address) -> std::optional { + if (!ProgramShortAddressAndConfirm(engine_, short_address)) { + return std::nullopt; + } + + const auto random_address = + QueryRandomAddressForShortAddress(engine_, short_address).value_or(fallback_random_address); + GatewayKnxCommissioningBallast ballast; + ballast.high = static_cast((random_address >> 16) & 0xff); + ballast.middle = static_cast((random_address >> 8) & 0xff); + ballast.low = static_cast(random_address & 0xff); + ballast.short_address = short_address; + return ballast; + }; + + if (options.assign) { + const auto next_address = NextFreeShortAddress(used_addresses); + if (!next_address.has_value()) { + ESP_LOGW(kTag, "REG1-Dali scan has no free short address left"); + break; + } + + const auto compare_base = FindSelectedCommissioningCompareBase(engine_); + if (!compare_base.has_value()) { + break; + } + + const auto first_ballast = + program_selected_ballast(next_address.value(), compare_base.value()); + if (!first_ballast.has_value()) { + ESP_LOGW(kTag, "REG1-Dali scan failed to program short address %u", + static_cast(next_address.value())); + break; + } + used_addresses[next_address.value()] = true; + log_and_record_ballast(first_ballast.value()); + + if (is_cancelled()) { + clear_results = true; + break; + } + if (!withdraw_selected()) { + ESP_LOGW(kTag, "REG1-Dali scan failed while withdrawing matched device"); + break; + } + + auto compare_cursor = IncrementRandomAddress(compare_base.value()); + bool compare_multi_failed = false; + while (!is_cancelled() && compare_cursor.has_value()) { + const auto contiguous_short = NextFreeShortAddress(used_addresses); + if (!contiguous_short.has_value()) { + break; + } + + const auto next_search = IncrementRandomAddress(compare_cursor.value()); + if (!next_search.has_value()) { + break; + } + + const auto matched = CompareSelectedSearchAddress( + engine_, next_search.value(), "knx-function-scan-compare-multi"); + if (!matched.has_value()) { + compare_multi_failed = true; + break; + } + if (!matched.value()) { + break; + } + + compare_cursor = next_search.value(); + const auto ballast = + program_selected_ballast(contiguous_short.value(), compare_cursor.value()); + if (!ballast.has_value()) { + ESP_LOGW(kTag, "REG1-Dali scan failed to program short address %u", + static_cast(contiguous_short.value())); + compare_multi_failed = true; + break; + } + + used_addresses[contiguous_short.value()] = true; + log_and_record_ballast(ballast.value()); + + if (is_cancelled()) { + clear_results = true; + break; + } + if (!withdraw_selected()) { + ESP_LOGW(kTag, "REG1-Dali scan failed while withdrawing matched device"); + compare_multi_failed = true; + break; + } + } + + if (compare_multi_failed) { + break; + } + if (is_cancelled()) { + clear_results = true; + break; + } + continue; + } + const auto random_address = FindLowestSelectedRandomAddress(engine_); if (!random_address.has_value()) { break; @@ -258,38 +373,15 @@ void GatewayKnxBridge::runCommissioningScanTask() { ballast.low = static_cast(random_address.value() & 0xff); ballast.short_address = 0xff; - if (options.assign) { - const auto next_address = NextFreeShortAddress(used_addresses); - if (!next_address.has_value()) { - ESP_LOGW(kTag, "REG1-Dali scan has no free short address left for 0x%06x", - static_cast(random_address.value())); - break; - } - if (!SendRaw(engine_, DALI_CMD_SPECIAL_PROGRAM_SHORT_ADDRESS, - DaliComm::toCmdAddr(next_address.value()), - "knx-function-scan-program-short") || - !VerifyShortAddress(engine_, next_address.value())) { - ESP_LOGW(kTag, "REG1-Dali scan failed to program short address %u", - static_cast(next_address.value())); - break; - } - used_addresses[next_address.value()] = true; - ballast.short_address = next_address.value(); - } else { - ballast.short_address = QuerySelectedShortAddress(engine_).value_or(0xff); - } + ballast.short_address = QuerySelectedShortAddress(engine_).value_or(0xff); - record_ballast(ballast); - ESP_LOGI(kTag, "REG1-Dali scan found random=0x%02X%02X%02X short=%u", - ballast.high, ballast.middle, ballast.low, - static_cast(ballast.short_address)); + log_and_record_ballast(ballast); if (is_cancelled()) { clear_results = true; break; } - if (!SendRaw(engine_, DALI_CMD_SPECIAL_WITHDRAW, DALI_CMD_OFF, - "knx-function-scan-withdraw")) { + if (!withdraw_selected()) { ESP_LOGW(kTag, "REG1-Dali scan failed while withdrawing matched device"); break; } @@ -662,12 +754,12 @@ bool GatewayKnxBridge::handleReg1AssignCommand(const uint8_t* data, size_t len, const uint8_t short_address = data[1] == 99 ? 0xff : data[1]; const bool ok = SendRawExt(engine_, DALI_CMD_SPECIAL_INITIALIZE, 0x00, "knx-function-assign-init") && - SendRaw(engine_, DALI_CMD_SPECIAL_SEARCHADDRH, data[2], - "knx-function-assign-search-h") && - SendRaw(engine_, DALI_CMD_SPECIAL_SEARCHADDRM, data[3], - "knx-function-assign-search-m") && SendRaw(engine_, DALI_CMD_SPECIAL_SEARCHADDRL, data[4], - "knx-function-assign-search-l") && + "knx-function-assign-search-l") && + SendRaw(engine_, DALI_CMD_SPECIAL_SEARCHADDRM, data[3], + "knx-function-assign-search-m") && + SendRaw(engine_, DALI_CMD_SPECIAL_SEARCHADDRH, data[2], + "knx-function-assign-search-h") && SendRaw(engine_, DALI_CMD_SPECIAL_PROGRAM_SHORT_ADDRESS, short_address == 0xff ? 0xff : DaliComm::toCmdAddr(short_address), "knx-function-assign-program") && diff --git a/components/gateway_knx/src/gateway_knx_private.hpp b/components/gateway_knx/src/gateway_knx_private.hpp index c964078..012938e 100644 --- a/components/gateway_knx/src/gateway_knx_private.hpp +++ b/components/gateway_knx/src/gateway_knx_private.hpp @@ -969,19 +969,16 @@ DaliBridgeResult IgnoredResult(uint16_t group_address, uint16_t group_object_num } bool SetSearchAddress(DaliBridgeEngine& engine, uint32_t search_address, const char* sequence) { - return SendRaw(engine, DALI_CMD_SPECIAL_SEARCHADDRH, - static_cast((search_address >> 16) & 0xff), sequence) && + return SendRaw(engine, DALI_CMD_SPECIAL_SEARCHADDRL, + static_cast(search_address & 0xff), sequence) && SendRaw(engine, DALI_CMD_SPECIAL_SEARCHADDRM, static_cast((search_address >> 8) & 0xff), sequence) && - SendRaw(engine, DALI_CMD_SPECIAL_SEARCHADDRL, - static_cast(search_address & 0xff), sequence); + SendRaw(engine, DALI_CMD_SPECIAL_SEARCHADDRH, + static_cast((search_address >> 16) & 0xff), sequence); } -std::optional CompareSelectedSearchAddress(DaliBridgeEngine& engine, uint32_t search_address, - const char* sequence) { - if (!SetSearchAddress(engine, search_address, sequence)) { - return std::nullopt; - } +std::optional CompareCurrentSearchAddress(DaliBridgeEngine& engine, + const char* sequence) { const auto result = ExecuteRaw(engine, BridgeOperation::query, DALI_CMD_SPECIAL_COMPARE, DALI_CMD_OFF, sequence); if (result.ok && result.data.has_value()) { @@ -989,14 +986,14 @@ std::optional CompareSelectedSearchAddress(DaliBridgeEngine& engine, uint3 } if (DaliQueryResultHasStatus(result, "noResponse") || DaliQueryResultHasStatus(result, "timeout")) { - ESP_LOGD(kTag, "DALI compare no match seq=%s search=0x%06x status=%s", - sequence == nullptr ? "" : sequence, static_cast(search_address), + ESP_LOGD(kTag, "DALI compare no match seq=%s status=%s", + sequence == nullptr ? "" : sequence, DaliQueryResultHasStatus(result, "timeout") ? "timeout" : "noResponse"); return false; } if (!result.error.empty()) { - ESP_LOGW(kTag, "DALI compare failed seq=%s search=0x%06x error=%s", - sequence == nullptr ? "" : sequence, static_cast(search_address), + ESP_LOGW(kTag, "DALI compare failed seq=%s error=%s", + sequence == nullptr ? "" : sequence, result.error.c_str()); } if (!result.ok || !result.data.has_value()) { @@ -1005,6 +1002,53 @@ std::optional CompareSelectedSearchAddress(DaliBridgeEngine& engine, uint3 return true; } +std::optional CompareSelectedSearchAddress(DaliBridgeEngine& engine, uint32_t search_address, + const char* sequence) { + if (!SetSearchAddress(engine, search_address, sequence)) { + return std::nullopt; + } + return CompareCurrentSearchAddress(engine, sequence); +} + +bool SetSearchAddressComponent(DaliBridgeEngine& engine, int typ, uint8_t value, + const char* sequence) { + switch (typ) { + case 1: + return SendRaw(engine, DALI_CMD_SPECIAL_SEARCHADDRH, value, sequence); + case 2: + return SendRaw(engine, DALI_CMD_SPECIAL_SEARCHADDRM, value, sequence); + case 3: + return SendRaw(engine, DALI_CMD_SPECIAL_SEARCHADDRL, value, sequence); + default: + return false; + } +} + +std::optional CompareSelectedSearchComponent(DaliBridgeEngine& engine, int typ, int value, + const char* sequence) { + if (!SetSearchAddressComponent(engine, typ, static_cast(value & 0xff), sequence)) { + return std::nullopt; + } + return CompareCurrentSearchAddress(engine, sequence); +} + +std::optional IncrementRandomAddress(uint32_t random_address) { + if (random_address >= 0x00ffffffu) { + return std::nullopt; + } + return random_address + 1u; +} + +std::optional FindLowestSelectedRandomAddress(DaliBridgeEngine& engine); + +std::optional FindSelectedCommissioningCompareBase(DaliBridgeEngine& engine) { + const auto lowest = FindLowestSelectedRandomAddress(engine); + if (!lowest.has_value()) { + return std::nullopt; + } + return lowest.value() == 0 ? 0u : lowest.value() - 1u; +} + std::optional FindLowestSelectedRandomAddress(DaliBridgeEngine& engine) { const auto any = CompareSelectedSearchAddress(engine, 0x00ffffffu, "knx-function-scan-compare-any"); @@ -1042,13 +1086,42 @@ std::optional QuerySelectedShortAddress(DaliBridgeEngine& engine) { return static_cast((raw.value() >> 1) & 0x3f); } -bool VerifyShortAddress(DaliBridgeEngine& engine, uint8_t short_address) { +bool VerifySelectedShortAddress(DaliBridgeEngine& engine, uint8_t short_address) { const auto raw = ExecuteRawQuery(engine, DALI_CMD_SPECIAL_VERIFY_SHORT_ADDRESS, DaliComm::toCmdAddr(short_address), "knx-function-scan-verify-short"); return raw.has_value() && raw.value() == 0xff; } +bool ProgramShortAddressAndConfirm(DaliBridgeEngine& engine, uint8_t short_address) { + if (!SendRaw(engine, DALI_CMD_SPECIAL_PROGRAM_SHORT_ADDRESS, + DaliComm::toCmdAddr(short_address), + "knx-function-scan-program-short")) { + return false; + } + if (VerifySelectedShortAddress(engine, short_address)) { + return true; + } + const auto confirmed = QuerySelectedShortAddress(engine); + return confirmed.has_value() && confirmed.value() == short_address; +} + +std::optional QueryRandomAddressForShortAddress(DaliBridgeEngine& engine, + uint8_t short_address) { + const auto high = QueryShort(engine, short_address, DALI_CMD_QUERY_RANDOM_ADDRESS_H, + "knx-function-scan-query-random-h"); + const auto middle = QueryShort(engine, short_address, DALI_CMD_QUERY_RANDOM_ADDRESS_M, + "knx-function-scan-query-random-m"); + const auto low = QueryShort(engine, short_address, DALI_CMD_QUERY_RANDOM_ADDRESS_L, + "knx-function-scan-query-random-l"); + if (!high.has_value() || !middle.has_value() || !low.has_value()) { + return std::nullopt; + } + return (static_cast(high.value() & 0xff) << 16) | + (static_cast(middle.value() & 0xff) << 8) | + static_cast(low.value() & 0xff); +} + std::array QueryUsedShortAddresses(DaliBridgeEngine& engine) { std::array used{}; for (int short_address = 0; short_address < static_cast(used.size()); ++short_address) {