Merge pull request #7790 from pcdiem/device-groups

Device groups
This commit is contained in:
Theo Arends 2020-02-25 11:09:03 +01:00 committed by GitHub
commit 8f5c05d716
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 974 additions and 30 deletions

45
Device_Groups.md Normal file
View File

@ -0,0 +1,45 @@
# Device Groups
The device groups module provides a framework to allow multiple devices to be in a group with values such as power, light color/temperature/brightness, PWM values, sensor values, etc. shared with other devices in the group. For example, with multiple light modules in a device group, the light settings can be changed on one module and the settings will automatically be changed on the other light modules. Dimmer switch modules could be in a device group with light modules and the dimmer switch could control the power, brightness and colors of all the lights in the group. Multiple dimmer switches could be in a device group to form a 3-way/4-way dimmer switch.
UDP multicasts, followed by UDP unicasts if necessary, are used to send updates to all devices so updates are fast. There is no need for an MQTT server but all the devices in a group must be on the same IP network.
To include device groups support in the build, define USE_DEVICE_GROUPS in your user_config_override. This adds 3.5K to the code size. All devices in a group must be running firmware with device group support and have device groups enabled.
To enable device groups, set Option85 to 1.
## Device Groups Operation
The device group name is the MQTT group topic set with the GroupTopic command. All devices in the same IP network with the same group topic are in the same group. Some modules may define additional device groups. For example, if Remote Device Mode is enabled, the PWM Dimmer module defines three devices groups.
The items that are sent to the group and the items that are received from the group are selected with the DevGroupShare command. By default all items are sent and received from the group. An example of when the DevGroupShare command would be used is when you have a group of lights that you control with a dimmer switch and home automation software. You want the dimmer switch to be able to control all items. The home automation software controls each light individually. When it controls the whole group, it actually sends command to each light in the group. If you use the home automation software to turn an individual light on or off or change its brightness, color or scheme, you do not want the change to be replicated to the other lights. In this case, you would set the incoming and outgoing item masks to 0xffffffff (all items) on the dimmer switch (DevGroupShare 0xffffffff,0xffffffff) and set the incoming item mask to 0xffffffff and outgoing item mask to 0 on all the lights (DevGroupShare 0xffffffff,0).
### Commands
<table>
<tr>
<td><strong>Command</strong>
</td>
<td><strong>Parameters</strong>
</td>
</tr>
<tr>
<td>DevGroupShare
</td>
<td><in>,&lt;out> = set incoming and outgoing shared item mask (default = 0xffffffff,0xffffffff)
<p>
1 = Power, 2 = Light brightness, 4 = Light fade/speed, 8 = Light scheme, 16 = Light color, 32 = Minimum brightness
</td>
</tr>
<tr>
<td>GroupTopic&lt;x>
</td>
<td>1 = reset device group &lt;x> MQTT group topic to firmware default (MQTT_GRPTOPIC) and restart
<p>
<value> = set device group &lt;x> MQTT group topic (32 chars max) and restart
</td>
</tr>
</table>

View File

@ -289,6 +289,7 @@
#define D_CMND_WIFIPOWER "WifiPower"
#define D_CMND_I2CSCAN "I2CScan"
#define D_CMND_I2CDRIVER "I2CDriver"
#define D_CMND_DEVGROUP_SHARE "DevGroupShare"
#define D_CMND_SERIALSEND "SerialSend"
#define D_CMND_SERIALDELIMITER "SerialDelimiter"
#define D_CMND_BAUDRATE "Baudrate"

View File

@ -415,6 +415,7 @@
//#define USE_EXS_DIMMER // Add support for ES-Store WiFi Dimmer (+1k5 code)
// #define EXS_MCU_CMNDS // Add command to send MCU commands (+0k8 code)
//#define USE_HOTPLUG // Add support for sensor HotPlug
//#define USE_DEVICE_GROUPS // Add support for device groups (+4k code)
// -- Optional light modules ----------------------
#define USE_WS2812 // WS2812 Led string using library NeoPixelBus (+5k code, +1k mem, 232 iram) - Disable by //

View File

