mirror of https://github.com/arendst/Tasmota.git
Fix Dali received data decoding
- Add support for DALI 1 on ESP8266
This commit is contained in:
parent
b5b11d6227
commit
0240da2bf7
|
@ -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
|
||||
|
||||
|
|
|
@ -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_ |
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
Loading…
Reference in New Issue