diff --git a/CHANGELOG.md b/CHANGELOG.md
index cd25deabf..e2b378aa8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,11 +6,12 @@ All notable changes to this project will be documented in this file.
## [14.2.0.6]
### Added
- Support for Sonoff SPM v1.3.0 (#13447)
-- LVGL port `colorwheel` from LVGL 8
-- HASPmota `cpicker` and `msgbox`
+- LVGL port `colorwheel` from LVGL 8 (#22244)
+- HASPmota `cpicker` and `msgbox` (#22244)
+- Support for DALI 1 on ESP8266
### Breaking Changed
-- HASPmota `delete` instead of `delete()`
+- HASPmota `delete` instead of `delete()` (#22245)
### Changed
- ESP32 platform update from 2024.09.10 to 2024.09.30 and Framework (Arduino Core) from v3.0.5 to v3.1.0.240926 (#22203)
@@ -20,6 +21,7 @@ All notable changes to this project will be documented in this file.
- HASPmota error when page '1' is not defined (#22220)
- ESP32-S3 uDisplay force cache writes to RGB display (#22222)
- ESP32 Dali compile error with core 3.x (#22214)
+- Dali received data decoding
### Removed
diff --git a/CODE_OWNERS.md b/CODE_OWNERS.md
index c6b33cdf6..f70f2b8ff 100644
--- a/CODE_OWNERS.md
+++ b/CODE_OWNERS.md
@@ -86,7 +86,7 @@ In addition to @arendst the following code is mainly owned by:
| xdrv_72_pipsolar | @chefpro
| xdrv_73_lora | @arendst
| xdrv_74 |
-| xdrv_75 |
+| xdrv_75_dali | @eeak, @arendst
| xdrv_76 |
| xdrv_77 |
| xdrv_78 |
@@ -98,7 +98,7 @@ In addition to @arendst the following code is mainly owned by:
| xdrv_86_esp32_sonoff_spm | @arendst
| xdrv_87_esp32_sonoff_tm1621 | @arendst
| xdrv_88_esp32_shelly_pro | @arendst
-| xdrv_89_esp32_dali | @eeak
+| xdrv_89_ |
| xdrv_90_esp32_dingtian_relay | @barbudor
| xdrv_91_ |
| xdrv_92_ |
diff --git a/RELEASENOTES.md b/RELEASENOTES.md
index d5c5a41c9..254ea05bf 100644
--- a/RELEASENOTES.md
+++ b/RELEASENOTES.md
@@ -121,6 +121,7 @@ The latter links can be used for OTA upgrades too like ``OtaUrl https://ota.tasm
- ESP8266 support for one-wire M1601 temperature sensor on DS18x20 GPIO [#21376](https://github.com/arendst/Tasmota/issues/21376)
- ESP8266 support for I2C CLK on GPIO16 [#22199](https://github.com/arendst/Tasmota/issues/22199)
- Support for I2C M5Unit (Mini)Scales using HX711 driver
+- Support for DALI 1 on ESP8266
- Support for RX8010 RTC as used in IOTTIMER [#21376](https://github.com/arendst/Tasmota/issues/21376)
- Support for BL0906 up to 6 channel energy monitor as used in Athom EM2/EM6 [#22167](https://github.com/arendst/Tasmota/issues/22167)
- Support for Sonoff SPM v1.3.0 [#13447](https://github.com/arendst/Tasmota/issues/13447)
@@ -136,6 +137,8 @@ The latter links can be used for OTA upgrades too like ``OtaUrl https://ota.tasm
- Berry Zigbee improvements to prepare Matter [#22083](https://github.com/arendst/Tasmota/issues/22083)
- Berry virtual Energy driver [#22134](https://github.com/arendst/Tasmota/issues/22134)
- Berry improve `int64` constructor [#22172](https://github.com/arendst/Tasmota/issues/22172)
+- LVGL port `colorwheel` from LVGL 8 [#22244](https://github.com/arendst/Tasmota/issues/22244)
+- HASPmota `cpicker` and `msgbox` [#22244](https://github.com/arendst/Tasmota/issues/22244)
- Matter support for Zigbee Temperature, Humidity and Pressure sensors [#22084](https://github.com/arendst/Tasmota/issues/22084)
- Matter support for Zigbee Occupancy and Light 0/1/2 (OnOff / Dimmer / White Color Temperature) [#22110](https://github.com/arendst/Tasmota/issues/22110)
@@ -165,6 +168,7 @@ The latter links can be used for OTA upgrades too like ``OtaUrl https://ota.tasm
- PZEM continue energy monitoring when one phase fails [#21968](https://github.com/arendst/Tasmota/issues/21968)
- BearSSL panic on ESP8266 in rare conditions [#22017](https://github.com/arendst/Tasmota/issues/22017)
- ModbusBridge request and response logic [#22075](https://github.com/arendst/Tasmota/issues/22075)
+- Dali received data decoding
- Autoconf prevent 'init.bat' from stopping on empty lines [#22158](https://github.com/arendst/Tasmota/issues/22158)
- Zigbee extend timeout for MCU reboot from 5s to 10s [#22009](https://github.com/arendst/Tasmota/issues/22009)
- Zigbee avoid disabling console serial on ESP32 and improved log messages [#22082](https://github.com/arendst/Tasmota/issues/22082)
diff --git a/tasmota/include/tasmota_template.h b/tasmota/include/tasmota_template.h
index 0bcb2adfd..59f108fe1 100644
--- a/tasmota/include/tasmota_template.h
+++ b/tasmota/include/tasmota_template.h
@@ -592,7 +592,7 @@ const uint16_t kGpioNiceList[] PROGMEM = {
* Protocol specifics
\*-------------------------------------------------------------------------------------------*/
-#if defined(USE_DALI) && defined(ESP32)
+#ifdef USE_DALI
AGPIO(GPIO_DALI_RX), // DALI RX
AGPIO(GPIO_DALI_TX), // DALI TX
#endif // USE_DALI
diff --git a/tasmota/my_user_config.h b/tasmota/my_user_config.h
index 21b9da449..e9d8128bb 100644
--- a/tasmota/my_user_config.h
+++ b/tasmota/my_user_config.h
@@ -1078,6 +1078,10 @@
//#define USE_FLOWRATEMETER // Add support for water flow meter YF-DN50 and similary (+1k7 code)
+// #define USE_DALI // Add support for DALI 1 bridge (+2k1 code)
+ #define DALI_IN_INVERT 0 // DALI RX inverted
+ #define DALI_OUT_INVERT 0 // DALI TX inverted
+
// -- Thermostat control ----------------------------
//#define USE_THERMOSTAT // Add support for Thermostat
#define THERMOSTAT_CONTROLLER_OUTPUTS 1 // Number of outputs to be controlled independently
@@ -1139,11 +1143,6 @@
#define USE_ESP32_SENSORS // Add support for ESP32 temperature and optional hall effect sensor
#define USE_GPIO_VIEWER // Enable GPIO Viewer to see realtime GPIO states (+5k6 code)
-// #define USE_DALI // Add support for DALI
- #define DALI_IN_INVERT 0 // DALI RX inverted ?
- #define DALI_OUT_INVERT 0 // DALI TX inverted ?
- #define DALI_TIMER 0 // ESP32 hardware timer number 0-3 !!! timer 3 used in xdrv_10_scripter.ino !!!
-
//#define USE_SONOFF_SPM // Add support for ESP32 based Sonoff Smart Stackable Power Meter (+11k code)
//#define USE_DISPLAY_TM1621_SONOFF // Add support for TM1621 dsiplay driver used by Sonoff POWR3xxD and THR3xxD
diff --git a/tasmota/tasmota_support/support_features.ino b/tasmota/tasmota_support/support_features.ino
index bc86f8edb..2d8104524 100644
--- a/tasmota/tasmota_support/support_features.ino
+++ b/tasmota/tasmota_support/support_features.ino
@@ -823,7 +823,7 @@ constexpr uint32_t feature[] = {
0x00000020 | // xdrv_88_esp32_shelly_pro.ino
#endif
#ifdef USE_DALI
- 0x00000040 | // xdrv_89_esp32_dali.ino
+ 0x00000040 | // xdrv_75_dali.ino
#endif
#if defined(USE_LIGHT) && defined(USE_BP1658CJ)
0x00000080 | // xlgt_10_bp1658cj.ino
diff --git a/tasmota/tasmota_xdrv_driver/xdrv_75_dali.ino b/tasmota/tasmota_xdrv_driver/xdrv_75_dali.ino
new file mode 100644
index 000000000..2614ffe0b
--- /dev/null
+++ b/tasmota/tasmota_xdrv_driver/xdrv_75_dali.ino
@@ -0,0 +1,411 @@
+/*
+ xdrv_75_dali.ino - DALI support for Tasmota
+
+ Copyright (C) 2022 Andrei Kazmirtsuk aka eeak and Theo Arends
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+
+ --------------------------------------------------------------------------------------------
+ Version yyyymmdd Action Description
+ --------------------------------------------------------------------------------------------
+ 0.1.0.0 20241006 rewrite - Add support for ESP8266
+ - Fix decoding of received Dali 1 data
+ - Refactor command `DaliPower 0..254` controlling Broadcast devices
+ - Add command `DaliDimmer 0..254` controlling Broadcast devices
+
+ 0.0.0.1 20221027 publish - Initial version
+*/
+
+#ifdef USE_DALI
+
+/*********************************************************************************************\
+ * DALI support for Tasmota
+\*********************************************************************************************/
+
+#define XDRV_75 75
+
+#ifndef DALI_IN_INVERT
+#define DALI_IN_INVERT 0 // DALI RX inverted ?
+#endif
+#ifndef DALI_OUT_INVERT
+#define DALI_OUT_INVERT 0 // DALI TX inverted ?
+#endif
+
+//#define DALI_DEBUG
+#ifndef DALI_DEBUG_PIN
+#define DALI_DEBUG_PIN 27
+#endif
+
+#define BROADCAST_DP 0b11111110 // 0xFE = 254
+
+#define DALI_TOPIC "DALI"
+// http and json defines
+#define D_NAME_DALI "DALI"
+#define D_PRFX_DALI "Dali"
+
+const char kDALICommands[] PROGMEM = D_PRFX_DALI "|" // Prefix
+ "|" D_CMND_POWER "|" D_CMND_DIMMER;
+
+void (* const DALICommand[])(void) PROGMEM = {
+ &CmndDali, &CmndDaliPower, &CmndDaliDimmer };
+
+struct DALI {
+ uint32_t bit_time;
+ uint16_t received_dali_data; // Data received from DALI bus
+ uint8_t pin_rx;
+ uint8_t pin_tx;
+ uint8_t dimmer;
+ bool power;
+ bool input_ready;
+} *Dali = nullptr;
+
+/*********************************************************************************************\
+ * DALI low level
+\*********************************************************************************************/
+
+void DaliEnableRxInterrupt(void) {
+ attachInterrupt(Dali->pin_rx, DaliReceiveData, FALLING);
+}
+
+void DaliDisableRxInterrupt(void) {
+ detachInterrupt(Dali->pin_rx);
+}
+
+/*************** R E C E I V E * P R O C E D U R E *********/
+
+#define DALI_WAIT_RCV { while (ESP.getCycleCount() < (wait + start)); wait += bit_time; }
+
+void IRAM_ATTR DaliReceiveData(void);
+void DaliReceiveData(void) {
+ if (Dali->input_ready) { return; }
+ uint32_t start = ESP.getCycleCount();
+ uint32_t bit_time = Dali->bit_time;
+ // Advance the starting point for the samples but compensate for the
+ // initial delay which occurs before the interrupt is delivered
+ uint32_t wait = bit_time / 2;
+ int bit_state = 0;
+ bool dali_read;
+ uint32_t received_dali_data = 0;
+
+ DALI_WAIT_RCV;
+ DALI_WAIT_RCV; // Start bit
+ for (uint32_t i = 0; i < 32; i++) {
+ DALI_WAIT_RCV;
+ if (abs(bit_state) <= 2) { // Manchester encoding max 2 consequtive equal bits
+ dali_read = digitalRead(Dali->pin_rx);
+#ifdef DALI_DEBUG
+ digitalWrite(DALI_DEBUG_PIN, i&1); // Add LogicAnalyzer poll indication
+#endif // DALI_DEBUG
+ bit_state += (dali_read) ? 1 : -1;
+ if (i &1) {
+ uint32_t j = i >>1;
+ received_dali_data |= ((DALI_IN_INVERT) ? !dali_read : dali_read << (15 -j));
+ }
+ }
+ }
+ DALI_WAIT_RCV;
+ DALI_WAIT_RCV; // Stop bit
+
+ if (abs(bit_state) <= 2) { // Valid Manchester encoding
+ Dali->received_dali_data = received_dali_data;
+ Dali->input_ready = true; // Valid data received
+ }
+
+#ifdef ESP8266
+ // Must clear this bit in the interrupt register,
+ // it gets set even when interrupts are disabled
+ GPIO_REG_WRITE(GPIO_STATUS_W1TC_ADDRESS, 1 << Dali->pin_rx);
+#endif // ESP8266
+}
+
+/*************** S E N D * P R O C E D U R E ***************/
+
+#define DALI_WAIT_SND { while (ESP.getCycleCount() < (wait + start)) optimistic_yield(1); wait += bit_time; } // Watchdog timeouts
+
+void DaliDigitalWrite(bool pin_value) {
+ digitalWrite(Dali->pin_tx, (pin_value == DALI_OUT_INVERT) ? LOW : HIGH);
+}
+
+void DaliSendData(uint8_t firstByte, uint8_t secondByte) {
+ if (BROADCAST_DP == firstByte) {
+ Dali->power = (secondByte); // State
+ Dali->dimmer = secondByte; // Value
+ }
+
+ uint16_t send_dali_data = firstByte << 8;
+ send_dali_data += secondByte & 0xff;
+
+ DaliDisableRxInterrupt();
+
+ uint32_t bit_time = Dali->bit_time;
+ uint32_t wait = bit_time;
+// digitalWrite(Dali->pin_tx, HIGH); // already in HIGH mode
+ uint32_t start = ESP.getCycleCount();
+
+ // Settling time between forward and backward frame
+ for (uint32_t i = 0; i < 8; i++) {
+ DALI_WAIT_SND;
+ }
+ // Start bit;
+ DaliDigitalWrite(LOW);
+ DALI_WAIT_SND;
+ DaliDigitalWrite(HIGH);
+ DALI_WAIT_SND;
+ for (uint32_t i = 0; i < 16; i++) {
+ // Bit value (edge) selection
+ bool bit_value = (bool)((send_dali_data >> (15 - i)) & 0x01); // MSB first
+ // Every half bit -> Manchester coding
+ DaliDigitalWrite(bit_value ? LOW : HIGH); // Manchester
+ DALI_WAIT_SND;
+ DaliDigitalWrite(bit_value ? HIGH : LOW); // Value
+ DALI_WAIT_SND;
+ }
+ // Stop bit
+ DaliDigitalWrite(HIGH);
+ delay(1);
+
+ DaliEnableRxInterrupt();
+}
+
+void DaliPower(uint8_t val) {
+ DaliSendData(BROADCAST_DP, val);
+}
+
+/***********************************************************/
+
+void DaliInput(void) {
+ if (Dali->input_ready) {
+ uint8_t DALIaddr = Dali->received_dali_data >> 8;
+ uint8_t DALIcmnd = Dali->received_dali_data;
+ if (BROADCAST_DP == DALIaddr) {
+ Dali->power = (DALIcmnd); // State
+ Dali->dimmer = DALIcmnd; // Value
+ }
+
+// AddLog(LOG_LEVEL_DEBUG, PSTR("DLI: Received 0x%04X"), Dali->received_dali_data);
+ Response_P(PSTR("{\"" D_NAME_DALI "\":{\"Power\":\"%s\",\"Dimmer\":%d,\"Address\":%d,\"Command\":%d}}"),
+ GetStateText(Dali->power), Dali->dimmer, DALIaddr, DALIcmnd);
+ MqttPublishPrefixTopicRulesProcess_P(RESULT_OR_TELE, PSTR(D_NAME_DALI));
+
+ Dali->input_ready = false;
+ }
+}
+
+void DaliPreInit(void) {
+ if (!PinUsed(GPIO_DALI_TX) || !PinUsed(GPIO_DALI_RX)) { return; }
+
+ Dali = (DALI*)calloc(sizeof(DALI), 1);
+ if (!Dali) { return; }
+
+ Dali->pin_rx = Pin(GPIO_DALI_RX);
+ Dali->pin_tx = Pin(GPIO_DALI_TX);
+
+ AddLog(LOG_LEVEL_INFO, PSTR("DLI: GPIO%d(RX) and GPIO%d(TX)"), Dali->pin_rx, Dali->pin_tx);
+
+ pinMode(Dali->pin_tx, OUTPUT);
+ digitalWrite(Dali->pin_tx, HIGH);
+ pinMode(Dali->pin_rx, INPUT);
+#ifdef DALI_DEBUG
+ pinMode(DALI_DEBUG_PIN, OUTPUT);
+ digitalWrite(DALI_DEBUG_PIN, HIGH);
+#endif // DALI_DEBUG
+
+ Dali->bit_time = ESP.getCpuFreqMHz() * 1000000 / 2400; // Manchester twice 1200 bps
+
+ DaliEnableRxInterrupt();
+}
+
+bool DaliMqtt(void) {
+/*
+ XdrvMailbox.topic = topic;
+ XdrvMailbox.index = strlen(topic);
+ XdrvMailbox.data = (char*)data;
+ XdrvMailbox.data_len = data_len;
+
+ This won't work as there is currently no subscribe done
+*/
+ char stopic[TOPSZ];
+ strncpy(stopic, XdrvMailbox.topic, TOPSZ);
+ XdrvMailbox.topic[TOPSZ - 1] = 0;
+
+ char *items[10];
+ char *p = stopic;
+ int cnt = 0;
+ do {
+ items[cnt] = strtok(p, "/");
+ cnt++;
+ p = nullptr;
+ } while (items[cnt - 1]);
+ cnt--; // represents the number of items
+
+ AddLog(LOG_LEVEL_DEBUG, PSTR("DLI: Cnt %d, Topic '%s', Payload '%s'"), cnt, XdrvMailbox.topic, XdrvMailbox.data);
+
+ if (cnt < 3) { // not for us?
+ AddLog(LOG_LEVEL_INFO, PSTR("DLI: Cnt %d < 3"), cnt);
+ return false;
+ }
+
+ int DALIindex = 0;
+ int ADRindex = 0;
+ int CMDindex = 0;
+ uint8_t DALIaddr = BROADCAST_DP;
+
+ if (strcasecmp_P(items[cnt - 3], PSTR(DALI_TOPIC)) != 0) { // dali
+ // cmnd
+ if (strcasecmp_P(items[cnt - 2], PSTR(DALI_TOPIC)) != 0) { // dali
+ // device
+ return false; // not for us
+ } else {
+ // cmnd/dali/percent
+ DALIindex = cnt - 2;
+ CMDindex = cnt - 1;
+ }
+ } else {
+ // dali/percent/2 20
+ DALIindex = cnt - 3;
+ CMDindex = cnt - 2;
+ ADRindex = cnt - 1;
+ DALIaddr = ((int)CharToFloat(items[ADRindex])) << 1;
+ }
+
+ uint8_t level;
+ uint8_t value = (uint8_t)CharToFloat(XdrvMailbox.data);
+ if (strcasecmp_P(items[CMDindex], PSTR("percent")) == 0) {
+ // dali/percent/
+ float percent = (float)(254 * value * 0.01);
+ level = (uint8_t)percent;
+ }
+ else if (strcasecmp_P(items[CMDindex], PSTR("level")) == 0) {
+ level = value;
+ }
+ else {
+ AddLog(LOG_LEVEL_INFO,PSTR("DLI: Command not recognized: %s"), items[CMDindex]);
+ return false; // not for us
+ }
+
+ AddLog(LOG_LEVEL_INFO,PSTR("DLI: Dali value %d on address %d"), value, DALIaddr);
+ DaliSendData(DALIaddr, level);
+
+ return true;
+}
+
+bool DaliJsonParse(void) {
+ // {"addr":254,"cmd":100}
+ // {"addr":2}
+ // {"dim":3}
+
+ bool served = false;
+ JsonParser parser((char *)XdrvMailbox.data);
+ JsonParserObject root = parser.getRootObject();
+ if (root) {
+ int DALIindex = 0;
+ int ADRindex = 0;
+ int8_t DALIdim = -1;
+ uint8_t DALIaddr = BROADCAST_DP;
+
+ JsonParserToken val = root[PSTR("cmd")];
+ if (val) {
+ uint8_t cmd = val.getUInt();
+ val = root[PSTR("addr")];
+ if (val) {
+ uint8_t addr = val.getUInt();
+ AddLog(LOG_LEVEL_DEBUG, PSTR("DLI: cmd = %d, addr = %d"), cmd, addr);
+ DaliSendData(addr, cmd);
+ return true;
+ } else {
+ return false;
+ }
+ }
+ val = root[PSTR("addr")];
+ if (val) {
+ uint8_t addr = val.getUInt();
+ if ((addr >= 0) && (addr < 64)) {
+ DALIaddr = addr << 1;
+ }
+ }
+ val = root[PSTR("dim")];
+ if (val) {
+ uint8_t dim = val.getUInt();
+ if (dim < 255) {
+ DALIdim = dim;
+ }
+ }
+ DaliSendData(DALIaddr, DALIdim);
+ served = true;
+ }
+ return served;
+}
+
+/*********************************************************************************************\
+ * Commands
+\*********************************************************************************************/
+
+void CmndDali(void) {
+ if (XdrvMailbox.data_len > 0) {
+ if (DaliJsonParse()) {
+ ResponseCmndDone();
+ }
+ }
+}
+
+void CmndDaliPower(void) {
+ if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 254)) {
+ DaliPower(XdrvMailbox.payload);
+ }
+ ResponseCmndStateText(Dali->power);
+}
+
+void CmndDaliDimmer(void) {
+ if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 254)) {
+ DaliPower(XdrvMailbox.payload);
+ }
+ ResponseCmndNumber(Dali->dimmer);
+}
+
+/*********************************************************************************************\
+ * Presentation
+\*********************************************************************************************/
+
+
+
+/*********************************************************************************************\
+ * Interface
+\*********************************************************************************************/
+
+bool Xdrv75(uint32_t function) {
+ bool result = false;
+
+ if (FUNC_INIT == function) {
+ DaliPreInit();
+ }
+ else if (Dali) {
+ switch (function) {
+ case FUNC_LOOP:
+ DaliInput();
+ break;
+ case FUNC_MQTT_DATA:
+ result = DaliMqtt();
+ break;
+ case FUNC_COMMAND:
+ result = DecodeCommand(kDALICommands, DALICommand);
+ break;
+ case FUNC_ACTIVE:
+ result = true;
+ break;
+ }
+ }
+ return result;
+}
+
+#endif // USE_DALI
diff --git a/tasmota/tasmota_xdrv_driver/xdrv_89_esp32_dali.ino b/tasmota/tasmota_xdrv_driver/xdrv_89_esp32_dali.ino
deleted file mode 100644
index a739f46bd..000000000
--- a/tasmota/tasmota_xdrv_driver/xdrv_89_esp32_dali.ino
+++ /dev/null
@@ -1,601 +0,0 @@
-/*
- xdrv_89_esp32_dali.ino - DALI support for Tasmota
-
- Copyright (C) 2022 Andrei Kazmirtsuk aka eeak
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program. If not, see .
-
- --------------------------------------------------------------------------------------------
- Version yyyymmdd Action Description
- --------------------------------------------------------------------------------------------
- 0.0.0.1 20221027 publish - initial version
-*/
-
-#ifdef ESP32
-#ifdef USE_DALI
-
-/*********************************************************************************************\
- * DALI support for Tasmota
-\*********************************************************************************************/
-
-#define XDRV_89 89
-
-#ifndef DALI_TIMER
- #define DALI_TIMER 0 // Default timer
-#endif
-
-#define BROADCAST_DP 0b11111110 // 0xFE
-#define DALI_TOPIC "DALI"
-
-enum
-{
- DALI_NO_ACTION,
- DALI_SENDING_DATA,
- DALI_RECEIVING_DATA,
- DALI_ERROR
-};
-
-// http and json defines
-#define D_NAME_DALI "DALI"
-
-const char S_JSON_DALI_COMMAND_NVALUE[] PROGMEM = "{\"" D_NAME_DALI "\":{\"%s\":%d}}";
-const char kDALI_Commands[] PROGMEM = D_CMND_DALI_POWER "|" D_CMND_DALI_DIMMER;
-
-enum DALI_Commands { // commands for Console
- CMND_DALI_PWR,
- CMND_DALI_DIM,
-};
-
-struct DALI {
- uint16_t send_dali_data; // data to send to DALI bus
- uint16_t received_dali_data; // data received from DALI bus
- uint8_t flag; // DALI status flag
- uint8_t bit_count; // nr of rec/send bits
- uint16_t tick_count; // nr of ticks of the timer
- bool former_val; // bit value in previous tick of timer
- hw_timer_t *timer; // hardware timer
-} *Dali = nullptr;
-
-
-
-/*********************************************************************************************\
- * DALI low level
-\*********************************************************************************************/
-
-/**
-* @brief This function handles hardware timer Handler.
-* @param None
-* @retval None
-*/
-void IRAM_ATTR DALI_Tick_Handler(void);
-void DALI_Tick_Handler(void)
-{
- if (getDaliFlag() == DALI_RECEIVING_DATA)
- {
- receive_tick();
- }
- else if (getDaliFlag() == DALI_SENDING_DATA)
- {
- send_tick();
- }
-}
-
-/**
-* @brief This function enable data transfer start interrupt.
-* @param None
-* @retval None
-*/
-void enableDaliRxInterrupt() {
- Dali->flag = DALI_NO_ACTION;
-// timerAlarmDisable(Dali->timer);
- timerStop(Dali->timer);
- attachInterrupt(Pin(GPIO_DALI_RX), receiveDaliData, FALLING);
-}
-
-/**
-* @brief This function disable data transfer start interrupt.
-* @param None
-* @retval None
-*/
-void disableRxInterrupt() {
-// timerAlarmEnable(Dali->timer);
- timerStart(Dali->timer);
- detachInterrupt(Pin(GPIO_DALI_RX));
-}
-
-/**
-* @brief receiving flag status
-* @param None
-* @retval uint8_t flag
-*/
-uint8_t getDaliFlag(void)
-{
- return Dali->flag;
-}
-
-/**
-* @brief DALI data received callback
-* @param None
-* @retval uint8_t flag
-*/
-void DataReceivedCallback() {
- AddLog(LOG_LEVEL_DEBUG, PSTR("DLI: Received: %d %d"), Dali->received_dali_data>>9, Dali->received_dali_data&0xff);
-}
-
-/*************** R E C E I V E * P R O C E D U R E S *******/
-
-/**
-* @brief receive data from DALI bus
-* @param None
-* @retval None
-*/
-void receiveDaliData()
-{
- // null variables
- Dali->received_dali_data = 0;
- Dali->bit_count = 0;
- Dali->tick_count = 0;
- Dali->former_val = true;
-
- Dali->flag = DALI_RECEIVING_DATA;
-
- disableRxInterrupt();
-}
-
-/**
-* @brief Get state of DALIIN pin
-* @param None
-* @retval bool status
-*/
-bool get_DALIIN(void)
-{
- bool dali_read = digitalRead(Pin(GPIO_DALI_RX));
- return (false == DALI_IN_INVERT) ? dali_read : !dali_read;
-}
-
-/**
-* @brief receiving data from DALI bus
-* @param None
-* @retval None
-*
-* |--------|----|---------------------------|----|
-* 0 24 32 160 176
-* wait start data stop
-*/
-void receive_tick(void)
-{
- // four ticks per bit
- bool actual_val = get_DALIIN();
- Dali->tick_count++;
-
- // edge detected
- if(actual_val != Dali->former_val)
- {
- switch(Dali->bit_count)
- {
- case 0:
- if (Dali->tick_count > 2)
- {
- Dali->tick_count = 0;
- Dali->bit_count = 1; // start bit
- }
- break;
- case 17: // 1st stop bit
- if(Dali->tick_count > 6) { // stop bit error, no edge should exist
- Dali->flag = DALI_ERROR;
- }
- break;
- default: // other bits
- if(Dali->tick_count > 6)
- {
- Dali->received_dali_data |= (actual_val << (16-Dali->bit_count));
- Dali->bit_count++;
- Dali->tick_count = 0;
- }
- break;
- }
- }else // voltage level stable
- {
- switch(Dali->bit_count)
- {
- case 0:
- if(Dali->tick_count==8) { // too long start bit
- Dali->flag = DALI_ERROR;
- }
- break;
- case 17:
- // First stop bit
- if (Dali->tick_count==8)
- {
- if (actual_val==0) // wrong level of stop bit
- {
- Dali->flag = DALI_ERROR;
- }
- else
- {
- Dali->bit_count++;
- Dali->tick_count = 0;
- }
- }
- break;
- case 18:
- // Second stop bit
- if (Dali->tick_count==8)
- {
- enableDaliRxInterrupt();
- DataReceivedCallback();
-
- }
- break;
- default: // normal bits
- if(Dali->tick_count==10)
- { // too long delay before edge
- Dali->flag = DALI_ERROR;
- }
- break;
- }
- }
- Dali->former_val = actual_val;
- if(getDaliFlag() == DALI_ERROR)
- {
- enableDaliRxInterrupt();
- }
-}
-
-
-/*************** S E N D * P R O C E D U R E S *************/
-
-/**
-* @brief Set value to the DALIOUT pin
-* @param bool
-* @retval None
-*/
-void set_DALIOUT(bool pin_value)
-{
- digitalWrite(Pin(GPIO_DALI_TX), pin_value == DALI_OUT_INVERT ? LOW : HIGH);
-}
-
-/**
-* @brief gets state of the DALIOUT pin
-* @param None
-* @retval bool state of the DALIOUT pin
-*/
-bool get_DALIOUT(void)
-{
- bool dali_read = digitalRead(Pin(GPIO_DALI_TX));
- return (false == DALI_OUT_INVERT) ? dali_read : !dali_read;
-}
-
-/**
-* @brief Send data to DALI bus
-* @param byteToSend
-* @retval None
-*/
-void sendDaliData(uint8_t firstByte, uint8_t secondByte)
-{
- Dali->send_dali_data = firstByte << 8;
- Dali->send_dali_data += secondByte & 0xff;
- Dali->bit_count = 0;
- Dali->tick_count = 0;
-
- Dali->flag = DALI_SENDING_DATA;
-
- disableRxInterrupt();
-}
-
-/**
-* @brief DALI protocol physical layer for slave device
-* @param None
-* @retval None
-*
-* |--------|----|---------------------------|----|
-* 0 24 32 160 176
-* wait start data stop
-*/
-void send_tick(void)
-{
- // access to the routine just every 4 ticks = every half bit
- if ((Dali->tick_count & 0x03) == 0)
- {
- if (Dali->tick_count < 160)
- {
- // settling time between forward and backward frame
- if (Dali->tick_count < 24)
- {
- Dali->tick_count++;
- return;
- }
-
- // start of the start bit
- if (Dali->tick_count == 24)
- {
- // GPIOB->ODR ^= GPIO_ODR_7;
- set_DALIOUT(false);
- Dali->tick_count++;
- return;
- }
-
- // edge of the start bit
- // 28 ticks = 28/9600 = 2,92ms = delay between forward and backward message frame
- if (Dali->tick_count == 28)
- {
- set_DALIOUT(true);
- Dali->tick_count++;
- return;
- }
-
- // bit value (edge) selection
- bool bit_value = (bool)((Dali->send_dali_data >> (15 - Dali->bit_count)) & 0x01);
-
- // Every half bit -> Manchester coding
- if (!((Dali->tick_count - 24) & 0x0007))
- { // div by 8
- if (get_DALIOUT() == bit_value) // former value of bit = new value of bit
- set_DALIOUT((bool)(1 - bit_value));
- }
-
- // Generate edge for actual bit
- if (!((Dali->tick_count - 28) & 0x0007))
- {
- set_DALIOUT(bit_value);
- Dali->bit_count++;
- }
- }
- else
- { // end of data byte, start of stop bits
- if (Dali->tick_count == 160)
- {
- set_DALIOUT(true); // start of stop bit
- }
-
- // end of stop bits, no settling time
- if (Dali->tick_count == 176)
- {
- enableDaliRxInterrupt();
- }
- }
- }
- Dali->tick_count++;
-
- return;
-}
-
-/***********************************************************/
-
-void DaliPreInit() {
- if (!PinUsed(GPIO_DALI_TX) || !PinUsed(GPIO_DALI_RX)) { return; }
- AddLog(LOG_LEVEL_INFO, PSTR("DLI: Init - RX-pin: %d, TX-pin: %d"), Pin(GPIO_DALI_RX), Pin(GPIO_DALI_TX));
- // pinMode(LED, OUTPUT);
- pinMode(Pin(GPIO_DALI_TX), OUTPUT);
- digitalWrite(Pin(GPIO_DALI_TX), HIGH);
- pinMode(Pin(GPIO_DALI_RX), INPUT);
-
- Dali = (DALI*)calloc(1,sizeof(DALI));
- if (!Dali) {
- AddLog(LOG_LEVEL_INFO, PSTR("DLI: Memory allocation error"));
- return;
- }
-// Arduino Core < 3
-// Dali->timer = timerBegin(DALI_TIMER, 13, true);
-// timerAttachInterrupt(Dali->timer, &DALI_Tick_Handler, true);
-// timerAlarmWrite(Dali->timer, 641, true);
-
-// Arduino Core > 3
- Dali->timer = timerBegin(6153846); // 80MHz / 13
- if (nullptr == Dali->timer) {
- AddLog(LOG_LEVEL_INFO, PSTR("DLI: No timer available"));
- free(Dali);
- Dali = nullptr;
- return;
- }
- timerAttachInterrupt(Dali->timer, &DALI_Tick_Handler);
- timerAlarm(Dali->timer, 641, true, 0);
-
- attachInterrupt(Pin(GPIO_DALI_RX), receiveDaliData, FALLING);
- enableDaliRxInterrupt();
-}
-
-void DaliPwr(uint8_t val){
- sendDaliData(BROADCAST_DP, val);
-}
-
-bool DaliCmd(void)
-{
- char command[CMDSZ];
- uint8_t name_len = strlen(D_NAME_DALI);
- if (!strncasecmp_P(XdrvMailbox.topic, PSTR(D_NAME_DALI), name_len))
- {
- uint32_t command_code = GetCommandCode(command, sizeof(command), XdrvMailbox.topic + name_len, kDALI_Commands);
- switch (command_code)
- {
- case CMND_DALI_PWR:
- if (XdrvMailbox.data_len)
- {
- if (254 >= XdrvMailbox.payload)
- {
- DaliPwr(XdrvMailbox.payload);
- }
- }
- Response_P(S_JSON_DALI_COMMAND_NVALUE, command, XdrvMailbox.payload);
- break;
- default:
- return false;
- }
- return true;
- }
- else
- {
- return false;
- }
-}
-
-
-bool DaliMqtt()
-{
- char stopic[TOPSZ];
- strncpy(stopic, XdrvMailbox.topic, TOPSZ);
- XdrvMailbox.topic[TOPSZ - 1] = 0;
-
- char *items[10];
- char *p = stopic;
- int cnt = 0;
- do
- {
- items[cnt] = strtok(p, "/");
- cnt++;
- p = nullptr;
- } while (items[cnt - 1]);
- cnt--; // repreents the number of items
-
- if (cnt < 3)
- { // not for us?
- AddLog(LOG_LEVEL_INFO,PSTR("cnt: %d < 3"), cnt);
- return false;
- }
-
- int DALIindex = 0;
- int ADRindex = 0;
- int CMDindex = 0;
- uint8_t DALIaddr = BROADCAST_DP;
- if (strcasecmp_P(items[cnt - 3], PSTR(DALI_TOPIC)) != 0)
- {
- if (strcasecmp_P(items[cnt - 2], PSTR(DALI_TOPIC)) != 0)
- {
- if (strcasecmp_P(items[cnt - 1], PSTR(DALI_TOPIC)) != 0)
- {
- return false; // not for us
- }
- else
- {
- if (true == DaliJsonParse()) { return true; }
- }
- }
- else
- {
- DALIindex = cnt - 2;
- CMDindex = cnt - 1;
- }
- }
- else
- {
- DALIindex = cnt - 3;
- CMDindex = cnt - 2;
- ADRindex = cnt - 1;
- DALIaddr = ((int)CharToFloat(items[ADRindex])) << 1;
-
- }
-
- uint8_t level;
- uint8_t value = (uint8_t)CharToFloat(XdrvMailbox.data);
- if (strcasecmp_P(items[CMDindex], PSTR("percent")) == 0) {
- float percent = (float)(254 * value * 0.01);
- level = (uint8_t)percent;
- }
- else if (strcasecmp_P(items[CMDindex], PSTR("level")) == 0) {
- level = value;
- }
- else {
- AddLog(LOG_LEVEL_INFO,PSTR("command not recognized: %s"), items[CMDindex]);
- return false; // not for us
- }
-
- AddLog(LOG_LEVEL_INFO,PSTR("Dali value %d on address %d"), value, DALIaddr);
- sendDaliData(DALIaddr, level);
-
- return true;
-}
-
-bool DaliJsonParse()
-{
- bool served = false;
- JsonParser parser((char *)XdrvMailbox.data);
- JsonParserObject root = parser.getRootObject();
- if (root)
- {
- int DALIindex = 0;
- int ADRindex = 0;
- int8_t DALIdim = -1;
- uint8_t DALIaddr = BROADCAST_DP;
-
- JsonParserToken val = root[PSTR("cmd")];
- if (val)
- {
- uint8_t cmd = val.getUInt();
- val = root[PSTR("addr")];
- if (val)
- {
- uint8_t addr = val.getUInt();
- AddLog(LOG_LEVEL_DEBUG, PSTR("DLI: cmd = %d, addr = %d"), cmd, addr);
- sendDaliData(addr, cmd);
- return true;
- }
- else
- {
- return false;
- }
- }
- val = root[PSTR("addr")];
- if (val)
- {
- uint8_t addr = val.getUInt();
- if ((addr >= 0) && (addr < 64))
- DALIaddr = addr << 1;
- }
- val = root[PSTR("dim")];
- if (val)
- {
- uint8_t dim = val.getUInt();
- if (dim < 255)
- DALIdim = dim;
- }
-
- sendDaliData(DALIaddr, DALIdim);
- served = true;
- }
-
- return served;
-}
-
-/*********************************************************************************************\
- * Interface
-\*********************************************************************************************/
-
-bool Xdrv89(uint32_t function)
-{
- bool result = false;
-
- if (FUNC_INIT == function)
- {
- DaliPreInit();
- }
- else if (Dali)
- {
- switch (function)
- {
- case FUNC_MQTT_DATA:
- result = DaliMqtt();
- break;
- case FUNC_COMMAND:
- result = DaliCmd();
- break;
- case FUNC_ACTIVE:
- result = true;
- break;
- }
- }
- return result;
-}
-
-#endif // USE_DALI
-#endif // ESP32
\ No newline at end of file