@ -30,6 +30,9 @@ const char kTasmotaCommands[] PROGMEM = "|" // No prefix
#ifdef USE_I2C
D_CMND_I2CSCAN "|" D_CMND_I2CDRIVER "|"
#endif
#ifdef USE_DEVICE_GROUPS
D_CMND_DEVGROUP_SHARE "|"
#endif // USE_DEVICE_GROUPS
D_CMND_SENSOR "|" D_CMND_DRIVER;
void (* const TasmotaCommand[])(void) PROGMEM = {
@ -45,6 +48,9 @@ void (* const TasmotaCommand[])(void) PROGMEM = {
#ifdef USE_I2C
&CmndI2cScan, CmndI2cDriver,
#endif
#ifdef USE_DEVICE_GROUPS
&CmndDevGroupShare,
#endif // USE_DEVICE_GROUPS
&CmndSensor, &CmndDriver };
const char kWifiConfig[] PROGMEM =
@ -1664,6 +1670,17 @@ void CmndI2cDriver(void)
}
#endif // USE_I2C
#ifdef USE_DEVICE_GROUPS
void CmndDevGroupShare(void)
{
uint32_t parm[2] = { Settings.device_group_share_in, Settings.device_group_share_out };
ParseParameters(2, parm);
Settings.device_group_share_in = parm[0];
Settings.device_group_share_out = parm[1];
Response_P(PSTR("{\"" D_CMND_DEVGROUP_SHARE "\":{\"In\":%x,\"Out\":%x}}"), Settings.device_group_share_in, Settings.device_group_share_out);
}
#endif // USE_DEVICE_GROUPS
void CmndSensor(void)
{
XsnsCall(FUNC_COMMAND_SENSOR);

View File

@ -0,0 +1,713 @@
/*
support_device_groups.ino - device groups support for Tasmota
Copyright (C) 2020 Paul C Diem
Device group allow multiple devices to be in a group with power, light
brightness, fade and speed settings and other module-specific settings
kept in sync across all devices in the group.
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_DEVICE_GROUPS
//#define DEVICE_GROUPS_DEBUG
extern bool udp_connected;
struct device_group_member {
struct device_group_member * flink;
IPAddress ip_address;
uint32_t timeout_time;
uint16_t received_sequence;
uint16_t acked_sequence;
};
struct device_group {
uint32_t next_ack_check_time;
uint16_t last_full_status_sequence;
uint16_t message_length;
uint8_t message_header_length;
uint8_t initial_status_requests_remaining;
bool local;
char group_name[TOPSZ];
char message[128];
uint8_t group_member_count;
struct device_group_member * device_group_members;
};
struct device_group * device_groups;
uint16_t outgoing_sequence = 0;
bool device_groups_initialized = false;
bool device_groups_initialization_failed = false;
bool building_status_message = false;
bool processing_remote_device_message = false;
bool waiting_for_acks;
bool udp_was_connected = false;
void DeviceGroupsInit(void)
{
// Initialize the device information for each device group. The group name is the MQTT group topic.
device_groups = (struct device_group *)calloc(device_group_count, sizeof(struct device_group));
if (device_groups == nullptr) {
AddLog_P2(LOG_LEVEL_ERROR, PSTR("DGR: error allocating %u-element device group array"), device_group_count);
device_groups_initialization_failed = true;
return;
}
for (uint32_t device_group_index = 0; device_group_index < device_group_count; device_group_index++) {
struct device_group * device_group = &device_groups[device_group_index];
strcpy(device_group->group_name, SettingsText((device_group_index == 0 ? SET_MQTT_GRP_TOPIC : SET_MQTT_GRP_TOPIC2 + device_group_index - 1)));
device_group->message_header_length = sprintf_P(device_group->message, PSTR("%s%s HTTP/1.1\n\n"), kDeviceGroupMessage, device_group->group_name);
device_group->last_full_status_sequence = -1;
}
device_groups[0].local = true;
// If both in and out shared items masks are 0, assume they're unitialized and initialize them.
if (!Settings.device_group_share_in && !Settings.device_group_share_out) {
Settings.device_group_share_in = Settings.device_group_share_out = 0xffffffff;
}
device_groups_initialized = true;
}
char * IPAddressToString(const IPAddress& ip_address)
{
static char buffer[16];
sprintf_P(buffer, PSTR("%u.%u.%u.%u"), ip_address[0], ip_address[1], ip_address[2], ip_address[3]);
return buffer;
}
// Return true if we're configured to share the specified item.
bool DeviceGroupItemShared(bool incoming, uint8_t item)
{
uint8_t mask = 0;
switch (item) {
case DGR_ITEM_LIGHT_BRI:
mask = DGR_SHARE_LIGHT_BRI;
break;
case DGR_ITEM_POWER:
mask = DGR_SHARE_POWER;
break;
case DGR_ITEM_LIGHT_SCHEME:
mask = DGR_SHARE_LIGHT_SCHEME;
break;
case DGR_ITEM_LIGHT_FIXED_COLOR:
case DGR_ITEM_LIGHT_CHANNELS:
mask = DGR_SHARE_LIGHT_COLOR;
break;
case DGR_ITEM_LIGHT_FADE:
case DGR_ITEM_LIGHT_SPEED:
mask = DGR_SHARE_LIGHT_FADE;
break;
case DGR_ITEM_BRI_MIN:
mask = DGR_SHARE_BRI_MIN;
break;
}
return (!mask || ((incoming ? Settings.device_group_share_in : Settings.device_group_share_out) & mask));
}
void SendDeviceGroupPacket(IPAddress ip, char * packet, int len, const char * label)
{
for (int attempt = 1; attempt <= 5; attempt++) {
if (PortUdp.beginPacket(ip, 1900)) {
PortUdp.write(packet, len);
if (PortUdp.endPacket()) return;
}
delay(10);
}
AddLog_P2(LOG_LEVEL_ERROR, PSTR("DGR: error sending %s packet"), label);
}
void _SendDeviceGroupMessage(uint8_t device_group_index, DeviceGroupMessageType message_type, ...)
{
// If device groups are not enabled, ignore this request.
if (!Settings.flag4.device_groups_enabled) return;
// If UDP is not set up, ignore this request.
if (!udp_connected) return;
// If we're currently processing a remote device message, ignore this request.
if (processing_remote_device_message) return;
// Get a pointer to the device information for this device.
device_group * device_group = &device_groups[device_group_index];
// If we're still sending initial status requests, ignore this request.
if (device_group->initial_status_requests_remaining) return;
// A full status request is a request from a remote device for the status of every item we
// control. As long as we're building it, we may as well multicast the status update to all
// device group members.
char * message_ptr = &device_group->message[device_group->message_header_length];
if (message_type == DGR_MSGTYP_FULL_STATUS) {
// Set the flag indicating we're currently building a status message. SendDeviceGroupMessage
// will build but not send messages while this flag is set.
building_status_message = true;
// Call the drivers to build the status update.
if (!++outgoing_sequence) outgoing_sequence = 1;
#ifdef DEVICE_GROUPS_DEBUG
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("Building device group %s full status packet"), device_group->group_name);
#endif // DEVICE_GROUPS_DEBUG
device_group->message_length = 0;
SendDeviceGroupMessage(device_group_index, DGR_MSGTYP_PARTIAL_UPDATE, DGR_ITEM_POWER, power);
XdrvMailbox.command_code = DGR_ITEM_STATUS;
XdrvCall(FUNC_DEVICE_GROUP_REQUEST);
building_status_message = false;
// If we have nothing to share with the other members, restore the message sequence and return.
if (!device_group->message_length) {
if (!--outgoing_sequence) outgoing_sequence = -1;
return;
}
device_group->last_full_status_sequence = outgoing_sequence;
// Set the status update flag in the outgoing message.
*(message_ptr + 2) |= DGR_FLAG_FULL_STATUS;
}
else {
bool shared;
uint8_t item;
uint32_t value;
uint8_t * value_ptr;
va_list ap;
#ifdef DEVICE_GROUPS_DEBUG
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("Building device group %s packet"), device_group->group_name);
#endif // DEVICE_GROUPS_DEBUG
uint16_t original_sequence = outgoing_sequence;
if (!building_status_message && message_type != DGR_MSGTYP_PARTIAL_UPDATE && !++outgoing_sequence) outgoing_sequence = 1;
*message_ptr++ = outgoing_sequence & 0xff;
*message_ptr++ = outgoing_sequence >> 8;
value = 0;
if (message_type == DGR_MSGTYP_UPDATE_MORE_TO_COME)
value |= DGR_FLAG_MORE_TO_COME;
else if (message_type == DGR_MSGTYP_UPDATE_DIRECT)
value |= DGR_FLAG_DIRECT;
#ifdef DEVICE_GROUPS_DEBUG
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(">sequence=%u, flags=%u"), outgoing_sequence, value);
#endif // DEVICE_GROUPS_DEBUG
*message_ptr++ = value & 0xff;
*message_ptr++ = value >> 8;
char * first_item_ptr = message_ptr;
// If we're still building this update or all group members haven't acknowledged the previous
// update yet, update the message to include these new updates. First we need to rebuild the
// previous update message to remove any items and their values that are included in this new
// update.
if (device_group->message_length) {
uint8_t item_array[32];
int item_index = 0;
int kept_item_count = 0;
// Build an array of all the items in this new update.
va_start(ap, message_type);
while ((item = va_arg(ap, int))) {
item_array[item_index++] = item;
if (item <= DGR_ITEM_MAX_32BIT)
va_arg(ap, int);
else if (item <= DGR_ITEM_MAX_STRING)
va_arg(ap, char *);
else {
switch (item) {
case DGR_ITEM_LIGHT_CHANNELS:
va_arg(ap, uint8_t *) ;
break;
}
}
}
va_end(ap);
item_array[item_index] = 0;
// Rebuild the previous update message, removing any items their values that are included in
// this new update.
char * previous_message_ptr = message_ptr;
while (item = *previous_message_ptr++) {
// Search for this item in the new update.
for (item_index = 0; item_array[item_index]; item_index++) {
if (item_array[item_index] == item) break;
}
// If this item was found in the new update skip over it and it's value.
if (item_array[item_index]) {
if (item <= DGR_ITEM_MAX_32BIT) {
previous_message_ptr++;
if (item > DGR_ITEM_MAX_8BIT) {
previous_message_ptr++;
if (item > DGR_ITEM_MAX_16BIT) {
previous_message_ptr++;
previous_message_ptr++;
}
}
}
else if (item <= DGR_ITEM_MAX_STRING)
previous_message_ptr += *previous_message_ptr++;
else {
switch (item) {
case DGR_ITEM_LIGHT_CHANNELS:
previous_message_ptr += 5;
break;
}
}
}
// If this item was not found in the new udpate, copy it to the new update message.
else {
*message_ptr++ = item;
if (item <= DGR_ITEM_MAX_32BIT) {
*message_ptr++ = *previous_message_ptr++;
if (item > DGR_ITEM_MAX_8BIT) {
*message_ptr++ = *previous_message_ptr++;
if (item > DGR_ITEM_MAX_16BIT) {
*message_ptr++ = *previous_message_ptr++;
*message_ptr++ = *previous_message_ptr++;
}
}
}
else if (item <= DGR_ITEM_MAX_STRING) {
*message_ptr++ = value = *previous_message_ptr++;
memmove(message_ptr, previous_message_ptr, value);
previous_message_ptr += value;
message_ptr += value;
}
else {
switch (item) {
case DGR_ITEM_LIGHT_CHANNELS:
memmove(message_ptr, previous_message_ptr, 5);
previous_message_ptr += 5;
message_ptr += 5;
break;
}
}
kept_item_count++;
}
}
#ifdef DEVICE_GROUPS_DEBUG
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("%u items carried over from previous update"), kept_item_count);
#endif // DEVICE_GROUPS_DEBUG
}
// Itertate through the passed items adding them and their values to the message.
va_start(ap, message_type);
while ((item = va_arg(ap, int))) {
shared = DeviceGroupItemShared(false, item);
if (shared) *message_ptr++ = item;
if (item <= DGR_ITEM_MAX_32BIT) {
value = va_arg(ap, int);
if (shared) {
#ifdef DEVICE_GROUPS_DEBUG
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(">item=%u, value=%u"), item, value);
#endif // DEVICE_GROUPS_DEBUG
*message_ptr++ = value & 0xff;
if (item > DGR_ITEM_MAX_8BIT) {
value >>= 8;
*message_ptr++ = value & 0xff;
}
if (item > DGR_ITEM_MAX_16BIT) {
value >>= 8;
*message_ptr++ = value & 0xff;
*message_ptr++ = value >> 8;
}
}
}
else if (item <= DGR_ITEM_MAX_STRING) {
value_ptr = va_arg(ap, uint8_t *);
if (shared) {
value = strlen((const char *)value_ptr);
#ifdef DEVICE_GROUPS_DEBUG
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(">item=%u, value=%s"), item, value_ptr);
#endif // DEVICE_GROUPS_DEBUG
*message_ptr++ = value;
memcpy(message_ptr, value_ptr, value);
message_ptr += value;
}
}
else {
switch (item) {
case DGR_ITEM_LIGHT_CHANNELS:
value_ptr = va_arg(ap, uint8_t *);
if (shared) {
#ifdef DEVICE_GROUPS_DEBUG
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(">item=%u, value=%u,%u,%u,%u,%u"), item, *value_ptr, *(value_ptr + 1), *(value_ptr + 2), *(value_ptr + 3), *(value_ptr + 4));
#endif // DEVICE_GROUPS_DEBUG
memmove(message_ptr, value_ptr, 5);
message_ptr += 5;
}
break;
}
}
}
va_end(ap);
// If there weren't any items added to the message, restore the outgoing message sequence and
// return.
if (message_ptr == first_item_ptr) {
outgoing_sequence = original_sequence;
return;
}
// Add the EOL item code and calculate the message length.
*message_ptr++ = 0;
device_group->message_length = message_ptr - device_group->message;
// If there's going to be more items added to this message, return.
if (building_status_message || message_type == DGR_MSGTYP_PARTIAL_UPDATE) return;
}
// Multicast the packet.
#ifdef DEVICE_GROUPS_DEBUG
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("DGR: sending %u-byte device group %s packet via multicast, sequence=%u"), device_group->message_length, device_group->group_name, device_group->message[device_group->message_header_length] | device_group->message[device_group->message_header_length + 1] << 8);
#endif // DEVICE_GROUPS_DEBUG
SendDeviceGroupPacket(IPAddress(239,255,255,250), device_group->message, device_group->message_length, PSTR("Multicast"));
device_group->next_ack_check_time = millis() + 100;
if (message_type == DGR_MSGTYP_UPDATE_MORE_TO_COME) {
for (struct device_group_member * device_group_member = device_group->device_group_members; device_group_member != nullptr; device_group_member = device_group_member->flink) {
device_group_member->acked_sequence = outgoing_sequence;
}
}
else {
waiting_for_acks = true;
}
}
void ProcessDeviceGroupMessage(char * packet, int packet_length)
{
// Make the group name a null-terminated string.
char * message_group_name = packet + sizeof(DEVICE_GROUP_MESSAGE) - 1;
char * message_ptr = strchr(message_group_name, ' ');
if (message_ptr == nullptr) return;
*message_ptr = 0;
// Search for a device group with the target group name. If one isn't found, return.
struct device_group * device_group;
uint32_t device_group_index = 0;
for (;;) {
device_group = &device_groups[device_group_index];
if (!strcmp(message_group_name, device_group->group_name)) break;
if (++device_group_index >= device_group_count) return;
}
*message_ptr++ = ' ';
// Find the group member. If this is a new group member, add it.
IPAddress remote_ip = PortUdp.remoteIP();
struct device_group_member * * flink = &device_group->device_group_members;
struct device_group_member * device_group_member;
for (;;) {
device_group_member = *flink;
if (!device_group_member) {
device_group_member = (struct device_group_member *)calloc(1, sizeof(struct device_group_member));
if (device_group_member == nullptr) {
AddLog_P2(LOG_LEVEL_ERROR, PSTR("DGR: error allocating device group member block"));
return;
}
#ifdef DEVICE_GROUPS_DEBUG
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("DGR: adding member %s (%p)"), IPAddressToString(remote_ip), device_group_member);
#endif // DEVICE_GROUPS_DEBUG
device_group_member->ip_address = remote_ip;
*flink = device_group_member;
break;
}
else if (device_group_member->ip_address == remote_ip) {
break;
}
flink = &device_group_member->flink;
}
// Find the start of the actual message (after the http header).
message_ptr = strstr_P(message_ptr, PSTR("\n\n"));
if (message_ptr == nullptr) return;
message_ptr += 2;
bool light_fade;
uint16_t flags;
uint16_t item;
uint16_t message_sequence;
int32_t value;
// Get the message sequence and flags.
if (packet_length - (message_ptr - packet) < 4) goto badmsg; // Malformed message - must be at least 16-bit sequence, 16-bit flags left
message_sequence = *message_ptr++;
message_sequence |= *message_ptr++ << 8;
flags = *message_ptr++;
flags |= *message_ptr++ << 8;
#ifdef DEVICE_GROUPS_DEBUG
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("Received device group %s packet from %s: sequence=%u, flags=%u"), device_group->group_name, IPAddressToString(remote_ip), message_sequence, flags);
#endif // DEVICE_GROUPS_DEBUG
// If this is an ack message, save the message sequence if it's newwer than the last ack we
// received from this member.
if (flags == DGR_FLAG_ACK) {
if (message_sequence > device_group_member->acked_sequence || device_group_member->acked_sequence - message_sequence < 64536) {
device_group_member->acked_sequence = message_sequence;
}
device_group_member->timeout_time = 0;
#ifdef DEVICE_GROUPS_DEBUG
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("<ack"));
#endif // DEVICE_GROUPS_DEBUG
return;
}
// Send an ack message to the sender.
if (!(flags & DGR_FLAG_MORE_TO_COME)) {
*(message_ptr - 2) = DGR_FLAG_ACK;
*(message_ptr - 1) = 0;
SendDeviceGroupPacket(remote_ip, packet, message_ptr - packet, PSTR("Ack"));
}
// If this is a status request message, then if the requestor didn't just ack our previous full
// status update, send a full status update.
if ((flags & DGR_FLAG_STATUS_REQUEST)) {
if ((flags & DGR_FLAG_RESET) || device_group_member->acked_sequence != device_group->last_full_status_sequence) {
_SendDeviceGroupMessage(device_group_index, DGR_MSGTYP_FULL_STATUS);
}
#ifdef DEVICE_GROUPS_DEBUG
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("<status request"));
#endif // DEVICE_GROUPS_DEBUG
return;
}
// If we already processed this or a later message from this group member, ignore this message.
if (message_sequence < device_group_member->received_sequence && device_group_member->received_sequence - message_sequence > 64536) {
#ifdef DEVICE_GROUPS_DEBUG
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("<old message"));
#endif // DEVICE_GROUPS_DEBUG
return;
}
device_group_member->received_sequence = message_sequence;
// Set the flag indicating we're currently processing a remote device message.
// SendDeviceGroupMessage will not send messages while this flag is set.
processing_remote_device_message = true;
/*
XdrvMailbox
bool grpflg
bool usridx
uint16_t command_code Item code
uint32_t index Flags
uint32_t data_len String item value length
int32_t payload Integer item value
char *topic
char *data Pointer to non-integer item value
char *command nullptr
*/
XdrvMailbox.command = nullptr; // Indicates the source is a device group update
XdrvMailbox.index = flags;
light_fade = Settings.light_fade;
if (flags & (DGR_FLAG_MORE_TO_COME | DGR_FLAG_DIRECT)) Settings.light_fade = false;
for (;;) {
if (packet_length - (message_ptr - packet) < 1) goto badmsg; // Malformed message
item = *message_ptr++;
if (!item) break; // Done
#ifdef DEVICE_GROUPS_DEBUG
switch (item) {
case DGR_ITEM_LIGHT_FADE:
case DGR_ITEM_LIGHT_SPEED:
case DGR_ITEM_LIGHT_BRI:
case DGR_ITEM_LIGHT_SCHEME:
case DGR_ITEM_LIGHT_FIXED_COLOR:
case DGR_ITEM_BRI_MIN:
case DGR_ITEM_BRI_PRESET_LOW:
case DGR_ITEM_BRI_PRESET_HIGH:
case DGR_ITEM_BRI_POWER_ON:
case DGR_ITEM_POWER:
case DGR_ITEM_LIGHT_CHANNELS:
break;
default:
AddLog_P2(LOG_LEVEL_ERROR, PSTR("DGR: ********** invalid item=%u received from device group %s member %s"), item, device_group->group_name, IPAddressToString(remote_ip));
}
#endif // DEVICE_GROUPS_DEBUG
XdrvMailbox.command_code = item;
if (item <= DGR_ITEM_LAST_32BIT) {
value = *message_ptr++;
if (item > DGR_ITEM_MAX_8BIT) {
value |= *message_ptr++ << 8;
if (item > DGR_ITEM_MAX_16BIT) {
value |= *message_ptr++ << 16;
value |= *message_ptr++ << 24;
}
}
else if (item == DGR_ITEM_LIGHT_FADE) {
light_fade = value;
}
#ifdef DEVICE_GROUPS_DEBUG
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("<item=%u, value=%u"), item, value);
#endif // DEVICE_GROUPS_DEBUG
XdrvMailbox.payload = value;
}
else if (item <= DGR_ITEM_MAX_STRING) {
value = *message_ptr++;
if (value >= packet_length - (message_ptr - packet)) goto badmsg; // Malformed message
#ifdef DEVICE_GROUPS_DEBUG
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("<item=%u, value=%.*s"), item, value, message_ptr);
#endif // DEVICE_GROUPS_DEBUG
XdrvMailbox.data_len = value;
XdrvMailbox.data = message_ptr;
message_ptr += value;
}
else {
switch (item) {
case DGR_ITEM_LIGHT_CHANNELS:
#ifdef DEVICE_GROUPS_DEBUG
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("<item=%u, value=%u,%u,%u,%u,%u"), item, *message_ptr, *(message_ptr + 1), *(message_ptr + 2), *(message_ptr + 3), *(message_ptr + 4));
#endif // DEVICE_GROUPS_DEBUG
XdrvMailbox.data = message_ptr;
message_ptr += 5;
break;
}
}
if (DeviceGroupItemShared(true, item)) {
if (item == DGR_ITEM_POWER) {
for (uint32_t i = 0; i < devices_present; i++) {
uint32_t mask = 1 << i;
bool on = (value & mask);
if (on != (power & mask)) ExecuteCommandPower(i + 1, (on ? POWER_ON : POWER_OFF), SRC_REMOTE);
}
}
else {
XdrvCall(FUNC_DEVICE_GROUP_REQUEST);
}
}
}
XdrvMailbox.command_code = DGR_ITEM_EOL;
XdrvCall(FUNC_DEVICE_GROUP_REQUEST);
Settings.light_fade = light_fade;
processing_remote_device_message = false;
#ifdef DEVICE_GROUPS_DEBUG
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("<processed"));
#endif // DEVICE_GROUPS_DEBUG
return;
badmsg:
AddLog_P2(LOG_LEVEL_ERROR, PSTR("DGR: malformed message received from %s"), IPAddressToString(remote_ip));
#ifdef DEVICE_GROUPS_DEBUG
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("packet_length=%u, offset=%u"), packet_length, message_ptr - packet);
#endif // DEVICE_GROUPS_DEBUG
processing_remote_device_message = false;
}
void DeviceGroupsLoop(void)
{
if (!Settings.flag4.device_groups_enabled) return;
if (udp_connected) {
if (!udp_was_connected) {
udp_was_connected = true;
if (!device_groups_initialized) DeviceGroupsInit();
if (device_groups_initialization_failed) return;
for (uint32_t device_group_index = 0; device_group_index < device_group_count; device_group_index++) {
device_group * device_group = &device_groups[device_group_index];
char * message_ptr = &device_group->message[device_group->message_header_length];
if (!++outgoing_sequence) outgoing_sequence = 1;
*message_ptr++ = outgoing_sequence & 0xff;
*message_ptr++ = outgoing_sequence >> 8;
*message_ptr++ = DGR_FLAG_RESET | DGR_FLAG_STATUS_REQUEST;
*message_ptr++ = 0;
device_group->message_length = message_ptr - device_group->message;
device_group->initial_status_requests_remaining = 5;
device_group->next_ack_check_time = millis() + 1000;
}
waiting_for_acks = true;
}
if (device_groups_initialization_failed) return;
if (waiting_for_acks) {
uint32_t now = millis();
waiting_for_acks = false;
for (uint32_t device_group_index = 0; device_group_index < device_group_count; device_group_index++) {
device_group * device_group = &device_groups[device_group_index];
if (device_group->next_ack_check_time) {
if (device_group->next_ack_check_time <= now) {
if (device_group->initial_status_requests_remaining) {
#ifdef DEVICE_GROUPS_DEBUG
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("DGR: sending initial status request for group %s"), device_group->group_name);
#endif // DEVICE_GROUPS_DEBUG
if (--device_group->initial_status_requests_remaining) {
SendDeviceGroupPacket(IPAddress(239,255,255,250), device_group->message, device_group->message_length, PSTR("Initial"));
device_group->message[device_group->message_header_length + 2] = DGR_FLAG_STATUS_REQUEST; // The reset flag is on only for the first packet - turn it off now
device_group->next_ack_check_time = now + 200;
waiting_for_acks = true;
}
else {
device_group->next_ack_check_time = 0;
_SendDeviceGroupMessage(device_group_index, DGR_MSGTYP_FULL_STATUS);
}
}
else {
bool acked = true;
struct device_group_member ** flink = &device_group->device_group_members;
struct device_group_member * device_group_member;
while ((device_group_member = *flink)) {
if (device_group_member->acked_sequence != outgoing_sequence) {
if (device_group_member->timeout_time && device_group_member->timeout_time < now) {
#ifdef DEVICE_GROUPS_DEBUG
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("DGR: removing member %s (%p)"), IPAddressToString(device_group_member->ip_address), device_group_member);
#endif // DEVICE_GROUPS_DEBUG
*flink = device_group_member->flink;
free(device_group_member);
}
else {
#ifdef DEVICE_GROUPS_DEBUG
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("DGR: sending %u-byte device group %s packet via unicast to %s, sequence %u, last message acked=%u"), device_group->message_length, device_group->group_name, IPAddressToString(device_group_member->ip_address), outgoing_sequence, device_group_member->acked_sequence);
#endif // DEVICE_GROUPS_DEBUG
SendDeviceGroupPacket(device_group_member->ip_address, device_group->message, device_group->message_length, PSTR("Unicast"));
if (!device_group_member->timeout_time) device_group_member->timeout_time = now + 15000;
acked = false;
flink = &device_group_member->flink;
}
}
else {
flink = &device_group_member->flink;
}
}
if (acked) {
device_group->next_ack_check_time = 0;
device_group->message_length = 0;
}
else {
device_group->next_ack_check_time = now + 500;
waiting_for_acks = true;
}
}
}
else {
waiting_for_acks = true;
}
}
}
}
}
else {
udp_was_connected = false;
}
}
#endif // USE_DEVICE_GROUPS

