From d716dec667fe0bbaaed7762d0e3efbe03a745b36 Mon Sep 17 00:00:00 2001 From: Theo Arends <11044339+arendst@users.noreply.github.com> Date: Sat, 6 Dec 2025 17:58:41 +0100 Subject: [PATCH] Fix and refactor DALI GUI multi controller sync --- .../include/tasmota_configurations_ESP32.h | 1 - tasmota/my_user_config.h | 3 +- tasmota/tasmota_xdrv_driver/xdrv_75_dali.ino | 270 +++++++++--------- 3 files changed, 140 insertions(+), 134 deletions(-) diff --git a/tasmota/include/tasmota_configurations_ESP32.h b/tasmota/include/tasmota_configurations_ESP32.h index 596a776b8..bc6c581f2 100644 --- a/tasmota/include/tasmota_configurations_ESP32.h +++ b/tasmota/include/tasmota_configurations_ESP32.h @@ -799,7 +799,6 @@ #define USE_KNX // Enable KNX IP Protocol Support (+23k code, +3k3 mem) #endif #define USE_DALI // Add support for DALI gateway (+5k code) - #define DALI_LIGHT_COLOR_SUPPORT // Add support for DALI DT8 RGBWAF color control (+0k7 code) #define USE_ESP32_TWAI // Add support for TWAI/CAN interface (+7k code) #endif // FIRMWARE_TASMOTA32 diff --git a/tasmota/my_user_config.h b/tasmota/my_user_config.h index 2e2f1078b..94e50d1a1 100644 --- a/tasmota/my_user_config.h +++ b/tasmota/my_user_config.h @@ -941,8 +941,7 @@ //#define USE_WOOLIIS // Add support for Wooliis Hall Effect Coulometer or Battery capacity monitor (+1k6 code) //#define USE_DALI // Add support for DALI gateway (+7k6 code) // #define DALI_POWER_OFF_NO_FADE // Power off immediatly without fading (+0k1 code) -// #define DALI_LIGHT_COLOR_SUPPORT // Add support for DALI DT8 RGBWAF color control (+0k7 code) -// #define DALI_LIGHT_NO_READ_AFTER_WRITE // Use no DTR read-after-write for smooth color transitions saving 55ms / channel (-0k1 code) +// #define DALI_LIGHT_NO_READ_AFTER_WRITE // Use no DTR read-after-write for smooth color transitions saving 55ms / channel (-0k1 code) // -- Power monitoring sensors -------------------- #define USE_ENERGY_SENSOR // Add support for Energy Monitors (+14k code) diff --git a/tasmota/tasmota_xdrv_driver/xdrv_75_dali.ino b/tasmota/tasmota_xdrv_driver/xdrv_75_dali.ino index 5648f8f54..b565a5728 100644 --- a/tasmota/tasmota_xdrv_driver/xdrv_75_dali.ino +++ b/tasmota/tasmota_xdrv_driver/xdrv_75_dali.ino @@ -65,6 +65,8 @@ -------------------------------------------------------------------------------------------- Version yyyymmdd Action Description -------------------------------------------------------------------------------------------- + 1.5.0.0 20251206 update - Fix WAF GUI sync + - Refactor GUI sync 1.4.1.0 20251130 update - Add options to `DaliGear` and DaliGroup` to toggle specific outputs - Make max number of devices persistent to speed up scan response 1.4.0.0 20251126 update - Change to TasmotaDali library @@ -134,9 +136,7 @@ #define DALI_TIMEOUT 20 // DALI backward frame receive timeout (ms) - Protocol = >7Te and <22Te (22 * 417us) #endif -//#define DALI_LIGHT_COLOR_SUPPORT // Support DALI DT8 RGBWAF //#define DALI_LIGHT_NO_READ_AFTER_WRITE // Use no DTR read-after-write for smooth color transitions (saves 55ms / channel) - //#define DALI_POWER_OFF_NO_FADE // Power off immediatly without fading //#define DALI_DEBUG @@ -162,6 +162,7 @@ typedef struct DliSettings_t { struct DALI { DliSettings_t Settings; // Persistent settings TasmotaDali *dali; + uint32_t light_sync; uint8_t address; uint8_t command; uint8_t last_dimmer; @@ -178,7 +179,6 @@ struct DALI { bool allow_light; bool last_power; bool power[DALI_MAX_STORED]; - bool light_sync; } *Dali = nullptr; /*********************************************************************************************\ @@ -486,7 +486,6 @@ int DaliQueryExtendedVersionNumber(uint32_t adr, uint32_t device_type) { return DaliSendWaitResponse(adr | DALI_SELECTOR_BIT, 255); // DALI_xxx_QUERY_EXTENDED_VERSION_NUMBER } -#ifdef DALI_LIGHT_COLOR_SUPPORT uint32_t DaliQueryRGBWAF(uint32_t adr) { // https://www.dali-alliance.org/tech-notes/device-type-discovery.html uint32_t rgbwaf_channels = 0; @@ -528,7 +527,6 @@ uint32_t DaliQueryRGBWAF(uint32_t adr) { } return rgbwaf_channels; } -#endif // DALI_LIGHT_COLOR_SUPPORT /*-------------------------------------------------------------------------------------------*/ @@ -713,6 +711,55 @@ void ResponseDali(uint32_t index) { /*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/ +#ifdef USE_LIGHT +bool DaliLoopSync(uint32_t channels) { + // Sync local light settings with DALI bus data + if (!Dali->allow_light || + !Settings->sbflag1.dali_light || // DaliLight 1 + (DaliTarget2Address(Dali->Settings.target) != Dali->address)) { + return false; + } + + if (Settings->save_data) { + // Postpone save_data during fast color changes which results in exception 0 on ESP8266 + TasmotaGlobal.save_data_counter = 4; + } + + uint32_t rgb = 1; // RGB channel(s) present but not sure if powered ON + uint32_t waf = (channels > 3) ? 1 : 0; + for (uint32_t i = 0; i < channels; i++) { + if (Dali->color[i] > 0) { + if (i < 3) { + rgb = 2; // At least one RGB channel is powered ON + } else { + waf = 2; // At least one WAF (CCT) channel is powered ON + } + } + } + if ((2 == rgb) || (2 == waf)) { + uint8_t color[LST_MAX] = {}; // Init as 0 + for (uint32_t i = 0; i < channels; i++) { + if (i < 3) { + color[i] = (2 == rgb) ? Dali->color[i] : Light.current_color[i]; + } else { + color[i] = (2 == waf) ? Dali->color[i] : Light.current_color[i]; + } + } + Dali->light_sync = millis(); // Block local loop + light_controller.changeChannels(color); + } + Dali->light_sync = millis(); // Block local loop + ExecuteCommandPower(LightDevice(), (2 == rgb) ? 9 : 8, SRC_SWITCH); + if (waf) { + Dali->light_sync = millis(); // Block local loop + ExecuteCommandPower(LightDevice() +1, (2 == waf) ? 9 : 8, SRC_SWITCH); + } + return true; +} +#endif // USE_LIGHT + +/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/ + void DaliLoop(void) { while (Dali->dali->available()) { uint32_t queue = Dali->dali->available(); @@ -724,82 +771,44 @@ void DaliLoop(void) { AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("DLI: Rx 0x%08X %2d queue %d%s%s"), frame.data, bit_count, queue, (8 == bit_count)?" backward":"", (collision)?" collision":""); - if ((frame.meta != 16) || // Skip backward frames - (1 == Dali->probe)) { // Probe only + if ((frame.meta != 16) || // Skip backward frames + (1 == Dali->probe)) { // Probe only continue; } Dali->address = (frame.data >> 8) &0xFF; Dali->command = frame.data &0xFF; - #ifdef USE_LIGHT - #ifdef DALI_LIGHT_COLOR_SUPPORT - if (DALI_102_SET_DTR0 == Dali->address) { Dali->dtr[0] = Dali->command; } // Might be Red / White +#ifdef USE_LIGHT + if (DALI_102_SET_DTR0 == Dali->address) { Dali->dtr[0] = Dali->command; } // Might be Red / White else if (DALI_102_SET_DTR1 == Dali->address) { Dali->dtr[1] = Dali->command; } // Might be Green / Amber else if (DALI_102_SET_DTR2 == Dali->address) { Dali->dtr[2] = Dali->command; } // Might be Blue else if (DALI_209_SET_TEMPORARY_RGB_DIMLEVEL == Dali->command) { - Dali->color[0] = Dali->dtr[0]; // Red - Dali->color[1] = Dali->dtr[1]; // Green - Dali->color[2] = Dali->dtr[2]; // Blue + Dali->color[0] = Dali->dtr[0]; // Red + Dali->color[1] = Dali->dtr[1]; // Green + Dali->color[2] = Dali->dtr[2]; // Blue } - else if (DALI_209_SET_TEMPORARY_RGB_DIMLEVEL == Dali->command) { - Dali->color[3] = Dali->dtr[0]; // Cold White - Dali->color[4] = Dali->dtr[1]; // Warm White (Amber) + else if (DALI_209_SET_TEMPORARY_WAF_DIMLEVEL == Dali->command) { + Dali->color[3] = Dali->dtr[0]; // Cold White + Dali->color[4] = Dali->dtr[1]; // Warm White (Amber) } else if (DALI_209_ACTIVATE == Dali->command) { uint32_t channels = Dali->Settings.light_type -8; if ((Dali->target_rgbwaf > 0) && (channels > 0)) { // Color control - Dali->address &= 0xFE; // Reset DALI_SELECTOR_BIT set - if (Dali->allow_light && (DaliTarget2Address(Dali->Settings.target) == Dali->address)) { - if (Settings->sbflag1.dali_light) { // DaliLight 1 - uint32_t any_color = 0; - char scolors[20]; - scolors[0] = 0; - for (uint32_t i = 0; i < channels; i++) { - any_color += Dali->color[i]; - snprintf_P(scolors, sizeof(scolors), PSTR("%s%02X"), scolors, Dali->color[i]); - } - Dali->light_sync = true; // Block local loop - if (any_color) { - if (Settings->save_data) { - // Postpone save_data during fast color changes which results in exception 0 on ESP8266 - TasmotaGlobal.save_data_counter = 4; - } - char scmnd[20]; - snprintf_P(scmnd, sizeof(scmnd), PSTR(D_CMND_COLOR " %s"), scolors); - ExecuteCommand(scmnd, SRC_SWITCH); - } else { - ExecuteCommandPower(LightDevice(), 0, SRC_SWITCH); - } - } - } + Dali->address &= 0xFE; // Reset DALI_SELECTOR_BIT set + DaliLoopSync(channels); // Sync local light settings with DALI bus data } } else - #endif // DALI_LIGHT_COLOR_SUPPORT - #endif // USE_LIGHT - if (!(Dali->address & DALI_SELECTOR_BIT)) { // Address +#endif // USE_LIGHT + if (!(Dali->address & DALI_SELECTOR_BIT)) { // Address = DAPC command uint32_t index = DaliSaveState(Dali->address, Dali->command); // Update dimmer and power bool show_response = true; - #ifdef USE_LIGHT - if (Dali->allow_light && (DaliTarget2Address(Dali->Settings.target) == Dali->address)) { - if (Settings->sbflag1.dali_light) { // DaliLight 1 - // Sync local light settings with DALI bus data - uint8_t dim_old = changeUIntScale(Dali->last_dimmer, 0, 254, 0, 100); - uint8_t dim_new = changeUIntScale(Dali->dimmer[index], 0, 254, 0, 100); - if (Dali->last_power != Dali->power[index]) { - Dali->light_sync = true; // Block local loop - ExecuteCommandPower(LightDevice(), Dali->power[index], SRC_SWITCH); - } - else if (dim_old != dim_new) { - char scmnd[20]; - snprintf_P(scmnd, sizeof(scmnd), PSTR(D_CMND_DIMMER " %d"), dim_new); - Dali->light_sync = true; // Block local loop - ExecuteCommand(scmnd, SRC_SWITCH); - } - show_response = false; - } +#ifdef USE_LIGHT + Dali->color[0] = Dali->command; // Contains "dimmer" value 0..254 + if (DaliLoopSync(1)) { // Sync local light settings with DALI bus data + show_response = false; } - #endif // USE_LIGHT +#endif // USE_LIGHT if (show_response) { ResponseDali(index); MqttPublishPrefixTopicRulesProcess_P(RESULT_OR_TELE, PSTR(D_PRFX_DALI)); @@ -828,85 +837,88 @@ bool DaliSetChannels(void) { if (Settings->sbflag1.dali_light) { // DaliLight 1 Settings->light_fade = 0; // Use Dali fading Settings->light_correction = 0; // Use Dali light correction + + uint8_t *cur_col = (uint8_t*)XdrvMailbox.data; + AddLog(LOG_LEVEL_DEBUG, PSTR("DBG: SetChannels Sync %d cur_col %02X %02X %02X %02X %02X"), Dali->light_sync, cur_col[0], cur_col[1], cur_col[2], cur_col[3], cur_col[4]); + if (Dali->light_sync) { // Block local loop - Dali->light_sync = false; - } else { - uint8_t *cur_col = (uint8_t*)XdrvMailbox.data; - // cur_col[0] = Red, cur_col[1] = Green, cur_col[2] = Blue, cur_col[3] = Cold = White, cur_col[4] = Warm = Amber - for (uint32_t i = 0; i < 5; i++) { - if (255 == cur_col[i]) { cur_col[i] = 254; } // Max Dali value + uint32_t light_sync = Dali->light_sync; + Dali->light_sync = 0; + if (TimePassedSince(light_sync) < 200) { // Time it can take to call DaliSetChannels() from Dali received data + return true; } - uint32_t adr = DaliTarget2Address(Dali->Settings.target); -#ifdef DALI_LIGHT_COLOR_SUPPORT - uint32_t channels = Dali->Settings.light_type -8; - if ((Dali->target_rgbwaf > 0) && (channels > 0)) { // Color control - adr |= DALI_SELECTOR_BIT; // Enable Selector bit + } + // cur_col[0] = Red, cur_col[1] = Green, cur_col[2] = Blue, cur_col[3] = Cold = White, cur_col[4] = Warm = Amber + for (uint32_t i = 0; i < 5; i++) { + if (255 == cur_col[i]) { cur_col[i] = 254; } // Max Dali value + } + uint32_t adr = DaliTarget2Address(Dali->Settings.target); + uint32_t channels = Dali->Settings.light_type -8; + if ((Dali->target_rgbwaf > 0) && (channels > 0)) { // Color control + adr |= DALI_SELECTOR_BIT; // Enable Selector bit #ifdef DALI_POWER_OFF_NO_FADE - uint32_t power_on = 0; - for (uint32_t i = 0; i < channels; i++) { - power_on += cur_col[i]; - } - if (!power_on) { - DaliSendData(adr, DALI_102_OFF); // Power off without fade - return true; - } + uint32_t power_on = 0; + for (uint32_t i = 0; i < channels; i++) { + power_on += cur_col[i]; + } + if (!power_on) { + DaliSendData(adr, DALI_102_OFF); // Power off without fade + return true; + } #endif // DALI_POWER_OFF_NO_FADE #ifdef DALI_LIGHT_NO_READ_AFTER_WRITE - // This takes 310ms for 3 channels but might send bad data as no DTR read-after-write - DaliSendData(DALI_102_SET_DTR0, 0x7F); // Linked Channel control - DaliSendData(DALI_102_ENABLE_DEVICE_TYPE_X, DALI_209_DEVICE_TYPE); // Enable Extended command - DaliSendData(adr, DALI_209_SET_TEMPORARY_RGBWAF_CONTROL); + // This takes 310ms for 3 channels but might send bad data as no DTR read-after-write + DaliSendData(DALI_102_SET_DTR0, 0x7F); // Linked Channel control + DaliSendData(DALI_102_ENABLE_DEVICE_TYPE_X, DALI_209_DEVICE_TYPE); // Enable Extended command + DaliSendData(adr, DALI_209_SET_TEMPORARY_RGBWAF_CONTROL); - DaliSendData(DALI_102_SET_DTR0, cur_col[0]); // DALI Red - DaliSendData(DALI_102_SET_DTR1, (channels > 1) ? cur_col[1] : 255); // DALI Green - DaliSendData(DALI_102_SET_DTR2, (channels > 2) ? cur_col[2] : 255); // DALI Blue - DaliSendData(DALI_102_ENABLE_DEVICE_TYPE_X, DALI_209_DEVICE_TYPE); // Enable Extended command - DaliSendData(adr, DALI_209_SET_TEMPORARY_RGB_DIMLEVEL); + DaliSendData(DALI_102_SET_DTR0, cur_col[0]); // DALI Red + DaliSendData(DALI_102_SET_DTR1, (channels > 1) ? cur_col[1] : 255); // DALI Green + DaliSendData(DALI_102_SET_DTR2, (channels > 2) ? cur_col[2] : 255); // DALI Blue + DaliSendData(DALI_102_ENABLE_DEVICE_TYPE_X, DALI_209_DEVICE_TYPE); // Enable Extended command + DaliSendData(adr, DALI_209_SET_TEMPORARY_RGB_DIMLEVEL); - if (channels > 3) { - DaliSendData(DALI_102_SET_DTR0, cur_col[3]); // DALI White - DaliSendData(DALI_102_SET_DTR1, (channels > 4) ? cur_col[4] : 255); // DALI Amber - DaliSendData(DALI_102_SET_DTR2, 255); // DALI Freecolour - no change - DaliSendData(DALI_102_ENABLE_DEVICE_TYPE_X, DALI_209_DEVICE_TYPE); // Enable Extended command - DaliSendData(adr, DALI_209_SET_TEMPORARY_WAF_DIMLEVEL); - } -#else - // This takes 480ms for 3 channels but might send nothing if DTR read-after-write fails - if (!DaliSetDTR(0, adr, 0x7F)) { return true; } // Linked Channel control + if (channels > 3) { + DaliSendData(DALI_102_SET_DTR0, cur_col[3]); // DALI White + DaliSendData(DALI_102_SET_DTR1, (channels > 4) ? cur_col[4] : 255); // DALI Amber + DaliSendData(DALI_102_SET_DTR2, 255); // DALI Freecolour - no change DaliSendData(DALI_102_ENABLE_DEVICE_TYPE_X, DALI_209_DEVICE_TYPE); // Enable Extended command - DaliSendData(adr, DALI_209_SET_TEMPORARY_RGBWAF_CONTROL); - - if (!DaliSetDTR(0, adr, cur_col[0])) { return true; } // DALI Red - if (!DaliSetDTR(1, adr, (channels > 1) ? cur_col[1] : 255)) { return true; } // DALI Green - if (!DaliSetDTR(2, adr, (channels > 2) ? cur_col[2] : 255)) { return true; } // DALI Blue - DaliSendData(DALI_102_ENABLE_DEVICE_TYPE_X, DALI_209_DEVICE_TYPE); // Enable Extended command - DaliSendData(adr, DALI_209_SET_TEMPORARY_RGB_DIMLEVEL); - - if (channels > 3) { - if (!DaliSetDTR(0, adr, cur_col[3])) { return true; } // DALI While - if (!DaliSetDTR(1, adr, (channels > 4) ? cur_col[4] : 255)) { return true; } // DALI Amber - if (!DaliSetDTR(2, adr, 255)) { return true; } // DALI Freecolour - no change - DaliSendData(DALI_102_ENABLE_DEVICE_TYPE_X, DALI_209_DEVICE_TYPE); // Enable Extended command - DaliSendData(adr, DALI_209_SET_TEMPORARY_WAF_DIMLEVEL); - } - -#endif // DALI_LIGHT_NO_READ_AFTER_WRITE - DaliSendData(DALI_102_ENABLE_DEVICE_TYPE_X, DALI_209_DEVICE_TYPE); // Enable Extended command - DaliSendData(adr, DALI_209_ACTIVATE); - return true; + DaliSendData(adr, DALI_209_SET_TEMPORARY_WAF_DIMLEVEL); } -#endif // DALI_LIGHT_COLOR_SUPPORT +#else + // This takes 480ms for 3 channels but might send nothing if DTR read-after-write fails + if (!DaliSetDTR(0, adr, 0x7F)) { return true; } // Linked Channel control + DaliSendData(DALI_102_ENABLE_DEVICE_TYPE_X, DALI_209_DEVICE_TYPE); // Enable Extended command + DaliSendData(adr, DALI_209_SET_TEMPORARY_RGBWAF_CONTROL); + + if (!DaliSetDTR(0, adr, cur_col[0])) { return true; } // DALI Red + if (!DaliSetDTR(1, adr, (channels > 1) ? cur_col[1] : 255)) { return true; } // DALI Green + if (!DaliSetDTR(2, adr, (channels > 2) ? cur_col[2] : 255)) { return true; } // DALI Blue + DaliSendData(DALI_102_ENABLE_DEVICE_TYPE_X, DALI_209_DEVICE_TYPE); // Enable Extended command + DaliSendData(adr, DALI_209_SET_TEMPORARY_RGB_DIMLEVEL); + + if (channels > 3) { + if (!DaliSetDTR(0, adr, cur_col[3])) { return true; } // DALI While + if (!DaliSetDTR(1, adr, (channels > 4) ? cur_col[4] : 255)) { return true; } // DALI Amber + if (!DaliSetDTR(2, adr, 255)) { return true; } // DALI Freecolour - no change + DaliSendData(DALI_102_ENABLE_DEVICE_TYPE_X, DALI_209_DEVICE_TYPE); // Enable Extended command + DaliSendData(adr, DALI_209_SET_TEMPORARY_WAF_DIMLEVEL); + } +#endif // DALI_LIGHT_NO_READ_AFTER_WRITE + DaliSendData(DALI_102_ENABLE_DEVICE_TYPE_X, DALI_209_DEVICE_TYPE); // Enable Extended command + DaliSendData(adr, DALI_209_ACTIVATE); + return true; + } #ifdef DALI_POWER_OFF_NO_FADE - if (!cur_col[0]) { - DaliSendData(adr | DALI_SELECTOR_BIT, DALI_102_OFF); // Power off without fade - return true; - } -#endif // DALI_POWER_OFF_NO_FADE - DaliSendData(adr, cur_col[0]); // DAPC command - dim level + if (!cur_col[0]) { + DaliSendData(adr | DALI_SELECTOR_BIT, DALI_102_OFF); // Power off without fade + return true; } +#endif // DALI_POWER_OFF_NO_FADE + DaliSendData(adr, cur_col[0]); // DAPC command - dim level } return true; } @@ -970,7 +982,6 @@ bool DaliInit(uint32_t function) { UpdateDevicesPresent(1); TasmotaGlobal.light_type = LT_W; // Single channel -#ifdef DALI_LIGHT_COLOR_SUPPORT Dali->target_rgbwaf = DaliQueryRGBWAF(DaliTarget2Address(Dali->Settings.target)); if (Dali->target_rgbwaf > 1) { TasmotaGlobal.light_type = Dali->Settings.light_type; @@ -980,7 +991,6 @@ bool DaliInit(uint32_t function) { UpdateDevicesPresent(1); // We manage RGB and W separately, hence adding a device } } -#endif // DALI_LIGHT_COLOR_SUPPORT return true; #else @@ -1090,9 +1100,7 @@ void CmndDaliTarget(void) { (XdrvMailbox.payload == 0)) { Dali->Settings.target = XdrvMailbox.payload; } -#ifdef DALI_LIGHT_COLOR_SUPPORT Dali->target_rgbwaf = DaliQueryRGBWAF(DaliTarget2Address(Dali->Settings.target)); -#endif // DALI_LIGHT_COLOR_SUPPORT ResponseCmndNumber(Dali->Settings.target); }