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 <http://www.gnu.org/licenses/>. + + -------------------------------------------------------------------------------------------- + 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 <http://www.gnu.org/licenses/>. - - -------------------------------------------------------------------------------------------- - 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