View File

@ -541,6 +541,9 @@ void ExecuteCommandPower(uint32_t device, uint32_t state, uint32_t source)
case POWER_TOGGLE:
power ^= mask;
}
#ifdef USE_DEVICE_GROUPS
if (SRC_REMOTE != source && SRC_RETRY != source) SendLocalDeviceGroupMessage(DGR_MSGTYP_UPDATE, DGR_ITEM_POWER, power);
#endif // USE_DEVICE_GROUPS
SetDevicePower(power, source);
#ifdef USE_DOMOTICZ
DomoticzUpdatePowerState(device);

View File

@ -85,6 +85,7 @@ void PollUdp(void)
// AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("\n%s"), packet_buffer);
// Simple Service Discovery Protocol (SSDP)
if (Settings.flag2.emulation) {
#ifdef USE_SCRIPT_HUE
if (!udp_response_mutex && (strstr_P(packet_buffer, PSTR("M-SEARCH")) != nullptr)) {
#else
@ -131,8 +132,15 @@ void PollUdp(void)
#endif // USE_EMULATION_HUE
udp_response_mutex = false;
continue;
}
}
#ifdef USE_DEVICE_GROUPS
if (Settings.flag4.device_groups_enabled && !strncmp_P(packet_buffer, kDeviceGroupMessage, sizeof(DEVICE_GROUP_MESSAGE) - 1)) {
ProcessDeviceGroupMessage(packet_buffer, len);
}
#endif // USE_DEVICE_GROUPS
}
optimistic_yield(100);
}

