2024-10-06 20:57:59 +01:00
|
|
|
/*
|
|
|
|
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
|
|
|
|
--------------------------------------------------------------------------------------------
|
2024-10-14 11:27:01 +01:00
|
|
|
0.1.0.5 20241014 update - Add command `DaliSend [repeat]<address>,<command>`
|
|
|
|
- Add command `DaliQuery [repeat]<address>,<command>`
|
|
|
|
- Send frame twice (repeat) for DALI defined commands
|
|
|
|
- Add support for receiving backward frame
|
2024-10-13 11:03:31 +01:00
|
|
|
0.1.0.4 20241013 update - Fix intermittent bad send timing
|
2024-10-10 16:49:11 +01:00
|
|
|
0.1.0.3 20241010 update - Change DaliDimmer range from 0..254 to 0..100
|
2024-10-14 11:27:01 +01:00
|
|
|
- Add command `DaliWeb 0|1` to enable persistent Web light controls
|
2024-10-08 16:54:17 +01:00
|
|
|
0.1.0.2 20241008 update - Better receive error detection
|
2024-10-11 16:27:12 +01:00
|
|
|
0.1.0.1 20241007 update - To stablizie communication send DALI datagram twice like Busch-Jaeger does
|
2024-10-07 17:03:20 +01:00
|
|
|
- Change DaliPower 0..2 to act like Tasmota Power (Off, On, Toggle)
|
|
|
|
- Keep last Dimmer value as default power on
|
2024-10-06 20:57:59 +01:00
|
|
|
0.1.0.0 20241006 rewrite - Add support for ESP8266
|
2024-10-11 16:27:12 +01:00
|
|
|
- Fix decoding of received DALI data
|
2024-10-06 20:57:59 +01:00
|
|
|
- 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
|
2024-10-14 11:27:01 +01:00
|
|
|
#define DALI_IN_INVERT 0 // DALI RX inverted
|
2024-10-06 20:57:59 +01:00
|
|
|
#endif
|
|
|
|
#ifndef DALI_OUT_INVERT
|
2024-10-07 17:03:20 +01:00
|
|
|
#define DALI_OUT_INVERT 0 // DALI TX inverted
|
2024-10-06 20:57:59 +01:00
|
|
|
#endif
|
2024-10-07 17:08:58 +01:00
|
|
|
#ifndef DALI_INIT_STATE
|
|
|
|
#define DALI_INIT_STATE 50 // DALI init dimmer state 50/254
|
|
|
|
#endif
|
2024-10-14 11:27:01 +01:00
|
|
|
#ifndef DALI_TIMEOUT
|
|
|
|
#define DALI_TIMEOUT 50 // DALI backward frame receive timeout (ms)
|
|
|
|
#endif
|
2024-10-06 20:57:59 +01:00
|
|
|
|
|
|
|
//#define DALI_DEBUG
|
|
|
|
#ifndef DALI_DEBUG_PIN
|
2024-10-08 16:54:17 +01:00
|
|
|
#define DALI_DEBUG_PIN 4
|
2024-10-06 20:57:59 +01:00
|
|
|
#endif
|
|
|
|
|
2024-10-11 10:54:08 +01:00
|
|
|
#define DALI_BROADCAST_DP 0b11111110 // 0xFE = 254
|
2024-10-06 20:57:59 +01:00
|
|
|
|
|
|
|
#define DALI_TOPIC "DALI"
|
|
|
|
#define D_PRFX_DALI "Dali"
|
|
|
|
|
|
|
|
const char kDALICommands[] PROGMEM = D_PRFX_DALI "|" // Prefix
|
2024-10-11 10:54:08 +01:00
|
|
|
"|" D_CMND_POWER
|
|
|
|
#ifdef USE_LIGHT
|
|
|
|
"|Web"
|
|
|
|
#endif // USE_LIGHT
|
2024-10-14 11:27:01 +01:00
|
|
|
"|" D_CMND_DIMMER "|Send|Query" ;
|
2024-10-06 20:57:59 +01:00
|
|
|
|
|
|
|
void (* const DALICommand[])(void) PROGMEM = {
|
2024-10-11 10:54:08 +01:00
|
|
|
&CmndDali, &CmndDaliPower,
|
|
|
|
#ifdef USE_LIGHT
|
|
|
|
&CmndDaliWeb,
|
|
|
|
#endif // USE_LIGHT
|
2024-10-14 11:27:01 +01:00
|
|
|
&CmndDaliDimmer, &CmndDaliSend, &CmndDaliQuery };
|
2024-10-06 20:57:59 +01:00
|
|
|
|
|
|
|
struct DALI {
|
|
|
|
uint32_t bit_time;
|
2024-10-07 17:03:20 +01:00
|
|
|
uint16_t received_dali_data; // Data received from DALI bus
|
2024-10-06 20:57:59 +01:00
|
|
|
uint8_t pin_rx;
|
|
|
|
uint8_t pin_tx;
|
2024-10-06 21:40:28 +01:00
|
|
|
uint8_t address;
|
|
|
|
uint8_t command;
|
2024-10-06 20:57:59 +01:00
|
|
|
uint8_t dimmer;
|
|
|
|
bool power;
|
2024-10-11 10:54:08 +01:00
|
|
|
bool available;
|
2024-10-14 11:27:01 +01:00
|
|
|
bool response;
|
2024-10-06 20:57:59 +01:00
|
|
|
} *Dali = nullptr;
|
|
|
|
|
|
|
|
/*********************************************************************************************\
|
|
|
|
* DALI low level
|
|
|
|
\*********************************************************************************************/
|
|
|
|
|
|
|
|
void DaliEnableRxInterrupt(void) {
|
2024-10-14 11:27:01 +01:00
|
|
|
Dali->available = false;
|
2024-10-06 20:57:59 +01:00
|
|
|
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 *********/
|
|
|
|
|
2024-10-08 16:54:17 +01:00
|
|
|
void IRAM_ATTR DaliReceiveData(void); // Fix ESP8266 ISR not in IRAM! exception
|
2024-10-06 20:57:59 +01:00
|
|
|
void DaliReceiveData(void) {
|
2024-10-14 11:27:01 +01:00
|
|
|
/*
|
|
|
|
Forward frame (1 Start bit + 16 data bits) * 2 bits/bit (manchester encoding) + 2 * 2 Stop bits = 38 bits
|
|
|
|
DALI data 0xFE64 1 1 1 1 1 1 1 0 0 1 1 0 0 1 0 0 Forward frame
|
|
|
|
Start and Stop bits 1 1 1
|
|
|
|
Manchester data 0101010101010101101001011010011010
|
|
|
|
Stop bits 1111
|
|
|
|
|
|
|
|
Backward frame (1 Start bit + 8 data bits) * 2 bits/bit (manchester encoding) + 2 * 2 Stop bits = 22 bits
|
|
|
|
DALI data 0x64 0 1 1 0 0 1 0 0 Backward frame
|
|
|
|
Start and Stop bits 1 1 1
|
|
|
|
Manchester data 011001011010011010
|
|
|
|
Stop bits 1111
|
|
|
|
|
|
|
|
Bit number 01234567890123456789012345678901234567
|
|
|
|
1 2 3
|
|
|
|
*/
|
2024-10-11 10:54:08 +01:00
|
|
|
if (Dali->available) { return; } // Skip if last input is not yet handled
|
2024-10-08 16:54:17 +01:00
|
|
|
uint32_t wait = ESP.getCycleCount() + (Dali->bit_time / 2);
|
2024-10-06 20:57:59 +01:00
|
|
|
int bit_state = 0;
|
|
|
|
bool dali_read;
|
|
|
|
uint32_t received_dali_data = 0;
|
2024-10-14 11:27:01 +01:00
|
|
|
uint32_t bit_number = 0;
|
|
|
|
while (bit_number < 38) {
|
2024-10-08 16:54:17 +01:00
|
|
|
while (ESP.getCycleCount() < wait);
|
|
|
|
wait += Dali->bit_time; // Auto roll-over
|
2024-10-14 11:27:01 +01:00
|
|
|
dali_read = digitalRead(Dali->pin_rx);
|
2024-10-06 20:57:59 +01:00
|
|
|
#ifdef DALI_DEBUG
|
2024-10-14 11:27:01 +01:00
|
|
|
digitalWrite(DALI_DEBUG_PIN, bit_number&1); // Add LogicAnalyzer poll indication
|
2024-10-06 20:57:59 +01:00
|
|
|
#endif // DALI_DEBUG
|
2024-10-14 11:27:01 +01:00
|
|
|
if (bit_number < 34) { // 34 manchester encoded bits
|
2024-10-06 20:57:59 +01:00
|
|
|
bit_state += (dali_read) ? 1 : -1;
|
2024-10-14 11:27:01 +01:00
|
|
|
if (0 == bit_state) { // Manchester encoding total 2 bits is always 0
|
|
|
|
if (bit_number > 2) { // Skip start bit
|
|
|
|
received_dali_data <<= 1;
|
|
|
|
received_dali_data |= (DALI_IN_INVERT) ? !dali_read : dali_read;
|
2024-10-08 16:54:17 +01:00
|
|
|
}
|
2024-10-06 20:57:59 +01:00
|
|
|
}
|
2024-10-14 11:27:01 +01:00
|
|
|
else if ((2 == bit_state) &&
|
|
|
|
(bit_number == 19)) { // Possible backward frame detected - Chk stop bits
|
|
|
|
bit_state = 0;
|
|
|
|
bit_number = 35;
|
|
|
|
}
|
|
|
|
else if (abs(bit_state) > 1) { // Invalid manchester data
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
} else { // 4 high Stop bits
|
|
|
|
if (bit_state != 0) { // Invalid manchester data
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
else if (dali_read != 1) { // Invalid level of stop bit
|
|
|
|
bit_state = 1;
|
|
|
|
break;
|
|
|
|
}
|
2024-10-06 20:57:59 +01:00
|
|
|
}
|
2024-10-14 11:27:01 +01:00
|
|
|
bit_number++;
|
2024-10-06 20:57:59 +01:00
|
|
|
}
|
2024-10-14 11:27:01 +01:00
|
|
|
if (0 == bit_state) { // Valid Manchester encoding including start and stop bits
|
|
|
|
if (Dali->response || // Response from last message send
|
|
|
|
(Dali->received_dali_data != received_dali_data)) { // Skip duplicates
|
2024-10-08 16:54:17 +01:00
|
|
|
Dali->received_dali_data = received_dali_data;
|
2024-10-11 10:54:08 +01:00
|
|
|
Dali->available = true; // Valid data received
|
2024-10-08 16:54:17 +01:00
|
|
|
}
|
2024-10-06 20:57:59 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*************** S E N D * P R O C E D U R E ***************/
|
|
|
|
|
2024-10-07 17:03:20 +01:00
|
|
|
void DaliSendDataOnce(uint16_t send_dali_data) {
|
2024-10-14 11:27:01 +01:00
|
|
|
/*
|
|
|
|
DALI protocol forward frame
|
|
|
|
DALI data 0xFE64 1 1 1 1 1 1 1 0 0 1 1 0 0 1 0 0
|
|
|
|
Start and Stop bits 1 1 1
|
|
|
|
Manchester data 0101010101010101101001011010011010
|
|
|
|
Stop bits 1111
|
|
|
|
|
|
|
|
Bit number 01234567890123456789012345678901234567
|
|
|
|
1 2 3
|
|
|
|
*/
|
2024-10-08 16:54:17 +01:00
|
|
|
bool bit_value;
|
|
|
|
uint32_t bit_pos = 15;
|
|
|
|
uint32_t wait = ESP.getCycleCount();
|
2024-10-14 11:27:01 +01:00
|
|
|
for (uint32_t i = 0; i < 35; i++) { // 417 * 35 = 14.7 ms
|
2024-10-08 16:54:17 +01:00
|
|
|
if (0 == (i &1)) { // Even bit
|
|
|
|
// Start bit, Stop bit, Data bits
|
|
|
|
bit_value = (0 == i) ? 1 : (34 == i) ? 0 : (bool)((send_dali_data >> bit_pos--) &1); // MSB first
|
|
|
|
} else { // Odd bit
|
|
|
|
bit_value = !bit_value; // Complement bit
|
|
|
|
}
|
|
|
|
bool pin_value = bit_value ? LOW : HIGH; // Invert bit
|
|
|
|
digitalWrite(Dali->pin_tx, (pin_value == DALI_OUT_INVERT) ? LOW : HIGH);
|
|
|
|
wait += Dali->bit_time; // Auto roll-over
|
2024-10-13 11:03:31 +01:00
|
|
|
while (ESP.getCycleCount() < wait);
|
2024-10-06 20:57:59 +01:00
|
|
|
}
|
2024-10-14 11:27:01 +01:00
|
|
|
// delayMicroseconds(1100); // Adds to total 15.8 ms
|
2024-10-07 17:03:20 +01:00
|
|
|
}
|
2024-10-06 20:57:59 +01:00
|
|
|
|
2024-10-14 11:27:01 +01:00
|
|
|
void DaliSendData(uint32_t adr, uint32_t cmd) {
|
|
|
|
bool repeat = (adr &0x100); // Set repeat if bit 8 is set
|
|
|
|
adr &= 0xFF;
|
|
|
|
cmd &= 0xFF;
|
|
|
|
|
|
|
|
Dali->address = adr;
|
|
|
|
Dali->command = cmd;
|
|
|
|
if (DALI_BROADCAST_DP == adr) {
|
|
|
|
repeat = true;
|
|
|
|
Dali->power = (cmd); // State
|
2024-10-07 17:03:20 +01:00
|
|
|
if (Dali->power) {
|
2024-10-14 11:27:01 +01:00
|
|
|
Dali->dimmer = cmd; // Value
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!repeat && (adr &0x01)) { // YAAAAAA1 Commands where user didn't set repeat
|
|
|
|
if ((adr >= 0xA1) && (adr <= 0xFD)) { // Special commands
|
|
|
|
repeat = ((0xA5 == adr) || (0xA7 == adr));
|
|
|
|
} else {
|
|
|
|
// ((cmd >=0) && (cmd <= 31)) // Arc power control commands
|
|
|
|
repeat = (((cmd >=32) && (cmd <= 143)) || // Configuration commands
|
|
|
|
((cmd >=224) && (cmd <= 236))); // Extended configuration commands
|
|
|
|
// ((cmd >=144) && (cmd <= 223)) // Query commands
|
|
|
|
// ((cmd >=237) && (cmd <= 255)) // Extended query commands
|
2024-10-07 17:03:20 +01:00
|
|
|
}
|
|
|
|
}
|
2024-10-14 11:27:01 +01:00
|
|
|
|
|
|
|
uint16_t send_dali_data = adr << 8 | cmd;
|
2024-10-07 17:03:20 +01:00
|
|
|
|
|
|
|
DaliDisableRxInterrupt();
|
2024-10-08 16:54:17 +01:00
|
|
|
delay(3); // Settling time between forward and backward frame
|
2024-10-14 11:27:01 +01:00
|
|
|
DaliSendDataOnce(send_dali_data); // Takes 14.7 ms
|
|
|
|
if (repeat) {
|
2024-10-13 11:03:31 +01:00
|
|
|
delay(14); // As used by Busch-Jaeger and suggested by DALI protocol (> 9.17 ms)
|
|
|
|
DaliSendDataOnce(send_dali_data); // Takes 14.7 ms
|
|
|
|
}
|
2024-10-14 11:27:01 +01:00
|
|
|
delay(2); // Block response
|
2024-10-06 20:57:59 +01:00
|
|
|
DaliEnableRxInterrupt();
|
|
|
|
}
|
|
|
|
|
2024-10-14 11:27:01 +01:00
|
|
|
int DaliSendWaitResponse(uint32_t adr, uint32_t cmd, uint32_t timeout = DALI_TIMEOUT);
|
|
|
|
int DaliSendWaitResponse(uint32_t adr, uint32_t cmd, uint32_t timeout) {
|
|
|
|
Dali->response = true;
|
|
|
|
DaliSendData(adr, cmd);
|
|
|
|
while (!Dali->available && timeout--) { // Expect backward frame within DALI_TIMEOUT ms
|
|
|
|
delay(1);
|
|
|
|
};
|
|
|
|
int result = -1; // DALI NO or no response
|
|
|
|
if (Dali->available) {
|
|
|
|
Dali->available = false;
|
|
|
|
result = Dali->received_dali_data;
|
|
|
|
}
|
|
|
|
Dali->response = false;
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
void DaliPower(uint32_t val) {
|
2024-10-11 10:54:08 +01:00
|
|
|
DaliSendData(DALI_BROADCAST_DP, val);
|
2024-10-06 20:57:59 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/***********************************************************/
|
|
|
|
|
2024-10-06 21:40:28 +01:00
|
|
|
void ResponseAppendDali(void) {
|
2024-10-10 16:49:11 +01:00
|
|
|
uint8_t dimmer = changeUIntScale(Dali->dimmer, 0, 254, 0, 100);
|
2024-10-14 11:27:01 +01:00
|
|
|
ResponseAppend_P(PSTR("\"DALI\":{\"Power\":\"%s\",\"Dimmer\":%d,\"Address\":%d,\"Command\":%d}"),
|
2024-10-10 16:49:11 +01:00
|
|
|
GetStateText(Dali->power), dimmer, Dali->address, Dali->command);
|
2024-10-06 21:40:28 +01:00
|
|
|
}
|
|
|
|
|
2024-10-07 17:03:20 +01:00
|
|
|
void ResponseDali(void) {
|
|
|
|
Response_P(PSTR("{"));
|
|
|
|
ResponseAppendDali();
|
|
|
|
ResponseJsonEnd();
|
|
|
|
}
|
|
|
|
|
2024-10-06 20:57:59 +01:00
|
|
|
void DaliInput(void) {
|
2024-10-14 11:27:01 +01:00
|
|
|
if (!Dali->available || Dali->response) { return; }
|
|
|
|
|
|
|
|
Dali->address = Dali->received_dali_data >> 8;
|
|
|
|
Dali->command = Dali->received_dali_data;
|
2024-10-10 17:16:32 +01:00
|
|
|
|
2024-10-11 10:54:08 +01:00
|
|
|
#ifdef USE_LIGHT
|
2024-10-14 11:27:01 +01:00
|
|
|
bool show_response = true;
|
|
|
|
if (DALI_BROADCAST_DP == Dali->address) {
|
|
|
|
uint8_t dimmer_old = changeUIntScale(Dali->dimmer, 0, 254, 0, 100);
|
|
|
|
uint8_t power_old = Dali->power;
|
|
|
|
Dali->power = (Dali->command); // State
|
|
|
|
if (Dali->power) {
|
|
|
|
Dali->dimmer = Dali->command; // Value
|
2024-10-10 17:04:27 +01:00
|
|
|
}
|
2024-10-14 11:27:01 +01:00
|
|
|
if (Settings->sbflag1.dali_web) { // DaliWeb 1
|
|
|
|
uint8_t dimmer_new = changeUIntScale(Dali->dimmer, 0, 254, 0, 100);
|
|
|
|
if (power_old != Dali->power) {
|
|
|
|
ExecuteCommandPower(LightDevice(), Dali->power, SRC_SWITCH);
|
|
|
|
}
|
|
|
|
else if (dimmer_old != dimmer_new) {
|
|
|
|
char scmnd[20];
|
|
|
|
snprintf_P(scmnd, sizeof(scmnd), PSTR(D_CMND_DIMMER " %d"), dimmer_new);
|
|
|
|
ExecuteCommand(scmnd, SRC_SWITCH);
|
2024-10-11 10:54:08 +01:00
|
|
|
}
|
2024-10-14 11:27:01 +01:00
|
|
|
show_response = false;
|
2024-10-11 10:54:08 +01:00
|
|
|
}
|
2024-10-14 11:27:01 +01:00
|
|
|
}
|
|
|
|
if (show_response) {
|
2024-10-11 10:54:08 +01:00
|
|
|
ResponseDali();
|
|
|
|
MqttPublishPrefixTopicRulesProcess_P(RESULT_OR_TELE, PSTR(D_PRFX_DALI));
|
2024-10-14 11:27:01 +01:00
|
|
|
}
|
|
|
|
#else
|
|
|
|
if (DALI_BROADCAST_DP == Dali->address) {
|
|
|
|
Dali->power = (Dali->command); // State
|
|
|
|
if (Dali->power) {
|
|
|
|
Dali->dimmer = Dali->command; // Value
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ResponseDali();
|
|
|
|
MqttPublishPrefixTopicRulesProcess_P(RESULT_OR_TELE, PSTR(D_PRFX_DALI));
|
2024-10-11 10:54:08 +01:00
|
|
|
#endif // USE_LIGHT
|
|
|
|
|
2024-10-14 11:27:01 +01:00
|
|
|
Dali->available = false;
|
2024-10-06 20:57:59 +01:00
|
|
|
}
|
|
|
|
|
2024-10-10 16:49:11 +01:00
|
|
|
bool DaliInit(void) {
|
|
|
|
if (!PinUsed(GPIO_DALI_TX) || !PinUsed(GPIO_DALI_RX)) { return false; }
|
2024-10-06 20:57:59 +01:00
|
|
|
|
|
|
|
Dali = (DALI*)calloc(sizeof(DALI), 1);
|
2024-10-10 16:49:11 +01:00
|
|
|
if (!Dali) { return false; }
|
2024-10-06 20:57:59 +01:00
|
|
|
|
|
|
|
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
|
|
|
|
|
2024-10-07 17:08:58 +01:00
|
|
|
Dali->dimmer = DALI_INIT_STATE;
|
2024-10-14 11:27:01 +01:00
|
|
|
// Manchester twice 1200 bps = 2400 bps = 417 (protocol 416.76 +/- 10%) us
|
|
|
|
Dali->bit_time = ESP.getCpuFreqMHz() * 1000000 / 2400;
|
2024-10-06 20:57:59 +01:00
|
|
|
|
|
|
|
DaliEnableRxInterrupt();
|
2024-10-10 16:49:11 +01:00
|
|
|
|
2024-10-11 10:54:08 +01:00
|
|
|
#ifdef USE_LIGHT
|
2024-10-10 16:49:11 +01:00
|
|
|
if (!Settings->sbflag1.dali_web) { // DaliWeb 0
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
UpdateDevicesPresent(1);
|
|
|
|
TasmotaGlobal.light_type = LT_SERIAL1; // Single channel
|
|
|
|
return true;
|
2024-10-11 10:54:08 +01:00
|
|
|
#else
|
|
|
|
return false;
|
|
|
|
#endif // USE_LIGHT
|
2024-10-10 16:49:11 +01:00
|
|
|
}
|
|
|
|
|
2024-10-11 10:54:08 +01:00
|
|
|
#ifdef USE_LIGHT
|
2024-10-10 16:49:11 +01:00
|
|
|
bool DaliSetChannels(void) {
|
|
|
|
if (Settings->sbflag1.dali_web) { // DaliWeb 1
|
|
|
|
uint8_t value = ((uint8_t*)XdrvMailbox.data)[0];
|
|
|
|
if (255 == value) { value = 254; } // Max Dali value
|
|
|
|
DaliPower(value);
|
|
|
|
}
|
|
|
|
return true;
|
2024-10-06 20:57:59 +01:00
|
|
|
}
|
2024-10-11 10:54:08 +01:00
|
|
|
#endif // USE_LIGHT
|
2024-10-06 20:57:59 +01:00
|
|
|
|
2024-10-06 21:40:28 +01:00
|
|
|
/*********************************************************************************************\
|
|
|
|
* Experimental - Not functioning
|
|
|
|
\*********************************************************************************************/
|
|
|
|
|
2024-10-06 20:57:59 +01:00
|
|
|
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;
|
2024-10-11 10:54:08 +01:00
|
|
|
uint8_t DALIaddr = DALI_BROADCAST_DP;
|
2024-10-06 20:57:59 +01:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2024-10-06 21:40:28 +01:00
|
|
|
/*********************************************************************************************\
|
|
|
|
* Commands
|
|
|
|
\*********************************************************************************************/
|
|
|
|
|
2024-10-06 20:57:59 +01:00
|
|
|
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;
|
2024-10-14 11:27:01 +01:00
|
|
|
uint32_t DALIaddr = DALI_BROADCAST_DP;
|
2024-10-06 20:57:59 +01:00
|
|
|
|
|
|
|
JsonParserToken val = root[PSTR("cmd")];
|
|
|
|
if (val) {
|
2024-10-14 11:27:01 +01:00
|
|
|
uint32_t cmd = val.getUInt();
|
2024-10-06 20:57:59 +01:00
|
|
|
val = root[PSTR("addr")];
|
|
|
|
if (val) {
|
2024-10-14 11:27:01 +01:00
|
|
|
uint32_t addr = val.getUInt();
|
2024-10-06 20:57:59 +01:00
|
|
|
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) {
|
2024-10-14 11:27:01 +01:00
|
|
|
uint32_t addr = val.getUInt();
|
2024-10-06 20:57:59 +01:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
void CmndDali(void) {
|
2024-10-07 17:03:20 +01:00
|
|
|
// Dali {"addr":254,"cmd":100} - Any address and/or command
|
2024-10-06 20:57:59 +01:00
|
|
|
if (XdrvMailbox.data_len > 0) {
|
2024-10-07 17:03:20 +01:00
|
|
|
DaliJsonParse();
|
2024-10-06 20:57:59 +01:00
|
|
|
}
|
2024-10-07 17:03:20 +01:00
|
|
|
ResponseDali();
|
2024-10-06 20:57:59 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void CmndDaliPower(void) {
|
2024-10-07 17:03:20 +01:00
|
|
|
// DaliPower 0 - Power off
|
|
|
|
// DaliPower 1 - Power on to last dimmer state
|
|
|
|
// DaliPower 2 - Toggle power off or last dimmer state
|
|
|
|
// DaliPower 3..254 - Equals DaliDimmer command
|
2024-10-06 20:57:59 +01:00
|
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 254)) {
|
2024-10-07 17:03:20 +01:00
|
|
|
if (XdrvMailbox.payload <= 2) {
|
|
|
|
if (2 == XdrvMailbox.payload) {
|
|
|
|
XdrvMailbox.payload = (Dali->power) ? 0 : 1;
|
|
|
|
}
|
|
|
|
if (1 == XdrvMailbox.payload) {
|
|
|
|
XdrvMailbox.payload = Dali->dimmer;
|
|
|
|
}
|
|
|
|
}
|
2024-10-06 20:57:59 +01:00
|
|
|
DaliPower(XdrvMailbox.payload);
|
|
|
|
}
|
2024-10-07 17:03:20 +01:00
|
|
|
ResponseDali();
|
2024-10-06 20:57:59 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void CmndDaliDimmer(void) {
|
2024-10-10 16:49:11 +01:00
|
|
|
// DaliDimmer 0..100 - Set power off or dimmer state
|
|
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 100)) {
|
|
|
|
uint8_t dimmer = changeUIntScale(XdrvMailbox.payload, 0, 100, 0, 254);
|
|
|
|
DaliPower(dimmer);
|
2024-10-06 20:57:59 +01:00
|
|
|
}
|
2024-10-07 17:03:20 +01:00
|
|
|
ResponseDali();
|
2024-10-06 20:57:59 +01:00
|
|
|
}
|
|
|
|
|
2024-10-14 11:27:01 +01:00
|
|
|
void CmndDaliSend(void) {
|
|
|
|
// Send command
|
|
|
|
// Setting bit 8 will repeat command twice
|
|
|
|
// DaliSend 0x1a5,255 - DALI Initialise (send twice)
|
|
|
|
uint32_t values[2] = { 0 };
|
|
|
|
uint32_t params = ParseParameters(2, values);
|
|
|
|
if (2 == params) {
|
|
|
|
DaliSendData(values[0] &0x1FF, values[1] &0xFF);
|
|
|
|
ResponseCmndDone();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void CmndDaliQuery(void) {
|
|
|
|
// Send command and return response or -1 (no response within DALI_TIMEOUT)
|
|
|
|
// Setting bit 8 will repeat command twice
|
|
|
|
// DaliQuery 0xff,0x90 - DALI Query status
|
|
|
|
// DaliQuery 0xff,144 - DALI Query status
|
|
|
|
uint32_t values[2] = { 0 };
|
|
|
|
uint32_t params = ParseParameters(2, values);
|
|
|
|
if (2 == params) {
|
|
|
|
int result = DaliSendWaitResponse(values[0] &0x1FF, values[1] &0xFF);
|
|
|
|
ResponseCmndNumber(result);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-10-11 10:54:08 +01:00
|
|
|
#ifdef USE_LIGHT
|
2024-10-10 16:49:11 +01:00
|
|
|
void CmndDaliWeb(void) {
|
|
|
|
// DaliWeb 0 - Disable GUI light controls
|
|
|
|
// DaliWeb 1 - Enable GUI light controls
|
|
|
|
if (XdrvMailbox.data_len > 0) {
|
|
|
|
Settings->sbflag1.dali_web = XdrvMailbox.payload &1;
|
|
|
|
TasmotaGlobal.restart_flag = 2;
|
|
|
|
}
|
|
|
|
ResponseCmndStateText(Settings->sbflag1.dali_web);
|
|
|
|
}
|
2024-10-11 10:54:08 +01:00
|
|
|
#endif // USE_LIGHT
|
2024-10-10 16:49:11 +01:00
|
|
|
|
2024-10-06 20:57:59 +01:00
|
|
|
/*********************************************************************************************\
|
|
|
|
* Presentation
|
|
|
|
\*********************************************************************************************/
|
|
|
|
|
2024-10-06 21:40:28 +01:00
|
|
|
void DaliShow(bool json) {
|
|
|
|
if (json) {
|
|
|
|
ResponseAppend_P(PSTR(","));
|
|
|
|
ResponseAppendDali();
|
|
|
|
}
|
|
|
|
}
|
2024-10-06 20:57:59 +01:00
|
|
|
|
|
|
|
/*********************************************************************************************\
|
|
|
|
* Interface
|
|
|
|
\*********************************************************************************************/
|
|
|
|
|
|
|
|
bool Xdrv75(uint32_t function) {
|
|
|
|
bool result = false;
|
|
|
|
|
2024-10-10 16:49:11 +01:00
|
|
|
if (FUNC_MODULE_INIT == function) {
|
|
|
|
result = DaliInit();
|
2024-10-06 20:57:59 +01:00
|
|
|
}
|
|
|
|
else if (Dali) {
|
|
|
|
switch (function) {
|
|
|
|
case FUNC_LOOP:
|
|
|
|
DaliInput();
|
|
|
|
break;
|
|
|
|
case FUNC_MQTT_DATA:
|
|
|
|
result = DaliMqtt();
|
|
|
|
break;
|
2024-10-11 10:54:08 +01:00
|
|
|
#ifdef USE_LIGHT
|
2024-10-10 16:49:11 +01:00
|
|
|
case FUNC_SET_CHANNELS:
|
|
|
|
result = DaliSetChannels();
|
|
|
|
break;
|
2024-10-11 10:54:08 +01:00
|
|
|
#endif // USE_LIGHT
|
2024-10-06 21:40:28 +01:00
|
|
|
case FUNC_JSON_APPEND:
|
|
|
|
DaliShow(true);
|
|
|
|
break;
|
|
|
|
#ifdef USE_WEBSERVER
|
|
|
|
case FUNC_WEB_SENSOR:
|
|
|
|
DaliShow(false);
|
|
|
|
break;
|
|
|
|
#endif // USE_WEBSERVER
|
2024-10-06 20:57:59 +01:00
|
|
|
case FUNC_COMMAND:
|
|
|
|
result = DecodeCommand(kDALICommands, DALICommand);
|
|
|
|
break;
|
|
|
|
case FUNC_ACTIVE:
|
|
|
|
result = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif // USE_DALI
|