mirror of
https://github.com/arendst/Tasmota.git
synced 2026-10-26 22:11:22 +00:00
* DALI: bind DALI-2 push button events to control gear targets Add command DaliBind<1..8> <device>,<instance>,<target>,<mode> to link push button events (IEC 62386-301) from an input device instance to a control gear target. Modes are Toggle, On, Off, Up and Down; Up and Down start a fade on button press and stop it on release. The first matching binding is executed. DaliBind<x> 0 removes a binding and DaliBind alone lists them all. Bindings are stored in the DALI settings file, packed as one uint32 per slot, so they survive a restart. * Describe how start-up brightness stored differently per short addres vs dali groups
2373 lines
104 KiB
Arduino
2373 lines
104 KiB
Arduino
/*
|
|
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/>.
|
|
*/
|
|
|
|
#ifdef USE_DALI
|
|
/*********************************************************************************************\
|
|
* DALI support for Tasmota
|
|
*
|
|
* Available commands:
|
|
* <byte1>..<byte4> = 0..255 or 0x00..0xFF - Both decimal and hexadecimal is supported
|
|
* <broadcast> = 0 - DALI default
|
|
* <device> = 1..64 - DALI short address + 1
|
|
* <group> = 101..116 - DALI group + 101
|
|
* <dt> = 6, 8 - DALI Device Type specific extended command
|
|
* DaliSend <byte1>,<byte2> - Execute DALI code and do not expect a DALI backward frame
|
|
* DaliSend <dt>,<byte1>,<byte2> - Execute DALI extended code for DT and do not expect a DALI backward frame
|
|
* DaliSend <0xA3>,<byte2>,<byte3>,<byte4> - Set DALI parameter using DTR0 and do not expect a DALI backward frame
|
|
* DaliSend6 - DT6 = 207 = Extended LED commands 224...236
|
|
* DaliSend8 - DT8 = 209 = Extended colour commands 224...246
|
|
* DaliSend255 <bitcount>,<value> - Dali-2 24-bit event frame
|
|
* DaliSend256 <bitcount>,<value> - Dali-2 24-bit event frame send twice
|
|
* DaliQuery <byte1>,<byte2> - Execute DALI code and report result (DALI backward frame)
|
|
* DaliQuery <dt>,<byte1>,<byte2> - Execute DALI extended code for DT and report result (DALI backward frame)
|
|
* DaliScan 1|2[,<max_count>] - Reset (0) or (1)/and commission device short addresses up to optional <max_count> - default 64
|
|
* DaliGear 1..64 - Set max short address to speed up scanning - default 64
|
|
* DaliGroup<1..16> [+]|-<device>,<device>... - Add(+) or Remove(-) devices to/from group
|
|
* DaliPower<broadcast>|<device>|<group> 0..254 - Control power (0 = Off, 1 = Last dimmer, 2 = Toggle, 3..254 = absolute light brightness)
|
|
* DaliDimmer<broadcast>|<device>|<group> 0..100 - Control dimmer (0 = Off, 1..100 = precentage of brightness)
|
|
* DaliBS 0|1 - Set display of GUI broadcast slider - default 1 (ON)
|
|
* DaliGroupSliders 0..16 - Set display number of GUI group sliders - default 2
|
|
* DaliLight 0|1 - Enable Tasmota light control for DaliTarget device - default 1
|
|
* DaliTarget <broadcast>|<device>|<group> - Set Tasmota light control device (0, 1..64, 101..116) - default 0
|
|
* DaliChannels 1..5 - Set Tasmota light type (1 = R/C = DT6, 2 = RG/CW, 3 = RGB, 4 = RGBW, 5 = RGBWC) for DaliTarget
|
|
* DaliDeviceScan 1|2[,<max_count>] - Reset (1) or (2)/and commission DALI-2 control device (input device as opposed to control gear) short addresses up to optional <max_count> - default 64
|
|
* DaliDevice - Scan bus for DALI-2 control devices with a short address
|
|
* DaliDevice <device> - Report DALI-2 control device version, status and instances (type, enabled, event scheme)
|
|
* DaliDevice <broadcast>|<device>,<scheme> - Set event scheme of all instances of a control device
|
|
* (0 = Instance, 1 = Device, 2 = DeviceInstance, 3 = DeviceGroup, 4 = InstanceGroup)
|
|
* DaliBind<1..8> <device>,<instance>,<target>,<mode> - Bind a DALI-2 push button instance to a control gear target
|
|
* <device> 0 = any control device, 1..64 = control device short address +1
|
|
* <instance> 0..31 = instance number, 255 = any instance
|
|
* <target> 0 = broadcast, 1..64 = control gear short address +1, 101..116 = group
|
|
* <mode> 1 = Toggle, 2 = On, 3 = Off, 4 = Up, 5 = Down (see below)
|
|
* DaliBind<1..8> 0 - Remove binding
|
|
* DaliBind - Show all bindings
|
|
*
|
|
* Binding behaviour for push button events (IEC 62386-301), the first matching binding is executed:
|
|
* 1 Toggle - short press toggles the target, long press dims alternating up and down, double press full brightness
|
|
* 2 On - short press switches on to the last level, long press dims up, double press full brightness
|
|
* 3 Off - short press switches off, long press dims down
|
|
* 4 Up - short press or rotation dims up, double press full brightness (rotary clockwise)
|
|
* 5 Down - short press or rotation dims down (rotary anticlockwise)
|
|
* The last level is only stored for broadcast and group targets. Switching on a short address target restores the
|
|
* broadcast level as the driver keeps no per short address state, so use a group target to retain a channel's own level.
|
|
* Example zencontrol zc-switch rotary at short address 0 controlling broadcast:
|
|
* Backlog DaliBind1 1,0,0,1; DaliBind2 1,1,0,4; DaliBind3 1,2,0,5
|
|
*
|
|
* DALI-2 input devices (push buttons, occupancy and light sensors) send 24-bit event messages which are decoded and
|
|
* published for rules and MQTT as {"DALI":{"Event":"0x82840B","Scheme":"Instance","Type":1,"Instance":1,"Info":11,"Name":"LongPressRepeat"}}
|
|
* By default an input device uses event scheme 0 (Instance) which does not contain its short address. After commissioning
|
|
* with DaliDeviceScan use DaliDevice <device>,2 to switch to DeviceInstance events so every event identifies the sending device.
|
|
*
|
|
* DALI background information
|
|
* Address type Address byte
|
|
* ------------------ --------------------
|
|
* Broadcast address 1111111S
|
|
* 64 short address 0AAAAAAS
|
|
* 16 group address 100AAAAS
|
|
* Special command 101CCCC1 to 110CCCC1
|
|
* A = Address bit, S = 0 Direct Arc Power control, S = 1 Command, C = Special command
|
|
*
|
|
* Shelly DALI Dimmer Gen3 (ESP32C3-8M) - GPIO3 controls DALI power. In following template it is always ON. Max output is 16V/10mA (= 5 DALI gear)
|
|
* - Template {"NAME":"Shelly DALI Dimmer Gen3","GPIO":[34,4736,0,3840,11360,11392,128,129,0,1,576,0,0,0,0,0,0,0,0,1,1,1],"FLAG":0,"BASE":1}
|
|
* - AdcGpio1 10000,10000,4000 <- Temperature parameters
|
|
* - Backlog ButtonTopic 0; SetOption1 1; SetOption11 0; SetOption32 20; DimmerStep 5; LedTable 0
|
|
* - rule1 on button1#state=2 do dimmer + endon on button2#state=2 do dimmer - endon on button1#state=3 do power 2 endon on button2#state=3 do power 2 endon
|
|
*
|
|
* DALI RGBWAF color support tested with MiBoxer DALI 5 in 1 LED Controller (DT8) and 12V RGB led strip:
|
|
* - Reset device (long press M button 10 seconds until digital display shows AES).
|
|
* - Set fixed unused short address (Scanning/Commissioning doesn't work for this device) ie. 005.
|
|
* - Use command `DaliTarget 6` to link Tasmota color control to this device ie. 005 +1 = 6.
|
|
* - Use command `DaliChannels 3` to set the amount of color hardware channels connected ie. RGB ledstrip is 3.
|
|
* - Use command `DaliLight 1` to enable Tasmota color control.
|
|
--------------------------------------------------------------------------------------------
|
|
Version yyyymmdd Action Description
|
|
--------------------------------------------------------------------------------------------
|
|
1.5.2.0 20260530 update - Add DaliSend256 to send 24-bit event frame twice
|
|
1.5.1.0 20251207 update - Display GUI broadcast slider even if Tasmota Light Control is active
|
|
- Add command `DaliNoBS 0|1` to disable display of GUI broadcast slider
|
|
1.5.0.0 20251206 update - Fix WAF GUI sync
|
|
- Refactor GUI sync
|
|
1.4.1.0 20251130 update - Add options to `DaliGear` and DaliGroup` to toggle specific outputs
|
|
- Make max number of devices persistent to speed up scan response
|
|
1.4.0.0 20251126 update - Change to TasmotaDali library
|
|
1.3.0.4 20251123 update - Add send retry on collision detection
|
|
- Prep DALI-2 24-bit transceive
|
|
1.3.0.3 20251122 update - Remove sleep dependency from frame handling
|
|
- Change receive timeout from 50 ms to 20 ms (DALI protocol is 9.2 ms)
|
|
- Add DALI DT8 RGBWAF Control Gear (receive) for Tasmota color light control
|
|
1.3.0.2 20251121 update - Revert timing from 10 to 14ms as changed due to bad dali PS (underrated Shelly DALI Dimmer Gen3)
|
|
- Add optional power off without fading (reduces DT8 dali commands)
|
|
- Remove not performing logging from interrupt routine
|
|
1.3.0.1 20251120 update - Reduce send-twice timing from 14 to 10ms fixing MiBoxer DT8
|
|
1.3.0.0 20251119 update - Add DALI DT8 RGBWAF Control Device (send) using Tasmota color light control
|
|
- Add persistent command `DaliChannels` to select Tasmota color type
|
|
1.2.0.0 20251116 update - Add persistence for `DaliTarget` if filesystem is present
|
|
1.1.0.4 20251115 fix - Tasmota light control using non-broadcast address
|
|
1.1.0.3 20251112 remove - Remove optional repeat for commands `DaliSend` and `DaliQuery`
|
|
Send twice is now based on DALI defined command type
|
|
1.1.0.2 20251109 update - Add optional extended commands prefix for commands `DaliSend` and `DaliQuery`
|
|
1.1.0.1 20241101 update - Enable DALI if another light is already claimed
|
|
1.1.0.0 20241031 update - Add GUI sliders with feedback when `DaliLight 0`
|
|
- Add command `DaliGroupSliders 0..16` to show GUI sliders
|
|
1.0.0.2 20241025 update - Fix GPIO detection
|
|
- Fix ESP32(C3) transmit stability by disabling interrupts
|
|
1.0.0.1 20241024 update - Change from signal invert defines to GPIO config DALI RX_i/DALI TX_i
|
|
- Fix inverted DALI signal support
|
|
- Experimental support for Shelly DALI Dimmer Gen3
|
|
1.0.0.0 20241022 update - Refactor commission
|
|
- Add receive collision detection
|
|
0.1.0.8 20241019 update - Rename command `DaliCommission` to `DaliScan`
|
|
- Rename command `DaliWeb` to `DaliLight`
|
|
- Add command `DaliGear` to set max found gear to speed up scan response
|
|
0.1.0.7 20241017 update - Add command `DaliCommission 1|2` assigning short addresses
|
|
- Add command `DaliTarget 0, 1..64, 101..116` to select light control address
|
|
- Add command `DaliGroup1..16 +<devices>|-<devices>` to add/remove devices from group
|
|
- Extend commands `DaliPower` and `DaliDimmer` with index to control short address or group
|
|
- Remove non-functional MQTT interface
|
|
0.1.0.6 20241014 update - Fix received light command loop
|
|
- Add send collision detection
|
|
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
|
|
0.1.0.4 20241013 update - Fix intermittent bad send timing
|
|
0.1.0.3 20241010 update - Change DaliDimmer range from 0..254 to 0..100
|
|
- Add command `DaliWeb 0|1` to enable persistent Web light controls
|
|
0.1.0.2 20241008 update - Better receive error detection
|
|
0.1.0.1 20241007 update - To stablizie communication send DALI datagram twice like Busch-Jaeger does
|
|
- Change DaliPower 0..2 to act like Tasmota Power (Off, On, Toggle)
|
|
- Keep last Dimmer value as default power on
|
|
0.1.0.0 20241006 rewrite - Add support for ESP8266 by Theo Arends
|
|
- Fix decoding of received DALI 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 by Andrei Kazmirtsuk aka eeak
|
|
\*********************************************************************************************/
|
|
|
|
#define XDRV_75 75
|
|
|
|
#ifndef DALI_INIT_STATE
|
|
#define DALI_INIT_STATE 50 // DALI init dimmer state 50/254
|
|
#endif
|
|
#ifndef DALI_INIT_FADE
|
|
#define DALI_INIT_FADE 1 // Fade between light states in number of seconds
|
|
#endif
|
|
#ifndef DALI_TIMEOUT
|
|
#define DALI_TIMEOUT 20 // DALI backward frame receive timeout (ms) - Protocol = >7Te and <22Te (22 * 417us)
|
|
#endif
|
|
|
|
//#define DALI_LIGHT_NO_READ_AFTER_WRITE // Use no DTR read-after-write for smooth color transitions (saves 55ms / channel)
|
|
//#define DALI_POWER_OFF_NO_FADE // Power off immediatly without fading
|
|
|
|
//#define DALI_DEBUG
|
|
|
|
/*********************************************************************************************/
|
|
|
|
#include <TasmotaDali.h>
|
|
|
|
#define DALI_MAX_STORED 17 // Store broadcast and group states
|
|
|
|
#ifndef DALI_MAX_BIND
|
|
#define DALI_MAX_BIND 8 // Number of input device to control gear bindings (max 32)
|
|
#endif
|
|
|
|
#define DALI_TOPIC "DALI"
|
|
#define D_PRFX_DALI "Dali"
|
|
|
|
/*********************************************************************************************\
|
|
* DALI-2 control devices (IEC 62386-103) use 24-bit command frames: <address byte><instance byte><opcode byte>
|
|
* Address byte 0AAAAAA1 short address, 10GGGGG1 device group, 0xFD broadcast unaddressed, 0xFF broadcast, 0xC1 special command
|
|
* Instance byte 000NNNNN instance number, 100GGGGG instance group, 110TTTTT instance type, 0xFE device, 0xFF all instances
|
|
* For special commands the instance byte is the opcode and the opcode byte is the data
|
|
\*********************************************************************************************/
|
|
|
|
#define DALI_103_FRAME (TM_DALI_EVENT_FRAME | 24) // 24-bit forward frame
|
|
#define DALI_103_BROADCAST 0xFF
|
|
#define DALI_103_SPECIAL_COMMAND 0xC1
|
|
#define DALI_103_INSTANCE_DEVICE 0xFE // Instance byte for device commands
|
|
#define DALI_103_INSTANCE_BROADCAST 0xFF // Instance byte for all instances
|
|
#define DALI_103_MASK 0xFF
|
|
// Special commands (address byte 0xC1)
|
|
#define DALI_103_TERMINATE 0x00
|
|
#define DALI_103_INITIALISE 0x01 // REPEAT - data 0x7F = all devices, 0xFF = all devices without short address
|
|
#define DALI_103_RANDOMISE 0x02 // REPEAT
|
|
#define DALI_103_COMPARE 0x03
|
|
#define DALI_103_WITHDRAW 0x04
|
|
#define DALI_103_SEARCHADDRH 0x05
|
|
#define DALI_103_SEARCHADDRM 0x06
|
|
#define DALI_103_SEARCHADDRL 0x07
|
|
#define DALI_103_PROGRAM_SHORT_ADDRESS 0x08 // data 0..63 or MASK
|
|
#define DALI_103_VERIFY_SHORT_ADDRESS 0x09 // data 0..63
|
|
#define DALI_103_QUERY_SHORT_ADDRESS 0x0A
|
|
#define DALI_103_DTR0 0x30
|
|
#define DALI_103_DTR1 0x31
|
|
#define DALI_103_DTR2 0x32
|
|
#define DALI_103_INITIALISE_ALL 0x7F
|
|
#define DALI_103_INITIALISE_UNADDRESSED 0xFF
|
|
// Device commands (instance byte 0xFE)
|
|
#define DALI_103_IDENTIFY_DEVICE 0x00 // REPEAT
|
|
#define DALI_103_RESET 0x10 // REPEAT
|
|
#define DALI_103_SET_SHORT_ADDRESS 0x14 // REPEAT - DTR0 0..63 or MASK
|
|
#define DALI_103_QUERY_DEVICE_STATUS 0x30
|
|
#define DALI_103_QUERY_MISSING_SHORT_ADDRESS 0x33
|
|
#define DALI_103_QUERY_VERSION_NUMBER 0x34
|
|
#define DALI_103_QUERY_NUMBER_OF_INSTANCES 0x35
|
|
// Instance commands (instance byte selects instance number, group, type or all)
|
|
#define DALI_103_ENABLE_INSTANCE 0x62 // REPEAT
|
|
#define DALI_103_DISABLE_INSTANCE 0x63 // REPEAT
|
|
#define DALI_103_SET_EVENT_SCHEME 0x67 // REPEAT - DTR0 0..4
|
|
#define DALI_103_QUERY_INSTANCE_TYPE 0x80
|
|
#define DALI_103_QUERY_INSTANCE_ENABLED 0x86
|
|
#define DALI_103_QUERY_EVENT_SCHEME 0x8B
|
|
#define DALI_103_QUERY_INPUT_VALUE 0x8C
|
|
|
|
/*********************************************************************************************/
|
|
|
|
typedef struct DliBind_t {
|
|
uint8_t device; // 0 = any control device, 1..64 = short address +1
|
|
uint8_t instance; // 0..31 = instance number, 255 = any instance
|
|
uint8_t target; // 0 = broadcast, 1..64 = gear short address +1, 101..116 = group
|
|
uint8_t mode; // 0 = unused, 1 = Toggle, 2 = On, 3 = Off, 4 = Up, 5 = Down
|
|
} DliBind_t;
|
|
|
|
typedef struct DliSettings_t {
|
|
uint32_t crc32; // To detect file changes
|
|
uint8_t target;
|
|
uint8_t light_type;
|
|
uint8_t max_gear;
|
|
uint8_t probe_retry;
|
|
DliBind_t bind[DALI_MAX_BIND];
|
|
} DliSettings_t;
|
|
|
|
struct DALI {
|
|
DliSettings_t Settings; // Persistent settings
|
|
TasmotaDali *dali;
|
|
uint32_t light_sync;
|
|
uint32_t bind_down; // Current dim direction per binding (bit set = down)
|
|
uint8_t address;
|
|
uint8_t command;
|
|
uint8_t last_dimmer;
|
|
uint8_t dimmer[DALI_MAX_STORED];
|
|
uint8_t web_dimmer[DALI_MAX_STORED];
|
|
uint8_t color[5];
|
|
uint8_t target_rgbwaf;
|
|
uint8_t device_type;
|
|
uint8_t dtr[3];
|
|
uint8_t probe;
|
|
#ifdef DALI_DEBUG
|
|
uint8_t log_level;
|
|
#endif // DALI_DEBUG
|
|
bool allow_light;
|
|
bool last_power;
|
|
bool power[DALI_MAX_STORED];
|
|
} *Dali = nullptr;
|
|
|
|
/*********************************************************************************************\
|
|
* Driver Settings load and save
|
|
\*********************************************************************************************/
|
|
|
|
#ifdef USE_UFILESYS
|
|
#define XDRV_75_KEY "drvset75"
|
|
|
|
bool DaliLoadData(void) {
|
|
char key[] = XDRV_75_KEY;
|
|
String json = UfsJsonSettingsRead(key);
|
|
if (json.length() == 0) { return false; }
|
|
|
|
// {"Crc":1882268982,"Target":0,"LightType":3}
|
|
JsonParser parser((char*)json.c_str());
|
|
JsonParserObject root = parser.getRootObject();
|
|
if (!root) { return false; }
|
|
|
|
Dali->Settings.crc32 = root.getUInt(PSTR("Crc"), Dali->Settings.crc32);
|
|
Dali->Settings.target = root.getUInt(PSTR("Target"), Dali->Settings.target);
|
|
Dali->Settings.light_type = root.getUInt(PSTR("LightType"), Dali->Settings.light_type);
|
|
Dali->Settings.max_gear = root.getUInt(PSTR("MaxGear"), Dali->Settings.max_gear);
|
|
Dali->Settings.probe_retry = root.getUInt(PSTR("Retry"), Dali->Settings.probe_retry);
|
|
JsonParserArray arr = root[PSTR("Bind")];
|
|
if (arr) {
|
|
for (uint32_t i = 0; i < DALI_MAX_BIND; i++) {
|
|
if (arr[i]) {
|
|
uint32_t bind = arr[i].getUInt(); // Packed as mode << 24 | target << 16 | instance << 8 | device
|
|
Dali->Settings.bind[i].device = bind;
|
|
Dali->Settings.bind[i].instance = bind >> 8;
|
|
Dali->Settings.bind[i].target = bind >> 16;
|
|
Dali->Settings.bind[i].mode = bind >> 24;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool DaliSaveData(void) {
|
|
Response_P(PSTR("{\"" XDRV_75_KEY "\":{\"Crc\":%u,\"Target\":%u,\"LightType\":%u,\"MaxGear\":%u,\"Retry\":%u}}"),
|
|
Dali->Settings.crc32,
|
|
Dali->Settings.target,
|
|
Dali->Settings.light_type,
|
|
Dali->Settings.max_gear,
|
|
Dali->Settings.probe_retry);
|
|
Response_P(PSTR("{\"" XDRV_75_KEY "\":{\"Crc\":%u,\"Target\":%u,\"LightType\":%u,\"MaxGear\":%u,\"Bind\":["),
|
|
Dali->Settings.crc32,
|
|
Dali->Settings.target,
|
|
Dali->Settings.light_type,
|
|
Dali->Settings.max_gear);
|
|
for (uint32_t i = 0; i < DALI_MAX_BIND; i++) {
|
|
DliBind_t *bind = &Dali->Settings.bind[i];
|
|
ResponseAppend_P(PSTR("%s%u"), (i) ? "," : "",
|
|
(bind->mode << 24) | (bind->target << 16) | (bind->instance << 8) | bind->device);
|
|
}
|
|
ResponseAppend_P(PSTR("]}}"));
|
|
|
|
return UfsJsonSettingsWrite(ResponseData());
|
|
}
|
|
|
|
void DaliDeleteData(void) {
|
|
char key[] = XDRV_75_KEY;
|
|
UfsJsonSettingsDelete(key); // Use defaults
|
|
}
|
|
#endif // USE_UFILESYS
|
|
|
|
/*********************************************************************************************/
|
|
|
|
void DaliSettingsLoad(bool erase) {
|
|
// Called from FUNC_MODULE_INIT/FUNC_PRE_INIT (erase = 0) once at restart
|
|
// Called from FUNC_RESET_SETTINGS (erase = 1) after command reset 4, 5, or 6
|
|
|
|
// *** Start init default values in case key is not found ***
|
|
memset(&Dali->Settings, 0x00, sizeof(DliSettings_t));
|
|
Dali->Settings.light_type = LT_RGB; // Default RGB channel
|
|
Dali->Settings.max_gear = 64; // Default max supported short address
|
|
// *** End Init default values ***
|
|
|
|
#ifndef USE_UFILESYS
|
|
AddLog(LOG_LEVEL_DEBUG, PSTR("DLI: Use default cfg as file system not enabled"));
|
|
#else
|
|
// Try to load key
|
|
if (erase) {
|
|
DaliDeleteData();
|
|
}
|
|
else if (DaliLoadData()) {
|
|
AddLog(LOG_LEVEL_DEBUG, PSTR("DLI: Cfg loaded from file"));
|
|
}
|
|
else {
|
|
AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("DLI: Use default cfg as file system not ready or key not found"));
|
|
}
|
|
#endif // USE_UFILESYS
|
|
}
|
|
|
|
void DaliSettingsSave(void) {
|
|
// Called from FUNC_SAVE_SETTINGS every SaveData second and at restart
|
|
#ifdef USE_UFILESYS
|
|
uint32_t crc32 = GetCfgCrc32((uint8_t*)&Dali->Settings +4, sizeof(DliSettings_t) -4); // Skip crc32
|
|
if (crc32 != Dali->Settings.crc32) {
|
|
Dali->Settings.crc32 = crc32;
|
|
if (DaliSaveData()) {
|
|
AddLog(LOG_LEVEL_DEBUG, PSTR("DLI: Cfg saved to file"));
|
|
} else {
|
|
AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("DLI: ERROR File system not ready or unable to save file"));
|
|
}
|
|
}
|
|
#endif // USE_UFILESYS
|
|
}
|
|
|
|
/*********************************************************************************************\
|
|
* DALI low level
|
|
\*********************************************************************************************/
|
|
|
|
uint32_t DaliKelvinToMirek(uint32_t kelvin) {
|
|
if (kelvin < 16) {
|
|
return 65535;
|
|
}
|
|
if (kelvin > 1000000) {
|
|
return 0;
|
|
}
|
|
return 1000000 / kelvin;
|
|
}
|
|
|
|
uint32_t DaliMirekToKelvin(uint32_t mirek) {
|
|
if (mirek <= 0) {
|
|
return 1000001;
|
|
}
|
|
if (mirek >= 65535) {
|
|
return 0;
|
|
}
|
|
return 1000000 / mirek;
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------------------------*/
|
|
|
|
uint32_t DaliTarget2Address(uint32_t target) {
|
|
// 1..64 = Short address
|
|
// 101..116 = Group address
|
|
// Others = Broadcast
|
|
if ((target >= 1) && (target <= 64)) { // 1 .. 64
|
|
return (target -1) << 1; // Short address: 0b00000000 .. 0b01111110
|
|
}
|
|
else if ((target >= 101) && (target <= 116)) { // 101 .. 116
|
|
return ((target -101) << 1) | 0x80; // Group address: 0b10000000 .. 0b10011110
|
|
}
|
|
return DALI_BROADCAST_DP; // Broadcast address: 0b11111110
|
|
}
|
|
/*
|
|
uint32_t DaliAddress2Target(uint32_t adr) {
|
|
if (adr >= 254) { // 0b1111111S
|
|
return 0; // Broadcast address (0)
|
|
}
|
|
else if ((adr >= 128) && (adr <= 159)) { // 0b1000000S .. 0b1001111S
|
|
return (adr >> 1) +101; // Group address (101 .. 116)
|
|
}
|
|
return (adr >> 1) +1; // 0b0000000S .. 0b0111111S Short address (1 .. 64)
|
|
}
|
|
*/
|
|
/*-------------------------------------------------------------------------------------------*/
|
|
|
|
uint32_t DaliSaveState(uint32_t adr, uint32_t cmd) {
|
|
if (adr & DALI_SELECTOR_BIT) { return 0; } // No address
|
|
int index = -1;
|
|
if (DALI_BROADCAST_DP == adr) { // Broadcast address
|
|
index = 0;
|
|
}
|
|
adr >>= 1;
|
|
if ((adr >= 0x40) && (adr <= 0x4F)) { // Group address 0 to 15
|
|
index = adr -0x3F;
|
|
}
|
|
if (index >= 0) {
|
|
Dali->last_power = Dali->power[index];
|
|
Dali->power[index] = (cmd); // State
|
|
if (Dali->power[index]) {
|
|
Dali->last_dimmer = Dali->dimmer[index];
|
|
Dali->dimmer[index] = cmd; // Value
|
|
}
|
|
if ((0 == index) && !Dali->power[0]) { // Only on Broadcast change to power Off
|
|
for (uint32_t i = 0; i < DALI_MAX_STORED; i++) {
|
|
Dali->power[i] = false; // Log all group power as Off when Broadcast is Off
|
|
}
|
|
}
|
|
} else {
|
|
index = 0; // Use broadcast
|
|
}
|
|
return index;
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------------------------*/
|
|
|
|
void DaliSendData(uint32_t adr, uint32_t cmd) {
|
|
DaliFrame frame;
|
|
if (adr & TM_DALI_EVENT_FRAME) { // 24-bit event frame
|
|
frame.data = cmd;
|
|
frame.meta = adr & (TM_DALI_BIT_COUNT_MASK | TM_DALI_SEND_TWICE);
|
|
} else { // 16-bit command frame
|
|
adr &= 0xFF;
|
|
cmd &= 0xFF;
|
|
|
|
Dali->address = adr;
|
|
Dali->command = cmd;
|
|
DaliSaveState(adr, cmd);
|
|
|
|
frame.data = adr << 8 | cmd;
|
|
frame.meta = 16;
|
|
|
|
if (adr & DALI_SELECTOR_BIT) { // Selector bit (command) or special command
|
|
if (DALI_102_ENABLE_DEVICE_TYPE_X == adr) {
|
|
Dali->device_type = cmd; // Next command will be an extended command
|
|
}
|
|
uint32_t send_twice_extended_start;
|
|
uint32_t send_twice_extended_end;
|
|
if (DALI_205_DEVICE_TYPE == Dali->device_type) {
|
|
send_twice_extended_start = DALI_205_REFERENCE_SYSTEM_POWER;
|
|
send_twice_extended_end = DALI_205_RESERVED237;
|
|
}
|
|
else if (DALI_207_DEVICE_TYPE == Dali->device_type) {
|
|
send_twice_extended_start = DALI_207_REFERENCE_SYSTEM_POWER;
|
|
send_twice_extended_end = DALI_207_RESERVED236;
|
|
}
|
|
else if (DALI_209_DEVICE_TYPE == Dali->device_type) {
|
|
send_twice_extended_start = DALI_209_SET_TEMPORARY_X_COORDINATE;
|
|
send_twice_extended_end = DALI_209_START_AUTO_CALIBRATION;
|
|
}
|
|
if ((DALI_102_INITIALISE == adr) ||
|
|
(DALI_102_RANDOMISE == adr) ||
|
|
((cmd >= DALI_102_RESET) && (cmd <= DALI_102_RESERVED143)) || // Configuration commands
|
|
(Dali->device_type && (cmd >= send_twice_extended_start) && (cmd <= send_twice_extended_end))) {
|
|
frame.meta |= TM_DALI_SEND_TWICE;
|
|
}
|
|
}
|
|
}
|
|
|
|
AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("DLI: Tx 0x%08X %2d DT%d%s"),
|
|
frame.data, frame.meta & TM_DALI_BIT_COUNT_MASK,
|
|
Dali->device_type, (frame.meta & TM_DALI_SEND_TWICE)?" twice":"");
|
|
|
|
Dali->dali->write(frame); // Takes 14.7 ms
|
|
|
|
if (Dali->device_type && (cmd >= 224) && (cmd <= 255)) { // Extended commands
|
|
Dali->device_type = 0; // Reset after execution of extended command
|
|
}
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------------------------*/
|
|
|
|
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->dali->flush();
|
|
DaliSendData(adr, cmd);
|
|
while (!Dali->dali->available() && timeout--) { // Expect backward frame within DALI_TIMEOUT ms (>7Te and <22Te)
|
|
delay(1);
|
|
};
|
|
int result = -1; // DALI NO or no response
|
|
DaliFrame frame;
|
|
frame.data = 0;
|
|
frame.meta = 0;
|
|
if (Dali->dali->available()) {
|
|
frame = Dali->dali->read();
|
|
if ((frame.meta & TM_DALI_COLLISION) ||
|
|
(frame.meta != 8)) {
|
|
result = -2; // Collision
|
|
}
|
|
else {
|
|
result = (frame.data &0xFF); // Backward frame
|
|
}
|
|
}
|
|
|
|
AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("DLI: Rx 0x%08X %2d response"), result, frame.meta);
|
|
|
|
return result;
|
|
}
|
|
|
|
/*********************************************************************************************\
|
|
* DALI-2 control device 24-bit command frames (IEC 62386-103)
|
|
\*********************************************************************************************/
|
|
|
|
uint32_t DaliDeviceFrame(uint32_t address, uint32_t instance, uint32_t opcode) {
|
|
return ((address & 0xFF) << 16) | ((instance & 0xFF) << 8) | (opcode & 0xFF);
|
|
}
|
|
|
|
uint32_t DaliDeviceAddress(uint32_t device) {
|
|
// Convert Tasmota device 1..64 to IEC 62386-103 address byte 0AAAAAA1 (0 = broadcast)
|
|
return (device) ? (((device -1) & 0x3F) << 1) | 0x01 : DALI_103_BROADCAST;
|
|
}
|
|
|
|
void DaliDeviceSend(uint32_t frame, bool twice = false);
|
|
void DaliDeviceSend(uint32_t frame, bool twice) {
|
|
DaliSendData(DALI_103_FRAME | ((twice) ? TM_DALI_SEND_TWICE : 0), frame);
|
|
}
|
|
|
|
void DaliDeviceSpecial(uint32_t opcode, uint32_t data, bool twice = false);
|
|
void DaliDeviceSpecial(uint32_t opcode, uint32_t data, bool twice) {
|
|
DaliDeviceSend(DaliDeviceFrame(DALI_103_SPECIAL_COMMAND, opcode, data), twice);
|
|
}
|
|
|
|
int DaliDeviceQuery(uint32_t frame) {
|
|
// Send 24-bit frame and return backward frame or -1 (no response) / -2 (collision)
|
|
return DaliSendWaitResponse(DALI_103_FRAME, frame);
|
|
}
|
|
|
|
/*********************************************************************************************\
|
|
* DALI tools
|
|
*
|
|
* Courtesy of https://github.com/qqqlab/DALI-Lighting-Interface
|
|
\*********************************************************************************************/
|
|
|
|
bool DaliSetDTR(uint32_t dtr, uint32_t adr, uint32_t value) {
|
|
uint8_t dtr_set[3] = { DALI_102_SET_DTR0, DALI_102_SET_DTR1, DALI_102_SET_DTR2 };
|
|
uint8_t dtr_query[3] = { DALI_102_QUERY_CONTENT_DTR0, DALI_102_QUERY_CONTENT_DTR1, DALI_102_QUERY_CONTENT_DTR2 };
|
|
|
|
if (dtr > 2) { dtr = 0; }
|
|
uint32_t retry = 3;
|
|
while (retry--) {
|
|
DaliSendData(dtr_set[dtr], value); // Store value in DTR
|
|
int result = DaliSendWaitResponse(adr | DALI_SELECTOR_BIT, dtr_query[dtr]); // Get DTR value
|
|
if (result == value) { return true; }
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool DaliSetValue(uint32_t adr, uint32_t getcmd, uint32_t setcmd, uint32_t v) {
|
|
// Set a parameter value, returns true on success
|
|
adr |= DALI_SELECTOR_BIT; // Enable Selector bit
|
|
int current_v = DaliSendWaitResponse(adr, getcmd); // Get current parameter value
|
|
if (current_v == v) { return true; } // Already set
|
|
if (!DaliSetDTR(0, adr, v)) { return false; }
|
|
DaliSendData(adr, setcmd); // Set parameter value = DTR
|
|
current_v = DaliSendWaitResponse(adr, getcmd); // Get current parameter value
|
|
if (current_v != v) { return false; } // Set failed
|
|
return true;
|
|
}
|
|
|
|
bool DaliSetOperatingMode(uint32_t adr, uint32_t v) {
|
|
return DaliSetValue(adr, DALI_102_QUERY_OPERATING_MODE, DALI_102_SET_OPERATING_MODE, v);
|
|
}
|
|
|
|
bool DaliSetMaxLevel(uint32_t adr, uint32_t v) {
|
|
return DaliSetValue(adr, DALI_102_QUERY_MAX_LEVEL, DALI_102_SET_MAX_LEVEL, v);
|
|
}
|
|
|
|
bool DaliSetMinLevel(uint32_t adr, uint32_t v) {
|
|
return DaliSetValue(adr, DALI_102_QUERY_MIN_LEVEL, DALI_102_SET_MIN_LEVEL, v);
|
|
}
|
|
|
|
bool DaliSetSystemFailureLevel(uint32_t adr, uint32_t v) {
|
|
return DaliSetValue(adr, DALI_102_QUERY_SYSTEM_FAILURE_LEVEL, DALI_102_SET_SYSTEM_FAILURE_LEVEL, v);
|
|
}
|
|
|
|
bool DaliSetPowerOnLevel(uint32_t adr, uint32_t v) {
|
|
return DaliSetValue(adr, DALI_102_QUERY_POWER_ON_LEVEL, DALI_102_SET_POWER_ON_LEVEL, v);
|
|
}
|
|
|
|
int DaliQueryExtendedVersionNumber(uint32_t adr, uint32_t device_type) {
|
|
DaliSendData(DALI_102_ENABLE_DEVICE_TYPE_X, device_type); // Enable Extended command
|
|
return DaliSendWaitResponse(adr | DALI_SELECTOR_BIT, 255); // DALI_xxx_QUERY_EXTENDED_VERSION_NUMBER
|
|
}
|
|
|
|
#ifdef USE_LIGHT
|
|
uint32_t DaliQueryRGBWAF(uint32_t adr) {
|
|
// https://www.dali-alliance.org/tech-notes/device-type-discovery.html
|
|
uint32_t rgbwaf_channels = 0;
|
|
|
|
adr |= DALI_SELECTOR_BIT; // Enable Selector bit
|
|
int dt = DaliSendWaitResponse(adr, DALI_102_QUERY_DEVICE_TYPE);
|
|
// If the device does not implement any part 2xx device type then the response will be 254;
|
|
// If the device implements one part 2xx device type then the response will be the device type number;
|
|
// If the device implements multiple part 2xx device types then the response will be MASK (0xff).
|
|
// In all other cases returns NO (no response).
|
|
if (255 == dt) { // DALI-2
|
|
int next_dt;
|
|
do {
|
|
next_dt = DaliSendWaitResponse(adr, DALI_102_QUERY_NEXT_DEVICE_TYPE);
|
|
// DALI2: If directly preceded by DALI_102_QUERY_DEVICE_TYPE and more than one device type is supported, returns the first and lowest device type number.
|
|
// DALI2: If directly preceded by DALI_102_QUERY_NEXT_DEVICE_TYPE and not all device types have been reported, returns the next lowest device type number.
|
|
// DALI2: If directly preceded by DALI_102_QUERY_NEXT_DEVICE_TYPE and all device types have been reported, returns 254.
|
|
// In all other cases returns NO (no response).
|
|
if (DALI_209_DEVICE_TYPE == next_dt) {
|
|
dt = DALI_209_DEVICE_TYPE;
|
|
}
|
|
} while ((next_dt >= 0) && (next_dt != 254));
|
|
}
|
|
if (dt < 0) { // DALI version-1
|
|
if (DaliQueryExtendedVersionNumber(adr, DALI_209_DEVICE_TYPE) >= 0) { // Color device
|
|
dt = DALI_209_DEVICE_TYPE;
|
|
}
|
|
}
|
|
if (DALI_209_DEVICE_TYPE == dt) {
|
|
uint32_t retry = 3;
|
|
while (retry--) {
|
|
DaliSendData(DALI_102_ENABLE_DEVICE_TYPE_X, DALI_209_DEVICE_TYPE); // Enable Extended command
|
|
int colour_type = DaliSendWaitResponse(adr, DALI_209_QUERY_COLOUR_TYPE_FEATURES);
|
|
if (colour_type >= 0) {
|
|
rgbwaf_channels = (colour_type >> 5) & 0x07; // RGBWAF channels in bits 5..7
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return rgbwaf_channels;
|
|
}
|
|
#endif // USE_LIGHT
|
|
|
|
/*-------------------------------------------------------------------------------------------*/
|
|
|
|
void DaliInitLight(void) {
|
|
// Taken from Shelly Dali Dimmer ;-)
|
|
uint32_t adr = DALI_BROADCAST_DP | DALI_SELECTOR_BIT;
|
|
DaliSendData(DALI_102_SET_DTR0, DALI_INIT_FADE); // Fade x second
|
|
DaliSendData(adr, DALI_102_SET_FADE_TIME);
|
|
DaliSendData(DALI_102_SET_DTR0, 0); // Power off after gear power restore
|
|
DaliSendData(adr, DALI_102_SET_POWER_ON_LEVEL);
|
|
DaliSendData(DALI_102_SET_DTR0, 0xFE); // Reset all but short circuit
|
|
DaliSendData(adr, DALI_102_SET_SYSTEM_FAILURE_LEVEL);
|
|
}
|
|
|
|
/*********************************************************************************************\
|
|
* DALI commissioning short addresses
|
|
*
|
|
* Courtesy of https://github.com/qqqlab/DALI-Lighting-Interface
|
|
\*********************************************************************************************/
|
|
|
|
void DaliSetSearchAddress(uint32_t adr, bool device = false);
|
|
void DaliSetSearchAddress(uint32_t adr, bool device) {
|
|
// Set search address of control gear (device = false) or DALI-2 control devices (device = true)
|
|
if (device) {
|
|
DaliDeviceSpecial(DALI_103_SEARCHADDRH, adr>>16);
|
|
DaliDeviceSpecial(DALI_103_SEARCHADDRM, adr>>8);
|
|
DaliDeviceSpecial(DALI_103_SEARCHADDRL, adr);
|
|
} else {
|
|
DaliSendData(DALI_102_SEARCHADDRH, adr>>16);
|
|
DaliSendData(DALI_102_SEARCHADDRM, adr>>8);
|
|
DaliSendData(DALI_102_SEARCHADDRL, adr);
|
|
}
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------------------------*/
|
|
|
|
void DaliSetSearchAddressDifference(uint32_t adr_new, uint32_t adr_current, bool device = false);
|
|
void DaliSetSearchAddressDifference(uint32_t adr_new, uint32_t adr_current, bool device) {
|
|
// Set search address, but set only changed bytes (takes less time)
|
|
if (device) {
|
|
if ( (uint8_t)(adr_new>>16) != (uint8_t)(adr_current>>16) ) DaliDeviceSpecial(DALI_103_SEARCHADDRH, adr_new>>16);
|
|
if ( (uint8_t)(adr_new>>8) != (uint8_t)(adr_current>>8) ) DaliDeviceSpecial(DALI_103_SEARCHADDRM, adr_new>>8);
|
|
if ( (uint8_t)(adr_new) != (uint8_t)(adr_current) ) DaliDeviceSpecial(DALI_103_SEARCHADDRL, adr_new);
|
|
} else {
|
|
if ( (uint8_t)(adr_new>>16) != (uint8_t)(adr_current>>16) ) DaliSendData(DALI_102_SEARCHADDRH, adr_new>>16);
|
|
if ( (uint8_t)(adr_new>>8) != (uint8_t)(adr_current>>8) ) DaliSendData(DALI_102_SEARCHADDRM, adr_new>>8);
|
|
if ( (uint8_t)(adr_new) != (uint8_t)(adr_current) ) DaliSendData(DALI_102_SEARCHADDRL, adr_new);
|
|
}
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------------------------*/
|
|
|
|
bool DaliCompare(bool device = false);
|
|
bool DaliCompare(bool device) {
|
|
// Is the random address smaller or equal to the search address?
|
|
// As more than one device can reply, the reply gets garbled
|
|
uint8_t retry = 2;
|
|
while (retry > 0) {
|
|
// Compare is true if we received any activity on the bus as reply.
|
|
// Sometimes the reply is not registered... so only accept retry times 'no reply' as a real false compare
|
|
int rv = (device) ? DaliDeviceQuery(DaliDeviceFrame(DALI_103_SPECIAL_COMMAND, DALI_103_COMPARE, 0x00)) :
|
|
DaliSendWaitResponse(DALI_102_COMPARE, 0x00);
|
|
if (rv == 0xFF) return true; // Yes reply
|
|
if (rv == -2) return true; // Reply but collision
|
|
retry--;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------------------------*/
|
|
|
|
uint32_t DaliFindAddress(bool device = false);
|
|
uint32_t DaliFindAddress(bool device) {
|
|
// Find addr with binary search
|
|
uint32_t adr = 0x800000;
|
|
uint32_t addsub = 0x400000;
|
|
uint32_t adr_last = adr;
|
|
DaliSetSearchAddress(adr, device);
|
|
|
|
while (addsub) {
|
|
DaliSetSearchAddressDifference(adr, adr_last, device);
|
|
adr_last = adr;
|
|
if (DaliCompare(device)) { // Returns true if searchadr > adr
|
|
adr -= addsub;
|
|
} else {
|
|
adr += addsub;
|
|
}
|
|
addsub >>= 1;
|
|
}
|
|
DaliSetSearchAddressDifference(adr, adr_last, device);
|
|
adr_last = adr;
|
|
if (!DaliCompare(device)) {
|
|
adr++;
|
|
DaliSetSearchAddressDifference(adr, adr_last, device);
|
|
}
|
|
return adr;
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------------------------*/
|
|
|
|
void DaliProgramShortAddress(uint8_t shortadr, bool device = false);
|
|
void DaliProgramShortAddress(uint8_t shortadr, bool device) {
|
|
// The slave shall store the received 6-bit address (AAAAAA) as a short address if it is selected.
|
|
if (device) {
|
|
DaliDeviceSpecial(DALI_103_PROGRAM_SHORT_ADDRESS, shortadr); // IEC 62386-103 uses 0..63
|
|
int verified = DaliDeviceQuery(DaliDeviceFrame(DALI_103_SPECIAL_COMMAND, DALI_103_VERIFY_SHORT_ADDRESS, shortadr));
|
|
AddLog(LOG_LEVEL_INFO, PSTR("DLI: Set control device short address %d%s"), shortadr +1, (0xFF == verified) ? "" : " not verified");
|
|
} else {
|
|
DaliSendData(DALI_102_PROGRAM_SHORT_ADDRESS, (shortadr << 1) | DALI_SELECTOR_BIT);
|
|
AddLog(LOG_LEVEL_INFO, PSTR("DLI: Set short address %d"), shortadr +1);
|
|
}
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------------------------*/
|
|
|
|
uint32_t DaliCommission(uint32_t init_arg, uint32_t max_count) {
|
|
// Based on Shelly DALI Dimmer Gen3 received frames
|
|
// init_arg=11111111 : all without short address
|
|
// init_arg=00000000 : all
|
|
// init_arg=0AAAAAA1 : only for this shortadr
|
|
// returns number of new short addresses assigned
|
|
DaliSendData(DALI_BROADCAST_DP | DALI_SELECTOR_BIT, DALI_102_RESET); // Turns ON all lights
|
|
uint8_t arr[64];
|
|
uint32_t sa;
|
|
for (sa = 0; sa < 64; sa++) {
|
|
arr[sa] = 0;
|
|
}
|
|
delay(450); // It is not guaranteed that any commands will be received properly within the next 300ms
|
|
DaliSendData(DALI_102_SET_DTR0, 0xFF);
|
|
DaliSendData(DALI_BROADCAST_DP | DALI_SELECTOR_BIT, DALI_102_SET_SHORT_ADDRESS);
|
|
DaliSendData(DALI_102_TERMINATE, 0x00); // Terminate the DALI_102_INITIALISE command
|
|
delay(15);
|
|
// Start commissioning
|
|
DaliSendData(DALI_102_INITIALISE, init_arg);
|
|
DaliSendData(DALI_102_RANDOMISE, 0x00);
|
|
delay(100); // The new random address shall be available within a time period of 100ms.
|
|
|
|
uint32_t cnt = 0;
|
|
while (true) { // Find random addresses and assign unused short addresses
|
|
uint32_t adr = DaliFindAddress();
|
|
if (adr > 0xffffff) { break; } // No more random addresses found -> exit
|
|
for (sa = 0; sa < 64; sa++) { // Find first unused short address
|
|
if (0 == arr[sa]) { break; }
|
|
}
|
|
if (sa >= 64) { break; } // All 64 short addresses assigned -> exit
|
|
|
|
arr[sa] = 1; // Mark short address as used
|
|
cnt++;
|
|
DaliProgramShortAddress(sa); // Assign short address
|
|
DaliSendData(DALI_102_WITHDRAW, 0x00); // Remove the device from the search
|
|
delay(100);
|
|
OsWatchLoop(); // Feed blocked-loop watchdog
|
|
DaliSendData((sa << 1) | DALI_SELECTOR_BIT, DALI_102_OFF); // Turns OFF latest short address light
|
|
|
|
if (cnt >= max_count) { break; }
|
|
}
|
|
|
|
delay(100);
|
|
DaliSendData(DALI_102_TERMINATE, 0x00); // Terminate the DALI_102_INITIALISE command
|
|
|
|
uint32_t address = DALI_BROADCAST_DP;
|
|
#ifdef USE_LIGHT
|
|
DaliInitLight();
|
|
if (Settings->sbflag1.dali_light) { // DaliLight 1
|
|
address = DaliTarget2Address(Dali->Settings.target);
|
|
}
|
|
#endif // USE_LIGHT
|
|
DaliSendData(address, Dali->power[0]); // Restore lights
|
|
return cnt;
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------------------------*/
|
|
|
|
void DaliToggle(uint32_t adr, uint32_t *count) {
|
|
static uint32_t interval = 0;
|
|
|
|
if (*count && TimeReached(interval)) {
|
|
SetNextTimeInterval(interval, 600);
|
|
(*count)--;
|
|
DaliSendData(adr, (*count &1) ? 128 : 0); // Power toggle
|
|
}
|
|
delay(1);
|
|
}
|
|
|
|
/*********************************************************************************************\
|
|
* DALI Control Gear - Ballast or Sensor / Receiver
|
|
*
|
|
* Implemented servicing of POWER, DIMMER and Color Control as send by Tasmota
|
|
\*********************************************************************************************/
|
|
|
|
void ResponseAppendDali(uint32_t index) {
|
|
char number[12];
|
|
uint8_t dimmer = changeUIntScale(Dali->dimmer[index], 0, 254, 0, 100);
|
|
ResponseAppend_P(PSTR("\"DALI\":{\"Power%s\":\"%s\",\"Dimmer%s\":%d,\"Address\":%d,\"Command\":%d}"),
|
|
(0==index)?"":itoa(index+100, number, 10), GetStateText(Dali->power[index]),
|
|
(0==index)?"":itoa(index+100, number, 10), dimmer,
|
|
Dali->address, Dali->command);
|
|
}
|
|
|
|
void ResponseDali(uint32_t index) {
|
|
Response_P(PSTR("{"));
|
|
ResponseAppendDali(index);
|
|
ResponseJsonEnd();
|
|
}
|
|
|
|
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
|
|
|
|
#ifdef USE_LIGHT
|
|
bool DaliLoopSync(uint32_t channels) {
|
|
// Sync local light settings with DALI bus data
|
|
if (!Dali->allow_light ||
|
|
!Settings->sbflag1.dali_light || // DaliLight 1
|
|
(DaliTarget2Address(Dali->Settings.target) != Dali->address)) {
|
|
return false;
|
|
}
|
|
|
|
if (Settings->save_data) {
|
|
// Postpone save_data during fast color changes which results in exception 0 on ESP8266
|
|
TasmotaGlobal.save_data_counter = 4;
|
|
}
|
|
|
|
uint32_t rgb = 1; // RGB channel(s) present but not sure if powered ON
|
|
uint32_t waf = (channels > 3) ? 1 : 0;
|
|
for (uint32_t i = 0; i < channels; i++) {
|
|
if (Dali->color[i] > 0) {
|
|
if (i < 3) {
|
|
rgb = 2; // At least one RGB channel is powered ON
|
|
} else {
|
|
waf = 2; // At least one WAF (CCT) channel is powered ON
|
|
}
|
|
}
|
|
}
|
|
if ((2 == rgb) || (2 == waf)) {
|
|
uint8_t color[LST_MAX] = {}; // Init as 0
|
|
for (uint32_t i = 0; i < channels; i++) {
|
|
if (i < 3) {
|
|
color[i] = (2 == rgb) ? Dali->color[i] : Light.current_color[i];
|
|
} else {
|
|
color[i] = (2 == waf) ? Dali->color[i] : Light.current_color[i];
|
|
}
|
|
}
|
|
Dali->light_sync = millis(); // Block local loop
|
|
light_controller.changeChannels(color, 1);
|
|
LightSetScheme(LS_POWER);
|
|
}
|
|
Dali->light_sync = millis(); // Block local loop
|
|
ExecuteCommandPower(LightDevice(), (2 == rgb) ? 9 : 8, SRC_SWITCH);
|
|
if (waf && (Settings->param[P_RGB_REMAP] & 128)) { // SetOption37 128)
|
|
Dali->light_sync = millis(); // Block local loop
|
|
ExecuteCommandPower(LightDevice() +1, (2 == waf) ? 9 : 8, SRC_SWITCH);
|
|
}
|
|
return true;
|
|
}
|
|
#endif // USE_LIGHT
|
|
|
|
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
|
|
|
|
/*********************************************************************************************\
|
|
* DALI control gear power control shared by command DaliPower and input device bindings
|
|
\*********************************************************************************************/
|
|
|
|
uint32_t DaliSetPower(uint32_t target, uint32_t payload) {
|
|
// target 0 = broadcast, 1..64 = short address, 101..116 = group
|
|
// payload 0 = off, 1 = on to last dimmer state, 2 = toggle, 3..254 = dim level
|
|
// returns state index (0 = broadcast or short address, 1..16 = group)
|
|
uint32_t index = 0; // Broadcast
|
|
if ((target >= 101) && (target <= 116)) {
|
|
index = target - 100; // Group1 to 16
|
|
}
|
|
if (payload <= 2) {
|
|
if (2 == payload) {
|
|
payload = (Dali->power[index]) ? 0 : 1;
|
|
}
|
|
if (1 == payload) {
|
|
payload = Dali->dimmer[index];
|
|
}
|
|
}
|
|
uint32_t adr = DaliTarget2Address(target);
|
|
#ifdef DALI_POWER_OFF_NO_FADE
|
|
if (!payload) {
|
|
DaliSendData(adr | DALI_SELECTOR_BIT, DALI_102_OFF); // Power off without fade
|
|
} else
|
|
#endif // DALI_POWER_OFF_NO_FADE
|
|
DaliSendData(adr, payload); // DAPC command - dim level
|
|
return index;
|
|
}
|
|
|
|
/*********************************************************************************************\
|
|
* DALI-2 input device to control gear bindings
|
|
*
|
|
* A binding links push button events (IEC 62386-301) of one input device instance to a control
|
|
* gear target so common switch and rotary use cases need no rules. See mode table in header.
|
|
\*********************************************************************************************/
|
|
|
|
const char kDaliBindMode[] PROGMEM = "|Toggle|On|Off|Up|Down";
|
|
|
|
enum DaliBindMode { DALI_BIND_NONE, DALI_BIND_TOGGLE, DALI_BIND_ON, DALI_BIND_OFF, DALI_BIND_UP, DALI_BIND_DOWN, DALI_BIND_MAX };
|
|
|
|
// IEC 62386-301 push button event information used by bindings
|
|
enum DaliButtonEvent { DALI_BUTTON_SHORT_PRESS = 2, DALI_BUTTON_DOUBLE_PRESS = 5, DALI_BUTTON_LONG_PRESS_START = 9,
|
|
DALI_BUTTON_LONG_PRESS_REPEAT = 11, DALI_BUTTON_LONG_PRESS_STOP = 12 };
|
|
|
|
void DaliBindDim(uint32_t adr, bool down, bool start) {
|
|
// Dim target for 200 ms at fade rate. Switch on if needed at start of an upward dim.
|
|
uint32_t cmd = (down) ? DALI_102_DOWN : (start) ? DALI_102_ON_AND_STEP_UP : DALI_102_UP;
|
|
DaliSendData(adr | DALI_SELECTOR_BIT, cmd);
|
|
}
|
|
|
|
void DaliBindExecute(uint32_t slot, uint32_t info) {
|
|
DliBind_t *bind = &Dali->Settings.bind[slot];
|
|
uint32_t adr = DaliTarget2Address(bind->target);
|
|
uint32_t index = ((bind->target >= 101) && (bind->target <= 116)) ? bind->target - 100 : 0;
|
|
uint32_t mask = 1 << slot;
|
|
bool down = Dali->bind_down & mask;
|
|
|
|
switch (info) {
|
|
case DALI_BUTTON_SHORT_PRESS:
|
|
switch (bind->mode) {
|
|
case DALI_BIND_TOGGLE: {
|
|
uint32_t payload = 2; // Toggle using stored broadcast or group state
|
|
if ((bind->target >= 1) && (bind->target <= 64)) { // Short address state is not stored so ask the gear
|
|
int level = DaliSendWaitResponse(adr | DALI_SELECTOR_BIT, DALI_102_QUERY_ACTUAL_LEVEL);
|
|
if (level >= 0) { payload = (0 == level) ? 1 : 0; }
|
|
}
|
|
DaliSetPower(bind->target, payload);
|
|
break;
|
|
}
|
|
case DALI_BIND_ON:
|
|
DaliSetPower(bind->target, 1);
|
|
break;
|
|
case DALI_BIND_OFF:
|
|
DaliSetPower(bind->target, 0);
|
|
break;
|
|
case DALI_BIND_UP:
|
|
DaliBindDim(adr, false, true);
|
|
break;
|
|
case DALI_BIND_DOWN:
|
|
DaliBindDim(adr, true, false);
|
|
break;
|
|
}
|
|
break;
|
|
case DALI_BUTTON_DOUBLE_PRESS:
|
|
if ((DALI_BIND_TOGGLE == bind->mode) || (DALI_BIND_ON == bind->mode) || (DALI_BIND_UP == bind->mode)) {
|
|
DaliSetPower(bind->target, 254); // Full brightness
|
|
}
|
|
break;
|
|
case DALI_BUTTON_LONG_PRESS_START:
|
|
switch (bind->mode) {
|
|
case DALI_BIND_TOGGLE: // Alternate direction on every long press, always up when off
|
|
down = (Dali->power[index]) ? !down : false;
|
|
break;
|
|
case DALI_BIND_ON:
|
|
case DALI_BIND_UP:
|
|
down = false;
|
|
break;
|
|
case DALI_BIND_OFF:
|
|
case DALI_BIND_DOWN:
|
|
down = true;
|
|
break;
|
|
}
|
|
if (down) { Dali->bind_down |= mask; } else { Dali->bind_down &= ~mask; }
|
|
DaliBindDim(adr, down, true);
|
|
break;
|
|
case DALI_BUTTON_LONG_PRESS_REPEAT:
|
|
DaliBindDim(adr, down, false);
|
|
break;
|
|
case DALI_BUTTON_LONG_PRESS_STOP: { // Sync stored state with the level reached by dimming
|
|
int level = DaliSendWaitResponse(adr | DALI_SELECTOR_BIT, DALI_102_QUERY_ACTUAL_LEVEL);
|
|
if ((level >= 0) && (level <= 254)) { DaliSaveState(adr, level); }
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
uint32_t DaliBindEvent(int address, int instance, int type, uint32_t info) {
|
|
// Execute the first binding matching a push button event. Returns binding 1..DALI_MAX_BIND or 0 if none.
|
|
// Type is unknown (-1) for Device and DeviceInstance event schemes, so only reject known non push button types
|
|
if ((type != -1) && (type != 1)) { return 0; }
|
|
for (uint32_t slot = 0; slot < DALI_MAX_BIND; slot++) {
|
|
DliBind_t *bind = &Dali->Settings.bind[slot];
|
|
if (DALI_BIND_NONE == bind->mode) { continue; }
|
|
if (bind->device && (address != bind->device -1)) { continue; }
|
|
if ((bind->instance != 255) && (instance != bind->instance)) { continue; }
|
|
DaliBindExecute(slot, info);
|
|
return slot +1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*********************************************************************************************\
|
|
* DALI-2 input device event messages (IEC 62386-103 clause 9.7)
|
|
*
|
|
* Input devices like push buttons (301), absolute inputs (302), occupancy sensors (303) and
|
|
* light sensors (304) send 24-bit event messages. Bit 16 is always 0 for an event message
|
|
* (bit 16 = 1 marks a command sent to a control device by another application controller).
|
|
*
|
|
* Addressing scheme is selected by bits 23, 22 and 15:
|
|
* 0 - 0 Device 0AAAAAA0 0TTTTTEE EEEEEEEE short address + instance type
|
|
* 0 - 1 DeviceInstance 0AAAAAA0 1NNNNNEE EEEEEEEE short address + instance number
|
|
* 1 0 0 DeviceGroup 10GGGGG0 0TTTTTEE EEEEEEEE device group + instance type
|
|
* 1 1 0 InstanceGroup 11GGGGG0 0TTTTTEE EEEEEEEE instance group + instance type
|
|
* 1 0 1 Instance 10TTTTT0 1NNNNNEE EEEEEEEE instance type + instance number (default)
|
|
* 1 1 1 Reserved
|
|
*
|
|
* The decoded event is published for rules and MQTT as
|
|
* {"DALI":{"Event":"0x82840B","Scheme":"Instance","Type":1,"Instance":1,"Info":11,"Name":"LongPressRepeat"}}
|
|
\*********************************************************************************************/
|
|
|
|
const char kDaliEventScheme[] PROGMEM = "Device|DeviceInstance|DeviceGroup|InstanceGroup|Instance|Reserved";
|
|
|
|
// IEC 62386-301 Table 4 - Push button event information
|
|
const char kDaliPushButtonEvent[] PROGMEM =
|
|
"ButtonReleased|ButtonPressed|ShortPress|||DoublePress||||LongPressStart||LongPressRepeat|LongPressStop||ButtonFree|ButtonStuck";
|
|
|
|
void DaliEventMessage(uint32_t data) {
|
|
if (data & 0x010000) { return; } // Command to a control device, not an event message
|
|
|
|
uint32_t scheme = ((data >> 21) & 0x06) | ((data >> 15) & 0x01); // bit23 bit22 bit15
|
|
int address = -1;
|
|
int group = -1;
|
|
int instance_group = -1;
|
|
int type = -1;
|
|
int instance = -1;
|
|
uint32_t scheme_index;
|
|
switch (scheme) {
|
|
case 0: case 2: // Device
|
|
scheme_index = 0;
|
|
address = (data >> 17) & 0x3F;
|
|
type = (data >> 10) & 0x1F;
|
|
break;
|
|
case 1: case 3: // DeviceInstance
|
|
scheme_index = 1;
|
|
address = (data >> 17) & 0x3F;
|
|
instance = (data >> 10) & 0x1F;
|
|
break;
|
|
case 4: // DeviceGroup
|
|
scheme_index = 2;
|
|
group = (data >> 17) & 0x1F;
|
|
type = (data >> 10) & 0x1F;
|
|
break;
|
|
case 6: // InstanceGroup
|
|
scheme_index = 3;
|
|
instance_group = (data >> 17) & 0x1F;
|
|
type = (data >> 10) & 0x1F;
|
|
break;
|
|
case 5: // Instance
|
|
scheme_index = 4;
|
|
type = (data >> 17) & 0x1F;
|
|
instance = (data >> 10) & 0x1F;
|
|
break;
|
|
default: // Reserved
|
|
scheme_index = 5;
|
|
break;
|
|
}
|
|
uint32_t info = data & 0x3FF;
|
|
|
|
uint32_t bind = DaliBindEvent(address, instance, type, info); // Execute binding before building the response
|
|
|
|
char scheme_name[16];
|
|
GetTextIndexed(scheme_name, sizeof(scheme_name), scheme_index, kDaliEventScheme);
|
|
Response_P(PSTR("{\"DALI\":{\"Event\":\"0x%06X\",\"Scheme\":\"%s\""), data, scheme_name);
|
|
if (address >= 0) { ResponseAppend_P(PSTR(",\"Address\":%d"), address); }
|
|
if (group >= 0) { ResponseAppend_P(PSTR(",\"DeviceGroup\":%d"), group); }
|
|
if (instance_group >= 0) { ResponseAppend_P(PSTR(",\"InstanceGroup\":%d"), instance_group); }
|
|
if (type >= 0) { ResponseAppend_P(PSTR(",\"Type\":%d"), type); }
|
|
if (instance >= 0) { ResponseAppend_P(PSTR(",\"Instance\":%d"), instance); }
|
|
ResponseAppend_P(PSTR(",\"Info\":%d"), info);
|
|
if (bind) { ResponseAppend_P(PSTR(",\"Bind\":%d"), bind); }
|
|
|
|
switch (type) {
|
|
case 1: { // IEC 62386-301 Push button
|
|
if (info < 16) {
|
|
char event_name[16];
|
|
GetTextIndexed(event_name, sizeof(event_name), info, kDaliPushButtonEvent);
|
|
if (strlen(event_name)) { ResponseAppend_P(PSTR(",\"Name\":\"%s\""), event_name); }
|
|
}
|
|
break;
|
|
}
|
|
case 3: // IEC 62386-303 Occupancy sensor
|
|
ResponseAppend_P(PSTR(",\"Movement\":%d,\"Occupied\":%d,\"Repeat\":%d,\"Sensor\":\"%s\""),
|
|
(info & 0x01) ? 1 : 0, (info & 0x02) ? 1 : 0, (info & 0x04) ? 1 : 0, (info & 0x08) ? "Movement" : "Presence");
|
|
break;
|
|
case 4: // IEC 62386-304 Light sensor
|
|
ResponseAppend_P(PSTR(",\"Illuminance\":%d"), info);
|
|
break;
|
|
}
|
|
ResponseJsonEndEnd();
|
|
MqttPublishPrefixTopicRulesProcess_P(RESULT_OR_TELE, PSTR(D_PRFX_DALI));
|
|
}
|
|
|
|
/*********************************************************************************************\
|
|
* DALI-2 control device commissioning and queries (IEC 62386-103)
|
|
*
|
|
* Commissioning uses the 24-bit special commands INITIALISE, RANDOMISE, COMPARE, WITHDRAW and
|
|
* PROGRAM SHORT ADDRESS which mirror the 16-bit control gear sequence used by DaliScan.
|
|
\*********************************************************************************************/
|
|
|
|
const char kDaliInstanceType[] PROGMEM = "Generic|PushButton|AbsoluteInput|OccupancySensor|LightSensor|ColourSensor|GeneralPurpose";
|
|
|
|
/*-------------------------------------------------------------------------------------------*/
|
|
|
|
int DaliDeviceInstances(uint32_t device) {
|
|
// Return number of instances of a control device or -1 if not present
|
|
return DaliDeviceQuery(DaliDeviceFrame(DaliDeviceAddress(device), DALI_103_INSTANCE_DEVICE, DALI_103_QUERY_NUMBER_OF_INSTANCES));
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------------------------*/
|
|
|
|
uint32_t DaliDeviceCommission(uint32_t init_arg, uint32_t max_count) {
|
|
// init_arg = DALI_103_INITIALISE_ALL (0x7F) : clear all short addresses and commission all control devices
|
|
// init_arg = DALI_103_INITIALISE_UNADDRESSED (0xFF) : commission control devices without short address only
|
|
// returns number of new short addresses assigned
|
|
uint8_t arr[64] = { 0 };
|
|
uint32_t sa;
|
|
|
|
if (DALI_103_INITIALISE_ALL == init_arg) { // Clear all short addresses
|
|
DaliDeviceSpecial(DALI_103_DTR0, DALI_103_MASK);
|
|
DaliDeviceSend(DaliDeviceFrame(DALI_103_BROADCAST, DALI_103_INSTANCE_DEVICE, DALI_103_SET_SHORT_ADDRESS), true);
|
|
} else { // Keep short addresses already in use
|
|
for (sa = 0; sa < 64; sa++) { // Takes about 2500 ms
|
|
if (DaliDeviceInstances(sa +1) >= 0) { arr[sa] = 1; }
|
|
OsWatchLoop(); // Feed blocked-loop watchdog
|
|
}
|
|
}
|
|
DaliDeviceSpecial(DALI_103_TERMINATE, 0x00); // Terminate any pending DALI_103_INITIALISE
|
|
delay(15);
|
|
// Start commissioning
|
|
DaliDeviceSpecial(DALI_103_INITIALISE, init_arg, true);
|
|
DaliDeviceSpecial(DALI_103_RANDOMISE, 0x00, true);
|
|
delay(100); // The new random address shall be available within a time period of 100ms.
|
|
|
|
uint32_t cnt = 0;
|
|
while (true) { // Find random addresses and assign unused short addresses
|
|
uint32_t adr = DaliFindAddress(true);
|
|
if (adr > 0xffffff) { break; } // No more random addresses found -> exit
|
|
for (sa = 0; sa < 64; sa++) { // Find first unused short address
|
|
if (0 == arr[sa]) { break; }
|
|
}
|
|
if (sa >= 64) { break; } // All 64 short addresses assigned -> exit
|
|
|
|
arr[sa] = 1; // Mark short address as used
|
|
cnt++;
|
|
DaliProgramShortAddress(sa, true); // Assign short address
|
|
DaliDeviceSpecial(DALI_103_WITHDRAW, 0x00); // Remove the device from the search
|
|
delay(100);
|
|
OsWatchLoop(); // Feed blocked-loop watchdog
|
|
|
|
if (cnt >= max_count) { break; }
|
|
}
|
|
|
|
delay(100);
|
|
DaliDeviceSpecial(DALI_103_TERMINATE, 0x00); // Terminate the DALI_103_INITIALISE command
|
|
return cnt;
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------------------------*/
|
|
|
|
bool DaliDeviceSetEventScheme(uint32_t device, uint32_t scheme) {
|
|
// Set event scheme 0..4 of all instances of a control device (0 = broadcast)
|
|
if (scheme > 4) { return false; }
|
|
DaliDeviceSpecial(DALI_103_DTR0, scheme);
|
|
DaliDeviceSend(DaliDeviceFrame(DaliDeviceAddress(device), DALI_103_INSTANCE_BROADCAST, DALI_103_SET_EVENT_SCHEME), true);
|
|
return true;
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------------------------*/
|
|
|
|
bool ResponseDaliDevice(uint32_t device) {
|
|
// {"DaliDevice":{"Device":1,"Version":"2.0","Status":0,"Instances":[{"Instance":0,"Type":1,"Name":"PushButton","Enabled":1,"Scheme":"Instance"}]}}
|
|
int instances = DaliDeviceInstances(device);
|
|
if (instances < 0) { return false; } // No control device with this short address
|
|
|
|
uint32_t address = DaliDeviceAddress(device);
|
|
int version = DaliDeviceQuery(DaliDeviceFrame(address, DALI_103_INSTANCE_DEVICE, DALI_103_QUERY_VERSION_NUMBER));
|
|
int status = DaliDeviceQuery(DaliDeviceFrame(address, DALI_103_INSTANCE_DEVICE, DALI_103_QUERY_DEVICE_STATUS));
|
|
|
|
ResponseCmnd();
|
|
ResponseAppend_P(PSTR("{\"Device\":%d"), device);
|
|
if (version >= 0) { ResponseAppend_P(PSTR(",\"Version\":\"%d.%d\""), version >> 2, version & 0x03); }
|
|
if (status >= 0) { ResponseAppend_P(PSTR(",\"Status\":%d"), status); }
|
|
ResponseAppend_P(PSTR(",\"Instances\":["));
|
|
for (int instance = 0; instance < instances; instance++) {
|
|
int type = DaliDeviceQuery(DaliDeviceFrame(address, instance, DALI_103_QUERY_INSTANCE_TYPE));
|
|
int enabled = DaliDeviceQuery(DaliDeviceFrame(address, instance, DALI_103_QUERY_INSTANCE_ENABLED));
|
|
int scheme = DaliDeviceQuery(DaliDeviceFrame(address, instance, DALI_103_QUERY_EVENT_SCHEME));
|
|
ResponseAppend_P(PSTR("%s{\"Instance\":%d"), (instance) ? "," : "", instance);
|
|
if (type >= 0) {
|
|
ResponseAppend_P(PSTR(",\"Type\":%d"), type);
|
|
if (type < 7) {
|
|
char type_name[16];
|
|
GetTextIndexed(type_name, sizeof(type_name), type, kDaliInstanceType);
|
|
ResponseAppend_P(PSTR(",\"Name\":\"%s\""), type_name);
|
|
}
|
|
}
|
|
if (enabled >= 0) { ResponseAppend_P(PSTR(",\"Enabled\":%d"), (0xFF == enabled) ? 1 : 0); }
|
|
if ((scheme >= 0) && (scheme <= 4)) {
|
|
// eventScheme 0..4 = Instance, Device, DeviceInstance, DeviceGroup, InstanceGroup. kDaliEventScheme starts at Device so rotate by 4
|
|
char scheme_name[16];
|
|
GetTextIndexed(scheme_name, sizeof(scheme_name), (scheme + 4) % 5, kDaliEventScheme);
|
|
ResponseAppend_P(PSTR(",\"Scheme\":\"%s\""), scheme_name);
|
|
}
|
|
ResponseJsonEnd();
|
|
OsWatchLoop(); // Feed blocked-loop watchdog
|
|
}
|
|
ResponseAppend_P(PSTR("]"));
|
|
ResponseJsonEndEnd();
|
|
return true;
|
|
}
|
|
|
|
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
|
|
|
|
void DaliLoop(void) {
|
|
while (Dali->dali->available()) {
|
|
uint32_t queue = Dali->dali->available();
|
|
|
|
DaliFrame frame = Dali->dali->read();
|
|
|
|
uint32_t bit_count = frame.meta & TM_DALI_BIT_COUNT_MASK;
|
|
bool collision = frame.meta & TM_DALI_COLLISION;
|
|
AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("DLI: Rx 0x%08X %2d queue %d%s%s"),
|
|
frame.data, bit_count, queue, (8 == bit_count)?" backward":"", (collision)?" collision":"");
|
|
|
|
if (1 == Dali->probe) { continue; } // Probe only
|
|
|
|
if (24 == frame.meta) { // DALI-2 24-bit forward frame (event message or control device command)
|
|
DaliEventMessage(frame.data);
|
|
continue;
|
|
}
|
|
if (frame.meta != 16) { continue; } // Skip backward frames and frames with collision
|
|
|
|
Dali->address = (frame.data >> 8) &0xFF;
|
|
Dali->command = frame.data &0xFF;
|
|
|
|
#ifdef USE_LIGHT
|
|
if (DALI_102_SET_DTR0 == Dali->address) { Dali->dtr[0] = Dali->command; } // Might be Red / White
|
|
else if (DALI_102_SET_DTR1 == Dali->address) { Dali->dtr[1] = Dali->command; } // Might be Green / Amber
|
|
else if (DALI_102_SET_DTR2 == Dali->address) { Dali->dtr[2] = Dali->command; } // Might be Blue
|
|
else if (DALI_209_SET_TEMPORARY_RGB_DIMLEVEL == Dali->command) {
|
|
Dali->color[0] = Dali->dtr[0]; // Red
|
|
Dali->color[1] = Dali->dtr[1]; // Green
|
|
Dali->color[2] = Dali->dtr[2]; // Blue
|
|
}
|
|
else if (DALI_209_SET_TEMPORARY_WAF_DIMLEVEL == Dali->command) {
|
|
Dali->color[3] = Dali->dtr[0]; // Cold White
|
|
Dali->color[4] = Dali->dtr[1]; // Warm White (Amber)
|
|
}
|
|
else if (DALI_209_ACTIVATE == Dali->command) {
|
|
uint32_t channels = Dali->Settings.light_type -8;
|
|
if ((Dali->target_rgbwaf > 0) && (channels > 0)) { // Color control
|
|
Dali->address &= 0xFE; // Reset DALI_SELECTOR_BIT set
|
|
DaliLoopSync(channels); // Sync local light settings with DALI bus data
|
|
}
|
|
} else
|
|
#endif // USE_LIGHT
|
|
if (!(Dali->address & DALI_SELECTOR_BIT)) { // Address = DAPC command
|
|
uint32_t index = DaliSaveState(Dali->address, Dali->command); // Update dimmer and power
|
|
bool show_response = true;
|
|
#ifdef USE_LIGHT
|
|
Dali->color[0] = Dali->command; // Contains "dimmer" value 0..254
|
|
if (DaliLoopSync(1)) { // Sync local light settings with DALI bus data
|
|
show_response = false;
|
|
}
|
|
#endif // USE_LIGHT
|
|
if (show_response) {
|
|
ResponseDali(index);
|
|
MqttPublishPrefixTopicRulesProcess_P(RESULT_OR_TELE, PSTR(D_PRFX_DALI));
|
|
}
|
|
}
|
|
// yield();
|
|
delay(0); // Prevent watchdog crashes
|
|
}
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------------------------*/
|
|
|
|
void DaliEverySecond(void) {
|
|
if (5 == TasmotaGlobal.uptime) {
|
|
DaliInitLight();
|
|
}
|
|
}
|
|
|
|
/*********************************************************************************************\
|
|
* DALI Control Device - Controller / Transmitter
|
|
*
|
|
* Implements Tasmota light POWER, DIMMER and Color Control if `DaliLight 1`
|
|
\*********************************************************************************************/
|
|
|
|
#ifdef USE_LIGHT
|
|
bool DaliSetChannels(void) {
|
|
if (Settings->sbflag1.dali_light) { // DaliLight 1
|
|
Settings->light_fade = 0; // Use Dali fading
|
|
Settings->light_correction = 0; // Use Dali light correction
|
|
if (Dali->light_sync) { // Block local loop
|
|
uint32_t light_sync = Dali->light_sync;
|
|
Dali->light_sync = 0;
|
|
if (TimePassedSince(light_sync) < 200) { // Time it can take to call DaliSetChannels() from Dali received data
|
|
return true;
|
|
}
|
|
}
|
|
uint8_t *cur_col = (uint8_t*)XdrvMailbox.data;
|
|
// cur_col[0] = Red, cur_col[1] = Green, cur_col[2] = Blue, cur_col[3] = Cold = White, cur_col[4] = Warm = Amber
|
|
for (uint32_t i = 0; i < 5; i++) {
|
|
if (255 == cur_col[i]) { cur_col[i] = 254; } // Max Dali value
|
|
}
|
|
uint32_t adr = DaliTarget2Address(Dali->Settings.target);
|
|
uint32_t channels = Dali->Settings.light_type -8;
|
|
if ((Dali->target_rgbwaf > 0) && (channels > 0)) { // Color control
|
|
adr |= DALI_SELECTOR_BIT; // Enable Selector bit
|
|
|
|
#ifdef DALI_POWER_OFF_NO_FADE
|
|
uint32_t power_on = 0;
|
|
for (uint32_t i = 0; i < channels; i++) {
|
|
power_on += cur_col[i];
|
|
}
|
|
if (!power_on) {
|
|
DaliSendData(adr, DALI_102_OFF); // Power off without fade
|
|
return true;
|
|
}
|
|
#endif // DALI_POWER_OFF_NO_FADE
|
|
|
|
#ifdef DALI_LIGHT_NO_READ_AFTER_WRITE
|
|
// This takes 310ms for 3 channels but might send bad data as no DTR read-after-write
|
|
DaliSendData(DALI_102_SET_DTR0, 0x7F); // Linked Channel control
|
|
DaliSendData(DALI_102_ENABLE_DEVICE_TYPE_X, DALI_209_DEVICE_TYPE); // Enable Extended command
|
|
DaliSendData(adr, DALI_209_SET_TEMPORARY_RGBWAF_CONTROL);
|
|
|
|
DaliSendData(DALI_102_SET_DTR0, cur_col[0]); // DALI Red
|
|
DaliSendData(DALI_102_SET_DTR1, (channels > 1) ? cur_col[1] : 255); // DALI Green
|
|
DaliSendData(DALI_102_SET_DTR2, (channels > 2) ? cur_col[2] : 255); // DALI Blue
|
|
DaliSendData(DALI_102_ENABLE_DEVICE_TYPE_X, DALI_209_DEVICE_TYPE); // Enable Extended command
|
|
DaliSendData(adr, DALI_209_SET_TEMPORARY_RGB_DIMLEVEL);
|
|
|
|
if (channels > 3) {
|
|
DaliSendData(DALI_102_SET_DTR0, cur_col[3]); // DALI White
|
|
DaliSendData(DALI_102_SET_DTR1, (channels > 4) ? cur_col[4] : 255); // DALI Amber
|
|
DaliSendData(DALI_102_SET_DTR2, 255); // DALI Freecolour - no change
|
|
DaliSendData(DALI_102_ENABLE_DEVICE_TYPE_X, DALI_209_DEVICE_TYPE); // Enable Extended command
|
|
DaliSendData(adr, DALI_209_SET_TEMPORARY_WAF_DIMLEVEL);
|
|
}
|
|
#else
|
|
// This takes 480ms for 3 channels but might send nothing if DTR read-after-write fails
|
|
if (!DaliSetDTR(0, adr, 0x7F)) { return true; } // Linked Channel control
|
|
DaliSendData(DALI_102_ENABLE_DEVICE_TYPE_X, DALI_209_DEVICE_TYPE); // Enable Extended command
|
|
DaliSendData(adr, DALI_209_SET_TEMPORARY_RGBWAF_CONTROL);
|
|
|
|
if (!DaliSetDTR(0, adr, cur_col[0])) { return true; } // DALI Red
|
|
if (!DaliSetDTR(1, adr, (channels > 1) ? cur_col[1] : 255)) { return true; } // DALI Green
|
|
if (!DaliSetDTR(2, adr, (channels > 2) ? cur_col[2] : 255)) { return true; } // DALI Blue
|
|
DaliSendData(DALI_102_ENABLE_DEVICE_TYPE_X, DALI_209_DEVICE_TYPE); // Enable Extended command
|
|
DaliSendData(adr, DALI_209_SET_TEMPORARY_RGB_DIMLEVEL);
|
|
|
|
if (channels > 3) {
|
|
if (!DaliSetDTR(0, adr, cur_col[3])) { return true; } // DALI While
|
|
if (!DaliSetDTR(1, adr, (channels > 4) ? cur_col[4] : 255)) { return true; } // DALI Amber
|
|
if (!DaliSetDTR(2, adr, 255)) { return true; } // DALI Freecolour - no change
|
|
DaliSendData(DALI_102_ENABLE_DEVICE_TYPE_X, DALI_209_DEVICE_TYPE); // Enable Extended command
|
|
DaliSendData(adr, DALI_209_SET_TEMPORARY_WAF_DIMLEVEL);
|
|
}
|
|
#endif // DALI_LIGHT_NO_READ_AFTER_WRITE
|
|
DaliSendData(DALI_102_ENABLE_DEVICE_TYPE_X, DALI_209_DEVICE_TYPE); // Enable Extended command
|
|
DaliSendData(adr, DALI_209_ACTIVATE);
|
|
return true;
|
|
}
|
|
|
|
#ifdef DALI_POWER_OFF_NO_FADE
|
|
if (!cur_col[0]) {
|
|
DaliSendData(adr | DALI_SELECTOR_BIT, DALI_102_OFF); // Power off without fade
|
|
return true;
|
|
}
|
|
#endif // DALI_POWER_OFF_NO_FADE
|
|
DaliSendData(adr, cur_col[0]); // DAPC command - dim level
|
|
}
|
|
return true;
|
|
}
|
|
#endif // USE_LIGHT
|
|
|
|
/*********************************************************************************************\
|
|
* DALI Tasmota init
|
|
\*********************************************************************************************/
|
|
|
|
bool DaliInit(uint32_t function) {
|
|
int pin_tx = -1;
|
|
bool invert_tx = false;
|
|
if (PinUsed(GPIO_DALI_TX)) {
|
|
pin_tx = Pin(GPIO_DALI_TX);
|
|
}
|
|
else if (PinUsed(GPIO_DALI_TX_INV)) {
|
|
pin_tx = Pin(GPIO_DALI_TX_INV);
|
|
invert_tx = true;
|
|
}
|
|
int pin_rx = -1;
|
|
bool invert_rx = false;
|
|
if (PinUsed(GPIO_DALI_RX)) {
|
|
pin_rx = Pin(GPIO_DALI_RX);
|
|
}
|
|
else if (PinUsed(GPIO_DALI_RX_INV)) {
|
|
pin_rx = Pin(GPIO_DALI_RX_INV);
|
|
invert_rx = true;
|
|
}
|
|
if ((-1 == pin_tx) || (-1 == pin_rx)) { return false; }
|
|
|
|
Dali = (DALI*)calloc(sizeof(DALI), 1); // Need calloc to reset registers to 0/false
|
|
if (!Dali) { return false; }
|
|
DaliSettingsLoad(0);
|
|
|
|
for (uint32_t i = 0; i < DALI_MAX_STORED; i++) {
|
|
Dali->dimmer[i] = DALI_INIT_STATE;
|
|
}
|
|
|
|
Dali->dali = new TasmotaDali(pin_rx, pin_tx, invert_rx, invert_tx);
|
|
if (!Dali->dali->begin()) { return false; }
|
|
|
|
Dali->allow_light = (FUNC_MODULE_INIT == function); // Light control is possible
|
|
|
|
AddLog(LOG_LEVEL_INFO, PSTR("DLI: GPIO%d(RX%s) and GPIO%d(TX%s)%s"),
|
|
pin_rx, (invert_rx)?"i":"", pin_tx, (invert_tx)?"i":"", (Dali->allow_light)?" as light":"");
|
|
|
|
#ifdef DALI_DEBUG
|
|
Dali->log_level = LOG_LEVEL_DEBUG;
|
|
#endif // DALI_DEBUG
|
|
|
|
if (!Dali->allow_light) {
|
|
Settings->sbflag1.dali_light = false; // No light control possible
|
|
}
|
|
#ifdef USE_LIGHT
|
|
if (!Settings->sbflag1.dali_light) { // DaliLight 0
|
|
return false;
|
|
}
|
|
|
|
Settings->light_fade = 0; // Use Dali fading instead
|
|
Settings->light_correction = 0; // Use Dali light correction
|
|
Settings->flag3.pwm_multi_channels = 0; // SetOption68 0 - Enable multi-channels PWM instead of Color PWM
|
|
UpdateDevicesPresent(1);
|
|
|
|
TasmotaGlobal.light_type = LT_W; // Single channel
|
|
|
|
/*
|
|
Dali->target_rgbwaf = DaliQueryRGBWAF(DaliTarget2Address(Dali->Settings.target));
|
|
if (Dali->target_rgbwaf > 1) {
|
|
TasmotaGlobal.light_type = Dali->Settings.light_type;
|
|
if ((TasmotaGlobal.light_type >= LT_RGBW) && // RGBW or RGBCW
|
|
(Settings->param[P_RGB_REMAP] & 128)) { // SetOption37 128
|
|
UpdateDevicesPresent(1); // We manage RGB and W separately, hence adding a device
|
|
}
|
|
}
|
|
*/
|
|
|
|
uint32_t probe_retry = Dali->Settings.probe_retry +1;
|
|
while (probe_retry--) {
|
|
Dali->target_rgbwaf = DaliQueryRGBWAF(DaliTarget2Address(Dali->Settings.target));
|
|
if (Dali->target_rgbwaf > 1) {
|
|
TasmotaGlobal.light_type = Dali->Settings.light_type;
|
|
if ((TasmotaGlobal.light_type >= LT_RGBW) && // RGBW or RGBCW
|
|
(Settings->param[P_RGB_REMAP] & 128)) { // SetOption37 128
|
|
UpdateDevicesPresent(1); // We manage RGB and W separately, hence adding a device
|
|
}
|
|
break;
|
|
}
|
|
if (probe_retry) {
|
|
AddLog(LOG_LEVEL_DEBUG, PSTR("DLI: Retry target probe %d"), Dali->Settings.probe_retry +1 - probe_retry);
|
|
delay(1000); // Power-On probe delay for DALI targets to get ready
|
|
}
|
|
}
|
|
|
|
return true;
|
|
#else
|
|
return false;
|
|
#endif // USE_LIGHT
|
|
}
|
|
|
|
/*********************************************************************************************\
|
|
* Commands
|
|
\*********************************************************************************************/
|
|
|
|
const char kDALICommands[] PROGMEM = D_PRFX_DALI "|" // Prefix
|
|
"|" D_CMND_POWER "|" D_CMND_DIMMER
|
|
#ifdef USE_LIGHT
|
|
"|Light|Target|Channels|Retry"
|
|
#endif // USE_LIGHT
|
|
"|Send|Query|Scan|Group|GroupSliders|BS|Gear|Device|DeviceScan|Bind";
|
|
|
|
void (* const DALICommand[])(void) PROGMEM = {
|
|
&CmndDali, &CmndDaliPower, &CmndDaliDimmer,
|
|
#ifdef USE_LIGHT
|
|
&CmndDaliLight, &CmndDaliTarget, &CmndDaliChannels, &CmndDaliRetry,
|
|
#endif // USE_LIGHT
|
|
&CmndDaliSend, &CmndDaliQuery, &CmndDaliScan, &CmndDaliGroup, &CmndDaliGroupSliders, &CmndDaliBroadcastSlider, &CmndDaliGear,
|
|
&CmndDaliDevice, &CmndDaliDeviceScan, &CmndDaliBind };
|
|
|
|
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;
|
|
uint32_t DALIaddr = DALI_BROADCAST_DP;
|
|
|
|
JsonParserToken val = root[PSTR("cmd")];
|
|
if (val) {
|
|
uint32_t cmd = val.getUInt();
|
|
val = root[PSTR("addr")];
|
|
if (val) {
|
|
uint32_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) {
|
|
uint32_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;
|
|
}
|
|
|
|
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
|
|
|
|
void CmndDali(void) {
|
|
// Dali {"addr":254,"cmd":100} - Any address and/or command
|
|
// Dali 0|1 - Enable DALI receive probe
|
|
// Dali 2 - Disable debug log output
|
|
if (XdrvMailbox.data_len > 0) {
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 2)) {
|
|
Dali->probe = XdrvMailbox.payload;
|
|
#ifdef DALI_DEBUG
|
|
if (0 == Dali->probe) {
|
|
Dali->log_level = LOG_LEVEL_DEBUG;
|
|
}
|
|
else if (2 == Dali->probe) {
|
|
Dali->log_level = LOG_LEVEL_DEBUG_MORE +1;
|
|
}
|
|
#endif // DALI_DEBUG
|
|
ResponseCmndNumber(Dali->probe);
|
|
return;
|
|
}
|
|
DaliJsonParse();
|
|
}
|
|
ResponseDali(0);
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------------------------*/
|
|
|
|
void CmndDaliPower(void) {
|
|
// DaliPower 0 - Broadcast power off
|
|
// DaliPower 1 - Broadcast power on to last dimmer state
|
|
// DaliPower 2 - Broadcast toggle power off or last dimmer state
|
|
// DaliPower 3..254 - Broadcast equals DaliDimmer command
|
|
// DaliPower 0..254 - Broadcast control
|
|
// DaliPower0 0..254 - Broadcast control (= DaliPower)
|
|
// DaliPower1 0..254 - Short address 0 control
|
|
// DaliPower3 0..254 - Short address 2 control
|
|
uint32_t index = 0; // Broadcast
|
|
if ((XdrvMailbox.index >= 101) && (XdrvMailbox.index <= 116)) {
|
|
index = XdrvMailbox.index - 100; // Group1 to 16
|
|
}
|
|
if (((XdrvMailbox.index >= 0) && (XdrvMailbox.index <= 64)) ||
|
|
((XdrvMailbox.index >= 101) && (XdrvMailbox.index <= 116))) {
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 254)) {
|
|
index = DaliSetPower(XdrvMailbox.index, XdrvMailbox.payload);
|
|
}
|
|
}
|
|
ResponseDali(index);
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------------------------*/
|
|
|
|
void CmndDaliDimmer(void) {
|
|
// DaliDimmer 0..100 - Broadcast set power off or dimmer state
|
|
// DaliDimmer0 0..100 - Broadcast set power off or dimmer state
|
|
// DaliDimmer1 0..100 - Short address 0 set power off or dimmer state
|
|
// DaliDimmer3 0..100 - Short address 2 set power off or dimmer state
|
|
uint32_t index = 0; // Broadcast
|
|
if ((XdrvMailbox.index >= 101) && (XdrvMailbox.index <= 116)) {
|
|
index = XdrvMailbox.index - 100; // Group1 to 16
|
|
}
|
|
if (((XdrvMailbox.index >= 0) && (XdrvMailbox.index <= 64)) ||
|
|
((XdrvMailbox.index >= 101) && (XdrvMailbox.index <= 116))) {
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 100)) {
|
|
uint8_t dimmer = changeUIntScale(XdrvMailbox.payload, 0, 100, 0, 254);
|
|
uint32_t adr = DaliTarget2Address(XdrvMailbox.index);
|
|
#ifdef DALI_POWER_OFF_NO_FADE
|
|
if (!dimmer) {
|
|
DaliSendData(adr | DALI_SELECTOR_BIT, DALI_102_OFF); // Power off without fade
|
|
} else
|
|
#endif // DALI_POWER_OFF_NO_FADE
|
|
DaliSendData(adr, dimmer); // DAPC command - dim level
|
|
}
|
|
}
|
|
ResponseDali(index);
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------------------------*/
|
|
|
|
void CmndDaliGroup(void) {
|
|
// DaliGroup1 1,2 - Add device 1 and 2 to group 1
|
|
// DaliGroup1 -1,2 - Remove device 1 and 2 to group 1
|
|
// DaliGroup1 b - Blink group devices twice
|
|
if ((XdrvMailbox.index >= 1) && (XdrvMailbox.index <= 16)) {
|
|
uint32_t group = XdrvMailbox.index -1;
|
|
bool more = false;
|
|
char temp[200] = { 0 };
|
|
uint32_t tcount = 0;
|
|
uint32_t adr = DaliTarget2Address(group + 101);
|
|
if (XdrvMailbox.data_len) {
|
|
if ('b' == XdrvMailbox.data[0]) { // Blink devices
|
|
tcount = 4;
|
|
XdrvMailbox.data++;
|
|
XdrvMailbox.data_len--;
|
|
}
|
|
}
|
|
if (XdrvMailbox.data_len) {
|
|
uint32_t command = DALI_102_ADD_TO_GROUP0;
|
|
temp[0] = '+';
|
|
if ('+' == XdrvMailbox.data[0]) { // Add devices
|
|
XdrvMailbox.data++;
|
|
XdrvMailbox.data_len--;
|
|
}
|
|
else if ('-' == XdrvMailbox.data[0]) { // Remove devices
|
|
command = DALI_102_REMOVE_FROM_GROUP0;
|
|
XdrvMailbox.data++;
|
|
XdrvMailbox.data_len--;
|
|
temp[0] = '-';
|
|
}
|
|
uint32_t argc = ArgC(); // Number of devices
|
|
if (argc) {
|
|
command |= group;
|
|
uint32_t sas[argc];
|
|
ParseParameters(argc, sas);
|
|
for (uint32_t arg = 0; arg < argc; arg++) {
|
|
uint32_t sa = sas[arg] -1;
|
|
if (sa < 64) {
|
|
snprintf_P(temp, sizeof(temp), PSTR("%s%s%d"), temp, (more)?",":"", sa +1);
|
|
more = true;
|
|
DaliSendData((sa << 1) | DALI_SELECTOR_BIT, command);
|
|
}
|
|
}
|
|
while (tcount) { DaliToggle(adr, &tcount); }
|
|
ResponseCmndIdxChar(temp);
|
|
}
|
|
} else {
|
|
uint32_t command = DALI_102_QUERY_GROUPS_0_7;
|
|
uint32_t bitmask = 1 << group;
|
|
if (group > 7) {
|
|
command = DALI_102_QUERY_GROUPS_8_15;
|
|
bitmask = 1 << group - 8;
|
|
}
|
|
for (uint32_t sa = 0; sa < Dali->Settings.max_gear; sa++) { // Scanning 64 addresses takes about 2500 ms
|
|
if (tcount) { DaliToggle(adr, &tcount); }
|
|
int result = DaliSendWaitResponse((sa << 1) | DALI_SELECTOR_BIT, command, 20);
|
|
if ((result >= 0) && (result & bitmask)) {
|
|
snprintf_P(temp, sizeof(temp), PSTR("%s%s%d"), temp, (more)?",":"", sa +1);
|
|
more = true;
|
|
}
|
|
}
|
|
if (!strlen(temp)) {
|
|
snprintf_P(temp, sizeof(temp), PSTR("None"));
|
|
} else {
|
|
while (tcount) { DaliToggle(adr, &tcount); }
|
|
}
|
|
ResponseCmndIdxChar(temp);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------------------------*/
|
|
|
|
void CmndDaliGear(void) {
|
|
// DaliGear[2] [<max_address>|<address>] - Scan bus for up to <max_address> devices and toggle output twice
|
|
// DaliGear - Scan bus for 64 devices taking around 2.5 sec
|
|
// DaliGear 15 - Scan bus for up to 15 devices
|
|
// DaliGear2 - Scan bus and toggle output twice
|
|
// DaliGear2 4 - Toggle output twice for device 4 only
|
|
uint32_t toggle_count = 0;
|
|
uint32_t start = 0;
|
|
uint32_t end = Dali->Settings.max_gear;
|
|
uint32_t payload = ((XdrvMailbox.payload >= 1) && (XdrvMailbox.payload <= 64)) ? XdrvMailbox.payload : 0;
|
|
if (1 == XdrvMailbox.index) {
|
|
if (payload) {
|
|
end = payload;
|
|
Dali->Settings.max_gear = end;
|
|
}
|
|
}
|
|
else if (2 == XdrvMailbox.index) {
|
|
toggle_count = 4;
|
|
if (payload) {
|
|
start = payload -1;
|
|
end = payload;
|
|
}
|
|
}
|
|
char temp[200] = { 0 };
|
|
uint32_t count = 0;
|
|
for (uint32_t address = start; address < end; address++) { // Scanning 64 addresses takes about 2500 ms
|
|
uint32_t adr = address << 1;
|
|
uint32_t tcount = toggle_count;
|
|
if (DaliSendWaitResponse(adr | DALI_SELECTOR_BIT, DALI_102_QUERY_CONTROL_GEAR_PRESENT, 20) >= 0) {
|
|
snprintf_P(temp, sizeof(temp), PSTR("%s%s%d"), temp, (count)?",":"", address +1);
|
|
count++;
|
|
AddLog(LOG_LEVEL_DEBUG, PSTR("DLI: Device %d at %d, short address %d"), count, address, adr);
|
|
while (tcount) { DaliToggle(adr, &tcount); }
|
|
}
|
|
}
|
|
ResponseCmnd();
|
|
ResponseAppend_P(PSTR("%d,\"Present\":%d,\"Address\":[%s]}"), Dali->Settings.max_gear, count, temp);
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------------------------*/
|
|
|
|
void CmndDaliSend(void) {
|
|
// Send command
|
|
// DaliSend 0xa5,255 - DALI Initialise
|
|
// DaliSend 6,3,0xe2 - DALI DT6 (6) for address 1 (3) extended command disable current protector (0xe2)
|
|
// DaliSend 0x01,0xa3,0x2d,254 - Set Power On level (0x2d) for address 0 (0x01) to 254 only if Read Power On level (0xa3) is different
|
|
// DaliSend6 - DT6 = 207 = Extended LED commands 224...236
|
|
// DaliSend8 - DT8 = 209 = Extended colour commands 224...246
|
|
// DaliSend255 <bitcount>,<value> - Dali-2 24-bit event frame
|
|
// DaliSend256 <bitcount>,<value> - Dali-2 24-bit event frame send twice
|
|
|
|
uint32_t values[5] = { 0 };
|
|
uint32_t params = ParseParameters(5, values);
|
|
|
|
#ifdef DALI_DEBUG
|
|
AddLog(Dali->log_level, PSTR("DLI: index %d, params %d, values %i,%i,%i,%i,%i"), XdrvMailbox.index, params, values[0], values[1], values[2], values[3], values[4]);
|
|
#endif // DALI_DEBUG
|
|
|
|
if (255 == XdrvMailbox.index) { // DaliSend255 <bitcount>,<value> - Dali-2 24-bit event frame
|
|
if (params >= 2) {
|
|
DaliSendData((values[0] & TM_DALI_BIT_COUNT_MASK) | TM_DALI_EVENT_FRAME, values[1]);
|
|
ResponseCmndDone();
|
|
}
|
|
return;
|
|
}
|
|
if (256 == XdrvMailbox.index) { // DaliSend256 <bitcount>,<value> - Dali-2 24-bit event frame send twice
|
|
if (params >= 2) {
|
|
DaliSendData((values[0] & TM_DALI_BIT_COUNT_MASK) | TM_DALI_EVENT_FRAME | TM_DALI_SEND_TWICE, values[1]);
|
|
ResponseCmndDone();
|
|
}
|
|
return;
|
|
}
|
|
if (DALI_207_DEVICE_TYPE == XdrvMailbox.index) { // DaliSend6 - DT6 = 207 = Extended LED commands 224...236
|
|
/*
|
|
params 0 1 2
|
|
DaliSend6 <broadcast>|<device>|<group> |<special_command>,<command>|<special_command_data>,<dtr0_data>
|
|
254 0..126 128..158 160..251 0..255 0..255 0..255
|
|
DaliSend6 <broadcast>|<device>|<group>,<command>
|
|
*/
|
|
if ((params >= 2) && (values[1] >= 224) && (values[1] <= 255)) { // DT6 extended command
|
|
uint32_t adr = values[0] | DALI_SELECTOR_BIT;
|
|
if ((DALI_207_SELECT_DIMMING_CURVE == values[1]) ||
|
|
(DALI_207_SET_FAST_FADE_TIME == values[1])) {
|
|
// DaliSend6 <broadcast>|<device>|<group>,<command>,<dtr0>
|
|
if (!DaliSetDTR(0, adr, values[2])) { return; }
|
|
}
|
|
DaliSendData(DALI_102_ENABLE_DEVICE_TYPE_X, DALI_207_DEVICE_TYPE); // Enable Extended command
|
|
DaliSendData(adr, values[1]);
|
|
ResponseCmndDone();
|
|
return;
|
|
}
|
|
}
|
|
if (DALI_209_DEVICE_TYPE == XdrvMailbox.index) { // DaliSend8 - DT8 = 209 = Extended colour commands 224...246
|
|
/*
|
|
params 0 1 2 3 4
|
|
DaliSend8 <broadcast>|<device>|<group> |<special_command>,<command>|<special_command_data>,<dtr0_data>|<dtr0_1_data>|<dtr2_data>,<dtr1_data>|<dtr2_data>,<dtr2_data>
|
|
254 0..126 128..158 160..251 0..255 0..255 0..255 0..65535 0..255 0..255 0..255 0..255
|
|
DaliSend8 <special_command>,<special_command_data>
|
|
DaliSend8 <broadcast>|<device>|<group>,<command>
|
|
*/
|
|
if ((params >= 2) && (values[1] >= 224) && (values[1] <= 255)) { // DT8 extended command
|
|
uint32_t adr = values[0] | DALI_SELECTOR_BIT;
|
|
// params == 3
|
|
if ((DALI_209_SET_TEMPORARY_RGBWAF_CONTROL == values[1]) ||
|
|
(DALI_209_STORE_GEAR_FEATURES_STATUS == values[1]) ||
|
|
(DALI_209_ASSIGN_COLOUR_TO_LINKED_COMMAND == values[1])) {
|
|
// DaliSend8 <broadcast>|<device>|<group>,<command>,<dtr0_data>
|
|
if (!DaliSetDTR(0, adr, values[2])) { return; }
|
|
}
|
|
else if ((DALI_209_SET_TEMPORARY_X_COORDINATE == values[1]) ||
|
|
(DALI_209_SET_TEMPORARY_Y_COORDINATE == values[1]) ||
|
|
(DALI_209_SET_TEMPORARY_COLOUR_TEMP_TC == values[1])) {
|
|
// DaliSend8 <broadcast>|<device>|<group>,<command>,<dtr0_1_data>
|
|
if (!DaliSetDTR(0, adr, values[2] % 256)) { return; }
|
|
if (!DaliSetDTR(1, adr, values[2] / 256)) { return; }
|
|
}
|
|
else if (DALI_209_STORE_XY_COORDINATE_PRIMARY_N == values[1]) {
|
|
// DaliSend8 <broadcast>|<device>|<group>,<command>,<dtr2_data>
|
|
if (!DaliSetDTR(2, adr, values[2])) { return; }
|
|
}
|
|
// params == 4
|
|
else if ((DALI_209_SET_TEMPORARY_PRIMARY_N_DIMLEVEL == values[1]) ||
|
|
(DALI_209_STORE_TY_PRIMARY_N == values[1]) ||
|
|
(DALI_209_STORE_COLOUR_TEMP_TC_LIMIT == values[1])) {
|
|
// DaliSend8 <broadcast>|<device>|<group>,<command>,<dtr0_1_data>,<dtr2_data>
|
|
if (!DaliSetDTR(0, adr, values[2] % 256)) { return; }
|
|
if (!DaliSetDTR(1, adr, values[2] / 256)) { return; }
|
|
if (!DaliSetDTR(2, adr, values[3])) { return; }
|
|
}
|
|
// params == 5
|
|
else if ((DALI_209_SET_TEMPORARY_RGB_DIMLEVEL == values[1]) ||
|
|
(DALI_209_SET_TEMPORARY_WAF_DIMLEVEL == values[1])) {
|
|
// DaliSend8 <broadcast>|<device>|<group>,<command>,<dtr0_data>,<dtr1_data>,<dtr2_data>
|
|
if (!DaliSetDTR(0, adr, values[2])) { return; }
|
|
if (!DaliSetDTR(1, adr, values[3])) { return; }
|
|
if (!DaliSetDTR(2, adr, values[4])) { return; }
|
|
}
|
|
DaliSendData(DALI_102_ENABLE_DEVICE_TYPE_X, DALI_209_DEVICE_TYPE); // Enable Extended command
|
|
DaliSendData(adr, values[1]);
|
|
ResponseCmndDone();
|
|
return;
|
|
}
|
|
}
|
|
/*
|
|
params 0 1 2 3 4
|
|
DaliSend [<dt>,]<broadcast>|<device>|<group> |<special_command>,<command>|<special_command_data>,<dtr0_1_data>,<dtr2_data>
|
|
6,8 254 0..126 128..158 160..251 0..255 0..255 0..255/65535 0..255
|
|
DaliSend <special_command>,<special_command_data>
|
|
DaliSend <broadcast>|<device>|<group>,<command>
|
|
DaliSend <broadcast>|<device>|<group>,<command>,<dtr0>
|
|
*/
|
|
if (2 == params) { // Prepare for default Extended command DT6 - LEDs
|
|
if ((values[1] >= 224) && (values[1] <= 255)) { // Extended command
|
|
values[2] = values[1];
|
|
values[1] = values[0];
|
|
values[0] = DALI_207_DEVICE_TYPE; // Default to DT6 - LEDs
|
|
params = 3;
|
|
}
|
|
}
|
|
if (3 == params) { // Set extended command mode
|
|
DaliSendData(DALI_102_ENABLE_DEVICE_TYPE_X, values[0]); // Enable Extended command
|
|
values[0] = values[1];
|
|
values[1] = values[2];
|
|
params = 2;
|
|
}
|
|
uint32_t adr = values[0];
|
|
if (XdrvMailbox.index > 1) { adr |= DALI_SELECTOR_BIT; }
|
|
if (2 == params) {
|
|
DaliSendData(adr, values[1]);
|
|
ResponseCmndDone();
|
|
}
|
|
else if (4 == params) {
|
|
if (DaliSetValue(adr, values[1], values[2], values[3])) {
|
|
ResponseCmndDone();
|
|
} else {
|
|
ResponseCmndFailed();
|
|
}
|
|
}
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------------------------*/
|
|
|
|
void CmndDaliQuery(void) {
|
|
// Send command and return response or -1 (no response within DALI_TIMEOUT)
|
|
// DaliQuery 0xff,0x90 - DALI Query status
|
|
// DaliQuery 0xff,144 - DALI Query status
|
|
// DaliQuery 6,7,237 - DALI DT6 (6) for address 3 (7) extended command query status (237)
|
|
uint32_t values[3] = { 0 };
|
|
uint32_t params = ParseParameters(3, values);
|
|
|
|
if (DALI_207_DEVICE_TYPE == XdrvMailbox.index) { // DaliQuery6 - DT6 = 207 = Extended LED commands 224...236
|
|
if ((params >= 2) && (values[1] >= 224) && (values[1] <= 255)) { // DT6 extended command
|
|
uint32_t adr = values[0] | DALI_SELECTOR_BIT;
|
|
DaliSendData(DALI_102_ENABLE_DEVICE_TYPE_X, DALI_207_DEVICE_TYPE); // Enable Extended command
|
|
int result = DaliSendWaitResponse(adr, values[1]);
|
|
ResponseCmndNumber(result);
|
|
return;
|
|
}
|
|
}
|
|
if (DALI_209_DEVICE_TYPE == XdrvMailbox.index) { // DaliQuery8 - DT8 = 209 = Extended colour commands 224...246
|
|
if ((params >= 2) && (values[1] >= 224) && (values[1] <= 255)) { // DT8 extended command
|
|
uint32_t adr = values[0] | DALI_SELECTOR_BIT;
|
|
if (DALI_209_QUERY_COLOUR_VALUE == values[1]) {
|
|
if (!DaliSetDTR(0, adr, values[2])) { return; }
|
|
}
|
|
DaliSendData(DALI_102_ENABLE_DEVICE_TYPE_X, DALI_209_DEVICE_TYPE); // Enable Extended command
|
|
int result = DaliSendWaitResponse(adr, values[1]);
|
|
if (DALI_209_QUERY_COLOUR_VALUE == values[1]) {
|
|
if (result >= 0) {
|
|
uint32_t result2 = result << 8;
|
|
int result = DaliSendWaitResponse(adr, DALI_102_QUERY_CONTENT_DTR0);
|
|
if (result >= 0) {
|
|
result = result2 | result;
|
|
}
|
|
}
|
|
}
|
|
ResponseCmndNumber(result);
|
|
return;
|
|
}
|
|
}
|
|
if (2 == params) {
|
|
if ((values[1] >= 224) && (values[1] <= 255)) { // Extended command
|
|
values[2] = values[1];
|
|
values[1] = values[0];
|
|
values[0] = DALI_207_DEVICE_TYPE; // Default to DT6 - LEDs
|
|
params = 3;
|
|
}
|
|
}
|
|
if (3 == params) {
|
|
DaliSendData(DALI_102_ENABLE_DEVICE_TYPE_X, values[0]); // Enable Extended command
|
|
values[0] = values[1];
|
|
values[1] = values[2];
|
|
params = 2;
|
|
}
|
|
if (2 == params) {
|
|
uint32_t adr = values[0] | DALI_SELECTOR_BIT;
|
|
int result = DaliSendWaitResponse(adr, values[1]);
|
|
ResponseCmndNumber(result);
|
|
}
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------------------------*/
|
|
|
|
void CmndDaliScan(void) {
|
|
// Scan short addresses
|
|
// DaliScan 1 - Reset and commission short addresses
|
|
// DaliScan 2 - Commission unassigned short addresses
|
|
// DaliScan x,5 - Commission up to 5 short addresses
|
|
uint32_t values[2] = { 0 };
|
|
uint32_t params = ParseParameters(2, values);
|
|
if ((values[0] >= 1) && (values[0] <= 2)) {
|
|
uint32_t init_arg = 0x00; // Commission all
|
|
if (2 == values[0]) {
|
|
init_arg = 0xFF; // Commission all without short addresses
|
|
}
|
|
int result = DaliCommission(init_arg, (0 == values[1]) ? 64 : values[1]);
|
|
ResponseCmndNumber(result);
|
|
}
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------------------------*/
|
|
|
|
void CmndDaliDeviceScan(void) {
|
|
// Commission DALI-2 control device (input device as opposed to control gear) short addresses
|
|
// DaliDeviceScan 1 - Clear all short addresses and commission all control devices
|
|
// DaliDeviceScan 2 - Commission control devices without short address
|
|
// DaliDeviceScan x,5 - Commission up to 5 short addresses
|
|
uint32_t values[2] = { 0 };
|
|
uint32_t params = ParseParameters(2, values);
|
|
if ((values[0] >= 1) && (values[0] <= 2)) {
|
|
uint32_t init_arg = (1 == values[0]) ? DALI_103_INITIALISE_ALL : DALI_103_INITIALISE_UNADDRESSED;
|
|
int result = DaliDeviceCommission(init_arg, (0 == values[1]) ? 64 : values[1]);
|
|
ResponseCmndNumber(result);
|
|
}
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------------------------*/
|
|
|
|
void CmndDaliDevice(void) {
|
|
// DaliDevice - Scan bus for control devices with a short address taking around 2.5 sec
|
|
// DaliDevice 3 - Report control device 3 (short address 2) version, status and instances
|
|
// DaliDevice 3,2 - Set event scheme of all instances of control device 3 to DeviceInstance and report
|
|
// DaliDevice 0,2 - Set event scheme of all instances of all control devices to DeviceInstance
|
|
uint32_t values[2] = { 0 };
|
|
uint32_t params = ParseParameters(2, values);
|
|
if (0 == params) { // Scan for control devices
|
|
char temp[200] = { 0 };
|
|
uint32_t count = 0;
|
|
for (uint32_t device = 1; device <= 64; device++) { // Scanning 64 addresses takes about 2500 ms
|
|
int instances = DaliDeviceInstances(device);
|
|
if (instances >= 0) {
|
|
snprintf_P(temp, sizeof(temp), PSTR("%s%s%d"), temp, (count)?",":"", device);
|
|
count++;
|
|
AddLog(LOG_LEVEL_DEBUG, PSTR("DLI: Control device %d with %d instance(s)"), device, instances);
|
|
}
|
|
OsWatchLoop(); // Feed blocked-loop watchdog
|
|
}
|
|
ResponseCmnd();
|
|
ResponseAppend_P(PSTR("{\"Present\":%d,\"Device\":[%s]}}"), count, temp);
|
|
return;
|
|
}
|
|
if (values[0] > 64) { return; }
|
|
if (params >= 2) { // Set event scheme
|
|
if (!DaliDeviceSetEventScheme(values[0], values[1])) { return; }
|
|
if (0 == values[0]) { // Broadcast has no report
|
|
ResponseCmndDone();
|
|
return;
|
|
}
|
|
}
|
|
if ((0 == values[0]) || !ResponseDaliDevice(values[0])) {
|
|
ResponseCmndFailed();
|
|
}
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------------------------*/
|
|
|
|
void ResponseAppendDaliBind(uint32_t slot, bool with_slot) {
|
|
// {"Bind":1,"Device":1,"Instance":0,"Target":0,"Mode":1,"Name":"Toggle"}
|
|
DliBind_t *bind = &Dali->Settings.bind[slot];
|
|
char mode_name[8];
|
|
GetTextIndexed(mode_name, sizeof(mode_name), (bind->mode < DALI_BIND_MAX) ? bind->mode : 0, kDaliBindMode);
|
|
ResponseAppend_P(PSTR("{"));
|
|
if (with_slot) { ResponseAppend_P(PSTR("\"Bind\":%d,"), slot +1); }
|
|
ResponseAppend_P(PSTR("\"Device\":%d,\"Instance\":%d,\"Target\":%d,\"Mode\":%d,\"Name\":\"%s\"}"),
|
|
bind->device, bind->instance, bind->target, bind->mode, mode_name);
|
|
}
|
|
|
|
void CmndDaliBind(void) {
|
|
// DaliBind - Show all bindings
|
|
// DaliBind2 - Show binding 2
|
|
// DaliBind2 0 - Remove binding 2
|
|
// DaliBind2 <device>,<instance>,<target>,<mode> - Bind instance of control device to control gear target
|
|
// DaliBind1 1,0,0,1 - Control device 1 instance 0 toggles and dims broadcast
|
|
// DaliBind2 0,255,101,2 - Any instance of any control device switches on group 1
|
|
if (!XdrvMailbox.usridx) { // Show all bindings
|
|
Response_P(PSTR("{\"%s\":["), XdrvMailbox.command);
|
|
uint32_t count = 0;
|
|
for (uint32_t slot = 0; slot < DALI_MAX_BIND; slot++) {
|
|
if (Dali->Settings.bind[slot].mode) {
|
|
if (count) { ResponseAppend_P(PSTR(",")); }
|
|
ResponseAppendDaliBind(slot, true);
|
|
count++;
|
|
}
|
|
}
|
|
ResponseAppend_P(PSTR("]}"));
|
|
return;
|
|
}
|
|
if ((XdrvMailbox.index < 1) || (XdrvMailbox.index > DALI_MAX_BIND)) { return; }
|
|
uint32_t slot = XdrvMailbox.index -1;
|
|
DliBind_t *bind = &Dali->Settings.bind[slot];
|
|
if (XdrvMailbox.data_len > 0) {
|
|
uint32_t values[4] = { 0 };
|
|
uint32_t params = ParseParameters(4, values);
|
|
if ((1 == params) && (0 == values[0])) { // Remove binding
|
|
memset(bind, 0, sizeof(DliBind_t));
|
|
}
|
|
else if ((4 == params) &&
|
|
(values[0] <= 64) &&
|
|
((values[1] <= 31) || (255 == values[1])) &&
|
|
((values[2] <= 64) || ((values[2] >= 101) && (values[2] <= 116))) &&
|
|
(values[3] >= DALI_BIND_TOGGLE) && (values[3] < DALI_BIND_MAX)) {
|
|
bind->device = values[0];
|
|
bind->instance = values[1];
|
|
bind->target = values[2];
|
|
bind->mode = values[3];
|
|
Dali->bind_down &= ~(1 << slot);
|
|
} else {
|
|
ResponseCmndFailed();
|
|
return;
|
|
}
|
|
}
|
|
Response_P(PSTR("{\"%s%d\":"), XdrvMailbox.command, XdrvMailbox.index);
|
|
ResponseAppendDaliBind(slot, false);
|
|
ResponseJsonEnd();
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------------------------*/
|
|
|
|
void CmndDaliBroadcastSlider(void) {
|
|
// DaliBS 1 - Show broadcast slider
|
|
// DaliBS 0 - Hide broadcast slider
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 1)) {
|
|
Settings->sbflag1.dali_no_broadcast_slider = !XdrvMailbox.payload;
|
|
TasmotaGlobal.restart_flag = 2; // Restart to update GUI
|
|
}
|
|
ResponseCmndStateText(!Settings->sbflag1.dali_no_broadcast_slider); // DaliNoBS 0/1
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------------------------*/
|
|
|
|
void CmndDaliGroupSliders(void) {
|
|
// DaliGroupSliders 0..16 - Add group sliders
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 16)) {
|
|
Settings->mbflag2.dali_group_sliders = XdrvMailbox.payload;
|
|
TasmotaGlobal.restart_flag = 2; // Restart to update GUI
|
|
}
|
|
ResponseCmndNumber(Settings->mbflag2.dali_group_sliders);
|
|
}
|
|
|
|
#ifdef USE_LIGHT
|
|
/*-------------------------------------------------------------------------------------------*/
|
|
|
|
void CmndDaliRetry(void) {
|
|
// DaliRetry 0..64 - Retry restart probe count once per second for DALI device
|
|
if (Dali->allow_light) {
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 64)) {
|
|
Dali->Settings.probe_retry = XdrvMailbox.payload;
|
|
}
|
|
}
|
|
ResponseCmndNumber(Dali->Settings.probe_retry);
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------------------------*/
|
|
|
|
void CmndDaliLight(void) {
|
|
// DaliLight 0 - Disable light controls
|
|
// DaliLight 1 - Enable light controls
|
|
if (Dali->allow_light && (XdrvMailbox.data_len > 0)) {
|
|
Settings->sbflag1.dali_light = XdrvMailbox.payload &1; // DaliLight 0/1
|
|
TasmotaGlobal.restart_flag = 2; // Restart to update GUI
|
|
}
|
|
ResponseCmndStateText(Settings->sbflag1.dali_light); // DaliLight 0/1
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------------------------*/
|
|
|
|
void CmndDaliTarget(void) {
|
|
// DaliTarget - Set transmit target
|
|
// DaliTarget 0 - Set target to broadcast address
|
|
// DaliTarget 1..64 - Set target to short address
|
|
// DaliTarget 101..116 - Set target to group address
|
|
if (Dali->allow_light) {
|
|
if (((XdrvMailbox.payload >= 1) && (XdrvMailbox.payload <= 64)) ||
|
|
((XdrvMailbox.payload >= 101) && (XdrvMailbox.payload <= 116)) ||
|
|
(XdrvMailbox.payload == 0)) {
|
|
Dali->Settings.target = XdrvMailbox.payload;
|
|
TasmotaGlobal.restart_flag = 2; // Restart to update GUI
|
|
}
|
|
Dali->target_rgbwaf = DaliQueryRGBWAF(DaliTarget2Address(Dali->Settings.target));
|
|
}
|
|
ResponseCmndNumber(Dali->Settings.target);
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------------------------*/
|
|
|
|
void CmndDaliChannels(void) {
|
|
// DaliChannels - Show amount of color channels
|
|
// DaliChannels 1 - Set amount of color channels to R
|
|
// DaliChannels 2 - Set amount of color channels to RG
|
|
// DaliChannels 3 - Set amount of color channels to RGB
|
|
// DaliChannels 4 - Set amount of color channels to RGBW
|
|
// DaliChannels 5 - Set amount of color channels to RGBWC
|
|
if (Dali->allow_light && ((XdrvMailbox.payload >= 1) && (XdrvMailbox.payload <= 5))) {
|
|
Dali->Settings.light_type = XdrvMailbox.payload + 8;
|
|
TasmotaGlobal.restart_flag = 2; // Restart to update GUI
|
|
}
|
|
ResponseCmndNumber(Dali->Settings.light_type -8);
|
|
}
|
|
#endif // USE_LIGHT
|
|
|
|
/*********************************************************************************************\
|
|
* Presentation
|
|
\*********************************************************************************************/
|
|
|
|
#ifdef USE_WEBSERVER
|
|
const char HTTP_MSG_SLIDER_DALI[] PROGMEM =
|
|
"<tr>"
|
|
"<td style='width:15%%'><button id='k75%d' style='background:#%06x;' onclick='la(\"&k75=%d\");'>%s%s</button></td>"
|
|
"<td style='width:85%%'><div class='r' style='background-image:linear-gradient(to right,#000,#FFF);'>"
|
|
"<input id='i75%d' type='range' min='1' max='100' value='%d' onchange='lc(\"i\",75%d,value)'></div></td>"
|
|
"</tr>";
|
|
|
|
void DaliWebAddMainSlider(void) {
|
|
WSContentSend_P(HTTP_TABLE100);
|
|
char number[12];
|
|
for (uint32_t i = Settings->sbflag1.dali_no_broadcast_slider; // DaliBS 0/1
|
|
i <= Settings->mbflag2.dali_group_sliders; i++) { // DaliGroupSliders
|
|
Dali->web_dimmer[i] = Dali->dimmer[i];
|
|
WSContentSend_P(HTTP_MSG_SLIDER_DALI, // Brightness - Black to White
|
|
i, // k75<i>
|
|
WebColor((Dali->power[i]) ? COL_BUTTON : COL_BUTTON_OFF),
|
|
i, // k75=<i>
|
|
(0==i)?"B":"G", // B (Broadcast) or G1 to G16 (Group)
|
|
(0==i)?"":itoa(i, number, 10),
|
|
i, // i75<i>
|
|
changeUIntScale(Dali->web_dimmer[i], 0, 254, 0, 100),
|
|
i // i75<i>
|
|
);
|
|
}
|
|
WSContentSend_P(PSTR("</table>"));
|
|
}
|
|
|
|
/*********************************************************************************************/
|
|
|
|
void DaliWebGetArg(void) {
|
|
char tmp[8]; // WebGetArg numbers only
|
|
char svalue[32]; // Command and number parameter
|
|
char webindex[8]; // WebGetArg name
|
|
|
|
uint32_t index;
|
|
for (uint32_t i = Settings->sbflag1.dali_no_broadcast_slider; // DaliBS 0/1
|
|
i <= Settings->mbflag2.dali_group_sliders; i++) { // DaliGroupSliders
|
|
snprintf_P(webindex, sizeof(webindex), PSTR("i75%d"), i);
|
|
WebGetArg(webindex, tmp, sizeof(tmp)); // 0 - 100 percent
|
|
if (strlen(tmp)) {
|
|
index = i;
|
|
if (index > 0) { index += 100; } // Group
|
|
snprintf_P(svalue, sizeof(svalue), PSTR("DaliDimmer%d %s"), index, tmp);
|
|
ExecuteWebCommand(svalue);
|
|
}
|
|
}
|
|
WebGetArg(PSTR("k75"), tmp, sizeof(tmp));
|
|
if (strlen(tmp)) {
|
|
index = atoi(tmp);
|
|
if (index > 0) { index += 100; } // Group
|
|
snprintf_P(svalue, sizeof(svalue), PSTR("DaliPower%d 2"), index);
|
|
ExecuteWebCommand(svalue);
|
|
}
|
|
}
|
|
|
|
/*********************************************************************************************/
|
|
|
|
void DaliWebShow(void) {
|
|
WSContentSend_P(PSTR("</table>")); // Terminate current {t}
|
|
WSContentSend_P(HTTP_MSG_EXEC_JAVASCRIPT); // "<img style='display:none;' src onerror=\""
|
|
for (uint32_t i = Settings->sbflag1.dali_no_broadcast_slider; // DaliBS 0/1
|
|
i <= Settings->mbflag2.dali_group_sliders; i++) { // DaliGroupSliders
|
|
WSContentSend_P(PSTR("eb('k75%d').style='background:#%06x';"),
|
|
i, WebColor((Dali->power[i]) ? COL_BUTTON : COL_BUTTON_OFF));
|
|
if (Dali->dimmer[i] != Dali->web_dimmer[i]) {
|
|
if (WebUpdateSliderTime()) {
|
|
Dali->web_dimmer[i] = Dali->dimmer[i];
|
|
}
|
|
WSContentSend_P(PSTR("eb('i75%d').value='%d';"),
|
|
i, changeUIntScale(Dali->dimmer[i], 0, 254, 0, 100));
|
|
}
|
|
}
|
|
WSContentSend_P(PSTR("\">{t}")); // Restart {t} = <table style='width:100%'>
|
|
WSContentSeparator(3); // Don't print separator on next WSContentSeparator(1)
|
|
}
|
|
|
|
#endif // USE_WEBSERVER
|
|
|
|
/*********************************************************************************************\
|
|
* Interface
|
|
\*********************************************************************************************/
|
|
|
|
bool Xdrv75(uint32_t function) {
|
|
bool result = false;
|
|
|
|
if (FUNC_MODULE_INIT == function) { // Try to claim DALI as light
|
|
result = DaliInit(function);
|
|
}
|
|
else if ((FUNC_PRE_INIT == function) && !Dali) { // If claim failed then use DALI controls only
|
|
DaliInit(function);
|
|
}
|
|
else if (Dali) {
|
|
switch (function) {
|
|
case FUNC_LOOP:
|
|
case FUNC_SLEEP_LOOP:
|
|
DaliLoop();
|
|
break;
|
|
case FUNC_EVERY_SECOND:
|
|
DaliEverySecond();
|
|
break;
|
|
case FUNC_RESET_SETTINGS:
|
|
DaliSettingsLoad(1);
|
|
break;
|
|
case FUNC_SAVE_SETTINGS:
|
|
DaliSettingsSave();
|
|
break;
|
|
#ifdef USE_LIGHT
|
|
case FUNC_SET_CHANNELS:
|
|
result = DaliSetChannels();
|
|
break;
|
|
#endif // USE_LIGHT
|
|
case FUNC_JSON_APPEND:
|
|
ResponseAppend_P(PSTR(","));
|
|
ResponseAppendDali(0);
|
|
break;
|
|
#ifdef USE_WEBSERVER
|
|
case FUNC_WEB_SENSOR:
|
|
DaliWebShow();
|
|
break;
|
|
case FUNC_WEB_ADD_MAIN_BUTTON:
|
|
DaliWebAddMainSlider();
|
|
break;
|
|
case FUNC_WEB_GET_ARG:
|
|
DaliWebGetArg();
|
|
break;
|
|
#endif // USE_WEBSERVER
|
|
case FUNC_COMMAND:
|
|
result = DecodeCommand(kDALICommands, DALICommand);
|
|
break;
|
|
case FUNC_ACTIVE:
|
|
result = true;
|
|
break;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif // USE_DALI
|