View File

@ -559,7 +559,11 @@ void WifiCheck(uint8_t param)
StopWebserver();
}
#ifdef USE_EMULATION
#ifdef USE_DEVICE_GROUPS
if (Settings.flag2.emulation || Settings.flag4.device_groups_enabled) { UdpConnect(); }
#else // USE_DEVICE_GROUPS
if (Settings.flag2.emulation) { UdpConnect(); }
#endif // USE_DEVICE_GROUPS
#endif // USE_EMULATION
#endif // USE_WEBSERVER

View File

@ -277,7 +277,8 @@ enum XsnsFunctions {FUNC_SETTINGS_OVERRIDE, FUNC_PIN_STATE, FUNC_MODULE_INIT, FU
FUNC_SET_POWER, FUNC_SET_DEVICE_POWER, FUNC_SHOW_SENSOR, FUNC_ANY_KEY,
FUNC_ENERGY_EVERY_SECOND, FUNC_ENERGY_RESET,
FUNC_RULES_PROCESS, FUNC_SERIAL, FUNC_FREE_MEM, FUNC_BUTTON_PRESSED,
FUNC_WEB_ADD_BUTTON, FUNC_WEB_ADD_MAIN_BUTTON, FUNC_WEB_ADD_HANDLER, FUNC_SET_CHANNELS, FUNC_SET_SCHEME, FUNC_HOTPLUG_SCAN};
FUNC_WEB_ADD_BUTTON, FUNC_WEB_ADD_MAIN_BUTTON, FUNC_WEB_ADD_HANDLER, FUNC_SET_CHANNELS, FUNC_SET_SCHEME, FUNC_HOTPLUG_SCAN,
FUNC_DEVICE_GROUP_REQUEST };
enum AddressConfigSteps { ADDR_IDLE, ADDR_RECEIVE, ADDR_SEND };
@ -299,8 +300,32 @@ enum SettingsTextIndex { SET_OTAURL,
SET_FRIENDLYNAME5, SET_FRIENDLYNAME6, SET_FRIENDLYNAME7, SET_FRIENDLYNAME8,
SET_BUTTON1, SET_BUTTON2, SET_BUTTON3, SET_BUTTON4, SET_BUTTON5, SET_BUTTON6, SET_BUTTON7, SET_BUTTON8,
SET_BUTTON9, SET_BUTTON10, SET_BUTTON11, SET_BUTTON12, SET_BUTTON13, SET_BUTTON14, SET_BUTTON15, SET_BUTTON16,
SET_MQTT_GRP_TOPIC2, SET_MQTT_GRP_TOPIC3, SET_MQTT_GRP_TOPIC4,
SET_MAX };
enum DeviceGroupMessageType { DGR_MSGTYP_FULL_STATUS, DGR_MSGTYP_PARTIAL_UPDATE, DGR_MSGTYP_UPDATE, DGR_MSGTYP_UPDATE_MORE_TO_COME, DGR_MSGTYP_UPDATE_DIRECT, DGR_MSGTYP_REUPDATE };
enum DeviceGroupMessageFlag { DGR_FLAG_RESET = 1, DGR_FLAG_STATUS_REQUEST = 2, DGR_FLAG_FULL_STATUS = 4, DGR_FLAG_ACK = 8, DGR_FLAG_MORE_TO_COME = 16, DGR_FLAG_DIRECT = 32 };
enum DeviceGroupItem { DGR_ITEM_EOL, DGR_ITEM_STATUS,
DGR_ITEM_LIGHT_FADE, DGR_ITEM_LIGHT_SPEED, DGR_ITEM_LIGHT_BRI, DGR_ITEM_LIGHT_SCHEME, DGR_ITEM_LIGHT_FIXED_COLOR,
DGR_ITEM_BRI_MIN, DGR_ITEM_BRI_PRESET_LOW, DGR_ITEM_BRI_PRESET_HIGH, DGR_ITEM_BRI_POWER_ON,
// Add new 8-bit items before this line
DGR_ITEM_LAST_8BIT, DGR_ITEM_MAX_8BIT = 63,
DGR_ITEM_ACK,
DGR_ITEM_ANALOG1, DGR_ITEM_ANALOG2, DGR_ITEM_ANALOG3, DGR_ITEM_ANALOG4, DGR_ITEM_ANALOG5,
// Add new 16-bit items before this line
DGR_ITEM_LAST_16BIT, DGR_ITEM_MAX_16BIT = 127,
DGR_ITEM_POWER,
// Add new 32-bit items before this line
DGR_ITEM_LAST_32BIT, DGR_ITEM_MAX_32BIT = 191,
// Add new string items before this line
DGR_ITEM_LAST_STRING, DGR_ITEM_MAX_STRING = 223,
DGR_ITEM_LIGHT_CHANNELS };
enum DeviceGroupShareItem { DGR_SHARE_POWER = 1, DGR_SHARE_LIGHT_BRI = 2, DGR_SHARE_LIGHT_FADE = 4, DGR_SHARE_LIGHT_SCHEME = 8,
DGR_SHARE_LIGHT_COLOR = 16, DGR_SHARE_BRI_MIN = 32 };
enum CommandSource { SRC_IGNORE, SRC_MQTT, SRC_RESTART, SRC_BUTTON, SRC_SWITCH, SRC_BACKLOG, SRC_SERIAL, SRC_WEBGUI, SRC_WEBCOMMAND, SRC_WEBCONSOLE, SRC_PULSETIMER,
SRC_TIMER, SRC_RULE, SRC_MAXPOWER, SRC_MAXENERGY, SRC_OVERTEMP, SRC_LIGHT, SRC_KNX, SRC_DISPLAY, SRC_WEMO, SRC_HUE, SRC_RETRY, SRC_REMOTE, SRC_SHUTTER,
SRC_MAX };

View File

@ -329,6 +329,9 @@ void loop(void)
#ifdef ROTARY_V1
RotaryLoop();
#endif
#ifdef USE_DEVICE_GROUPS
DeviceGroupsLoop();
#endif // USE_DEVICE_GROUPS
BacklogLoop();
if (TimeReached(state_50msecond)) {

View File

@ -55,6 +55,9 @@ extern "C" void custom_crash_callback(struct rst_info * rst_info, uint32_t stack
#ifdef USE_EMULATION_WEMO
#define USE_EMULATION
#endif
#ifdef USE_DEVICE_GROUPS
#define USE_EMULATION
#endif
#ifdef USE_MQTT_TLS
const uint16_t WEB_LOG_SIZE = 2000; // Max number of characters in weblog
@ -246,6 +249,7 @@ extern "C" void custom_crash_callback(struct rst_info * rst_info, uint32_t stack
#undef USE_EMULATION // Disable Belkin WeMo and Hue Bridge emulation for Alexa (-16k code, -2k mem)
#undef USE_EMULATION_HUE // Disable Hue Bridge emulation for Alexa (+14k code, +2k mem common)
#undef USE_EMULATION_WEMO // Disable Belkin WeMo emulation for Alexa (+6k code, +2k mem common)
#undef USE_DEVICE_GROUPS // Disable support for device groups (+3k5 code)
#undef DEBUG_THEO // Disable debug code
#undef USE_DEBUG_DRIVER // Disable debug code
#endif // FIRMWARE_KNX_NO_EMULATION
@ -283,6 +287,7 @@ extern "C" void custom_crash_callback(struct rst_info * rst_info, uint32_t stack
#undef USE_DEEPSLEEP // Disable support for deepsleep (+1k code)
#undef USE_EXS_DIMMER // Disable support for EX-Store WiFi Dimmer
#undef USE_HOTPLUG // Disable support for HotPlug
#undef USE_DEVICE_GROUPS // Disable support for device groups (+3k5 code)
#undef USE_ENERGY_SENSOR // Disable energy sensors (-14k code)
#undef USE_PZEM004T // Disable PZEM004T energy sensor
@ -359,6 +364,7 @@ extern "C" void custom_crash_callback(struct rst_info * rst_info, uint32_t stack
#undef USE_DEEPSLEEP // Disable support for deepsleep (+1k code)
#undef USE_EXS_DIMMER // Disable support for EX-Store WiFi Dimmer
#undef USE_HOTPLUG // Disable support for HotPlug
#undef USE_DEVICE_GROUPS // Disable support for device groups (+3k5 code)
// -- Optional light modules ----------------------
//#undef USE_LIGHT // Also disable all Dimmer/Light support
@ -580,6 +586,7 @@ extern "C" void custom_crash_callback(struct rst_info * rst_info, uint32_t stack
#undef USE_DEEPSLEEP // Disable support for deepsleep (+1k code)
#undef USE_EXS_DIMMER // Disable support for EX-Store WiFi Dimmer
#undef USE_HOTPLUG // Disable support for HotPlug
#undef USE_DEVICE_GROUPS // Disable support for device groups (+3k5 code)
// -- Optional light modules ----------------------
#undef USE_LIGHT // Disable support for lights
@ -700,6 +707,14 @@ extern "C" void custom_crash_callback(struct rst_info * rst_info, uint32_t stack
#undef USE_MQTT_TLS_CA_CERT
#endif
#ifdef USE_DEVICE_GROUPS
#define SendDeviceGroupMessage(DEVICE_INDEX, REQUEST_TYPE, ...) _SendDeviceGroupMessage(DEVICE_INDEX, REQUEST_TYPE, __VA_ARGS__, 0)
#define SendLocalDeviceGroupMessage(REQUEST_TYPE, ...) _SendDeviceGroupMessage(0, REQUEST_TYPE, __VA_ARGS__, 0)
#define DEVICE_GROUP_MESSAGE "M-TASMOTA_DGR/"
const char kDeviceGroupMessage[] PROGMEM = DEVICE_GROUP_MESSAGE;
uint8_t device_group_count = 1;
#endif // USE_DEVICE_GROUPS
#ifdef DEBUG_TASMOTA_CORE
#define DEBUG_CORE_LOG(...) AddLog_Debug(__VA_ARGS__)
#else

View File

@ -2970,7 +2970,11 @@ bool Xdrv01(uint8_t function)
case FUNC_LOOP:
PollDnsWebserver();
#ifdef USE_EMULATION
#ifdef USE_DEVICE_GROUPS
if (Settings.flag2.emulation || Settings.flag4.device_groups_enabled) { PollUdp(); }
#else // USE_DEVICE_GROUPS
if (Settings.flag2.emulation) { PollUdp(); }
#endif // USE_DEVICE_GROUPS
#endif // USE_EMULATION
break;
case FUNC_COMMAND:

View File

@ -871,10 +871,17 @@ void CmndPublish(void)
void CmndGroupTopic(void)
{
#ifdef USE_DEVICE_GROUPS
uint32_t settings_text_index = (XdrvMailbox.index <= 1 ? SET_MQTT_GRP_TOPIC : SET_MQTT_GRP_TOPIC2 + XdrvMailbox.index - 2);
#endif // USE_DEVICE_GROUPS
if (XdrvMailbox.data_len > 0) {
MakeValidMqtt(0, XdrvMailbox.data);
if (!strcmp(XdrvMailbox.data, mqtt_client)) { SetShortcutDefault(); }
#ifdef USE_DEVICE_GROUPS
SettingsUpdateText(settings_text_index, (SC_DEFAULT == Shortcut()) ? MQTT_GRPTOPIC : XdrvMailbox.data);
#else // USE_DEVICE_GROUPS
SettingsUpdateText(SET_MQTT_GRP_TOPIC, (SC_DEFAULT == Shortcut()) ? MQTT_GRPTOPIC : XdrvMailbox.data);
#endif // USE_DEVICE_GROUPS
restart_flag = 2;
}
ResponseCmndChar(SettingsText(SET_MQTT_GRP_TOPIC));

View File

@ -1767,6 +1767,10 @@ void LightAnimate(void)
Light.update = true;
}
if (Light.update) {
#ifdef USE_DEVICE_GROUPS
if (Light.power) LightSendDeviceGroupStatus();
#endif // USE_DEVICE_GROUPS
uint16_t cur_col_10[LST_MAX]; // 10 bits resolution
Light.update = false;
@ -2077,6 +2081,86 @@ void calcGammaBulbs(uint16_t cur_col_10[5]) {
}
}
#ifdef USE_DEVICE_GROUPS
void LightSendDeviceGroupStatus()
{
uint8_t channels[LST_MAX];
light_state.getChannels(channels);
SendLocalDeviceGroupMessage(DGR_MSGTYP_UPDATE, DGR_ITEM_LIGHT_SCHEME, Settings.light_scheme, DGR_ITEM_LIGHT_CHANNELS, channels,
DGR_ITEM_LIGHT_BRI, (power ? light_state.getBri() : 0));
}
void LightHandleDeviceGroupRequest()
{
static bool send_state = false;
uint32_t value = XdrvMailbox.payload;
switch (XdrvMailbox.command_code) {
case DGR_ITEM_EOL:
LightAnimate();
if (send_state && !(XdrvMailbox.index & DGR_FLAG_MORE_TO_COME)) {
light_controller.saveSettings();
if (Settings.flag3.hass_tele_on_power) { // SetOption59 - Send tele/%topic%/STATE in addition to stat/%topic%/RESULT
MqttPublishTeleState();
}
send_state = false;
}
break;
case DGR_ITEM_LIGHT_BRI:
if (light_state.getBri() != value) {
light_controller.changeBri(value);
send_state = true;
}
break;
case DGR_ITEM_LIGHT_SCHEME:
if (Settings.light_scheme != value) {
Settings.light_scheme = value;
send_state = true;
}
break;
case DGR_ITEM_LIGHT_CHANNELS:
light_controller.changeChannels((uint8_t *)XdrvMailbox.data);
send_state = true;
break;
case DGR_ITEM_LIGHT_FIXED_COLOR:
{
power_t save_power = Light.power;
if (value) {
bool save_decimal_text = Settings.flag.decimal_text;
char str[16];
XdrvMailbox.index = 2;
XdrvMailbox.data_len = sprintf_P(str, PSTR("%u"), value);
XdrvMailbox.data = str;
CmndSupportColor();
Settings.flag.decimal_text = save_decimal_text;
}
else {
Light.fixed_color_index = 0;
XdrvMailbox.index = 1;
XdrvMailbox.payload = light_state.BriToDimmer(light_state.getBri());
CmndWhite();
}
if (Light.power != save_power) {
XdrvMailbox.index = save_power;
LightSetPower();
}
send_state = true;
}
break;
case DGR_ITEM_LIGHT_SPEED:
if (Settings.light_speed != value && value > 0 && value <= 40) {
Settings.light_speed = value;
send_state = true;
}
break;
case DGR_ITEM_STATUS:
SendLocalDeviceGroupMessage(DGR_MSGTYP_PARTIAL_UPDATE, DGR_ITEM_LIGHT_FADE, Settings.light_fade,
DGR_ITEM_LIGHT_SPEED, Settings.light_speed);
LightSendDeviceGroupStatus();
break;
}
}
#endif // USE_DEVICE_GROUPS
/*********************************************************************************************\
* Commands
\*********************************************************************************************/
@ -2339,6 +2423,9 @@ void CmndScheme(void)
Light.wheel = parm[1];
}
Settings.light_scheme = XdrvMailbox.payload;
#ifdef USE_DEVICE_GROUPS
SendLocalDeviceGroupMessage(DGR_MSGTYP_UPDATE, DGR_ITEM_LIGHT_SCHEME, Settings.light_scheme);
#endif // USE_DEVICE_GROUPS
if (LS_WAKEUP == Settings.light_scheme) {
Light.wakeup_active = 3;
}
@ -2516,6 +2603,9 @@ void CmndFade(void)
Settings.light_fade ^= 1;
break;
}
#ifdef USE_DEVICE_GROUPS
if (XdrvMailbox.payload >= 0 && XdrvMailbox.payload <= 2) SendLocalDeviceGroupMessage(DGR_MSGTYP_UPDATE, DGR_ITEM_LIGHT_FADE, Settings.light_fade);
#endif // USE_DEVICE_GROUPS
if (!Settings.light_fade) { Light.fade_running = false; }
ResponseCmndStateText(Settings.light_fade);
}
@ -2536,6 +2626,9 @@ void CmndSpeed(void)
}
if ((XdrvMailbox.payload > 0) && (XdrvMailbox.payload <= 40)) {
Settings.light_speed = XdrvMailbox.payload;
#ifdef USE_DEVICE_GROUPS
SendLocalDeviceGroupMessage(DGR_MSGTYP_UPDATE, DGR_ITEM_LIGHT_SPEED, Settings.light_speed);
#endif // USE_DEVICE_GROUPS
}
ResponseCmndNumber(Settings.light_speed);
}
@ -2588,6 +2681,11 @@ bool Xdrv04(uint8_t function)
case FUNC_EVERY_50_MSECOND:
LightAnimate();
break;
#ifdef USE_DEVICE_GROUPS
case FUNC_DEVICE_GROUP_REQUEST:
LightHandleDeviceGroupRequest();
break;
#endif // USE_DEVICE_GROUPS
case FUNC_SET_POWER:
LightSetPower();
break;