diff --git a/tasmota/my_user_config.h b/tasmota/my_user_config.h index 5d9dee34e..67b824403 100644 --- a/tasmota/my_user_config.h +++ b/tasmota/my_user_config.h @@ -420,8 +420,8 @@ #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 (+5k code) - #define USE_DEVICE_GROUPS_SEND // Add support for the DevGroupSend command (+0k6 code) +#define USE_DEVICE_GROUPS // Add support for device groups (+5k6 code) + #define USE_DEVICE_GROUPS_SEND // Add support for the DevGroupSend command (+0k5 code) #define USE_PWM_DIMMER // Add support for MJ-SD01/acenx/NTONPOWER PWM dimmers (+2k5 code) #define USE_PWM_DIMMER_REMOTE // Add support for remote switches to PWM Dimmer, also adds device groups support (+1k code plus device groups size) //#define USE_KEELOQ // Add support for Jarolift rollers by Keeloq algorithm (+4k5 code) diff --git a/tasmota/support_device_groups.ino b/tasmota/support_device_groups.ino index 26684cafc..7c272f267 100644 --- a/tasmota/support_device_groups.ino +++ b/tasmota/support_device_groups.ino @@ -41,6 +41,7 @@ struct device_group { uint32_t next_announcement_time; uint32_t next_ack_check_time; uint32_t member_timeout_time; + uint16_t outgoing_sequence; uint16_t last_full_status_sequence; uint16_t message_length; uint16_t ack_check_interval; @@ -58,12 +59,13 @@ struct device_group { }; struct device_group * device_groups; +char * log_ptr; +int log_remaining; uint32_t next_check_time = 0; -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 ignore_dgr_sends = false; bool udp_was_connected = false; void DeviceGroupsInit(void) @@ -117,12 +119,21 @@ char * IPAddressToString(const IPAddress& ip_address) return buffer; } +void AddDeviceGroupLog(const char * format, ...) { + va_list ap; + va_start(ap, format); + int log_length = vsnprintf_P(log_ptr, log_remaining, format, ap); + va_end(ap); + log_ptr += log_length; + log_remaining -= log_length; +} + char * BeginDeviceGroupMessage(struct device_group * device_group, uint16_t flags, bool hold_sequence = false) { char * message_ptr = &device_group->message[device_group->message_header_length]; - if (!hold_sequence && !++outgoing_sequence) outgoing_sequence = 1; - *message_ptr++ = outgoing_sequence & 0xff; - *message_ptr++ = outgoing_sequence >> 8; + if (!hold_sequence && !++device_group->outgoing_sequence) device_group->outgoing_sequence = 1; + *message_ptr++ = device_group->outgoing_sequence & 0xff; + *message_ptr++ = device_group->outgoing_sequence >> 8; *message_ptr++ = flags & 0xff; *message_ptr++ = flags >> 8; return message_ptr; @@ -162,17 +173,241 @@ bool DeviceGroupItemShared(bool incoming, uint8_t item) 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) +void SendReceiveDeviceGroupPacket(struct device_group * device_group, struct device_group_member * device_group_member, char * message, int message_length, bool received) { - if (!ip) ip = IPAddress(239,255,255,250); - for (int attempt = 1; attempt <= 5; attempt++) { - if (PortUdp.beginPacket(ip, 1900)) { - PortUdp_write(packet, len); - if (PortUdp.endPacket()) return; + char log_buffer[LOGSZ]; + bool item_processed = false; + uint16_t message_sequence; + uint16_t flags; + int device_group_index = device_group - device_groups; + + log_ptr = log_buffer; + log_remaining = sizeof(log_buffer); + AddDeviceGroupLog(PSTR("DGR: %s %s message"), (received ? PSTR("Received") : PSTR("Sending")), device_group->group_name); + if (received || device_group_member) AddDeviceGroupLog(PSTR(" %s %s"), (received ? PSTR("from") : PSTR("to")), (device_group_member ? IPAddressToString(device_group_member->ip_address) : PSTR("local"))); + + // Find the start of the actual message (after the http header). + char * message_end_ptr = message + message_length; + char * message_ptr = strstr_P(message, PSTR("\n\n")); + if (message_ptr == nullptr) return; + message_ptr += 2; + + // Get the message sequence and flags. + if (message_ptr + 4 > message_end_ptr) 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; + AddDeviceGroupLog(PSTR(": seq=%u, flags=%u"), message_sequence, flags); + + // If this is an announcement, just log it. + if (flags == DGR_FLAG_ANNOUNCEMENT) goto write_log; + + // If this is a received 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 (received && device_group_member && (message_sequence > device_group_member->acked_sequence || device_group_member->acked_sequence - message_sequence < 64536)) { + device_group_member->acked_sequence = message_sequence; } - delay(10); + goto write_log; + } + + // If this is a received message, send an ack message to the sender. + if (device_group_member) { + if (received) { + if (!(flags & DGR_FLAG_MORE_TO_COME)) { + *(message_ptr - 2) = DGR_FLAG_ACK; + *(message_ptr - 1) = 0; + SendReceiveDeviceGroupPacket(device_group, device_group_member, message, message_ptr - message, false); + } + } + + // If we're sending this message directly to a member, it's a resend of the lst message. + else { + AddDeviceGroupLog(PSTR(", last ack=%u"), device_group_member->acked_sequence); + goto write_log; + } + } + + // If this is a status request message, skip item processing. + if ((flags & DGR_FLAG_STATUS_REQUEST)) goto write_log; + + // If this is a received message, ... + if (received) { + + // If we already processed this or a later message from this group member, ignore this message. + if (device_group_member) { + if (message_sequence <= device_group_member->received_sequence) { + if (message_sequence == device_group_member->received_sequence || device_group_member->received_sequence - message_sequence > 64536) { + AddDeviceGroupLog(PSTR(" (old)")); + goto write_log; + } + } + device_group_member->received_sequence = message_sequence; + } + + /* + XdrvMailbox + bool grpflg + bool usridx + uint16_t command_code Item code + uint32_t index 0:15 Flags, 16:31 Message sequence + uint32_t data_len String item value length + int32_t payload Integer item value + char *topic Pointer to device group index + 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 | message_sequence << 16; + XdrvMailbox.topic = (char *)&device_group_index; + if (flags & (DGR_FLAG_MORE_TO_COME | DGR_FLAG_DIRECT)) skip_light_fade = true; + + // Set the flag to ignore device group send message request so callbacks from the drivers do not + // send updates. + ignore_dgr_sends = true; + } + + uint8_t item; + int32_t value; + for (;;) { + if (message_ptr >= message_end_ptr) 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_PRESET_LOW: + case DGR_ITEM_BRI_PRESET_HIGH: + case DGR_ITEM_BRI_POWER_ON: + case DGR_ITEM_POWER: + case DGR_ITEM_EVENT: + case DGR_ITEM_LIGHT_CHANNELS: + break; + default: + AddLog_P2(LOG_LEVEL_ERROR, PSTR("DGR: *** Invalid item=%u"), item); + } +#endif // DEVICE_GROUPS_DEBUG + + AddDeviceGroupLog(PSTR(", %u="), item); + if (received) XdrvMailbox.data = message_ptr; + 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; +#ifdef USE_DEVICE_GROUPS_SEND + device_group->values_32bit[item - DGR_ITEM_MAX_16BIT - 1] = (item == DGR_ITEM_POWER ? value & 0xffffff : value); +#endif // USE_DEVICE_GROUPS_SEND + } +#ifdef USE_DEVICE_GROUPS_SEND + else { + device_group->values_16bit[item - DGR_ITEM_MAX_8BIT - 1] = value; + } +#endif // USE_DEVICE_GROUPS_SEND + } +#ifdef USE_DEVICE_GROUPS_SEND + else { + device_group->values_8bit[item] = value; + } +#endif // USE_DEVICE_GROUPS_SEND + AddDeviceGroupLog(PSTR("%u"), value); + } + else if (item <= DGR_ITEM_MAX_STRING) { + value = strlen(message_ptr); + if (message_ptr + value >= message_end_ptr) goto badmsg; // Malformed message + AddDeviceGroupLog(PSTR("'%s'"), message_ptr); + message_ptr += value + 1; + } + else { + switch (item) { + case DGR_ITEM_LIGHT_CHANNELS: + AddDeviceGroupLog(PSTR("%08X%02X"), *(uint32_t *)message_ptr, *(message_ptr + 4)); + message_ptr += 5; + break; + } + } + + if (received && DeviceGroupItemShared(true, item)) { + item_processed = true; + XdrvMailbox.command_code = item; + XdrvMailbox.payload = value; + XdrvMailbox.data_len = value; + AddDeviceGroupLog(PSTR("*")); + switch (item) { + case DGR_ITEM_POWER: + if (device_group->local) { + uint8_t mask_devices = value >> 24; + if (mask_devices > devices_present) mask_devices = devices_present; + 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); + } + } + break; + case DGR_ITEM_EVENT: + CmndEvent(); + break; + case DGR_ITEM_COMMAND: + ExecuteCommand(XdrvMailbox.data, SRC_REMOTE); + break; + } + XdrvCall(FUNC_DEVICE_GROUP_ITEM); + } + } + + if (received) { + if (item_processed) { + XdrvMailbox.command_code = DGR_ITEM_EOL; + XdrvCall(FUNC_DEVICE_GROUP_ITEM); + } + + skip_light_fade = false; + ignore_dgr_sends = false; + } + +write_log: + AddLog_P(LOG_LEVEL_DEBUG_MORE, log_buffer); + + // If this is a received status request message, then if the requestor didn't just ack our + // previous full status update, send a full status update. + if (received) { + 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); + } + } + } + + // If this is a message being sent, send it. + else { + for (int attempt = 1; attempt <= 5; attempt++) { + if (PortUdp.beginPacket((device_group_member ? device_group_member->ip_address : IPAddress(239,255,255,250)), 1900)) { + PortUdp_write(message, message_length); + if (PortUdp.endPacket()) return; + } + delay(10); + } + AddLog_P2(LOG_LEVEL_ERROR, PSTR("DGR: Error sending message")); + } + goto cleanup; + +badmsg: + AddLog_P(LOG_LEVEL_ERROR, PSTR("%s ** incorrect length"), log_buffer); + +cleanup: + if (received) { + skip_light_fade = false; + ignore_dgr_sends = false; } - AddLog_P2(LOG_LEVEL_ERROR, PSTR("DGR: Error sending %s packet"), label); } void _SendDeviceGroupMessage(uint8_t device_group_index, DevGroupMessageType message_type, ...) @@ -184,7 +419,7 @@ void _SendDeviceGroupMessage(uint8_t device_group_index, DevGroupMessageType mes if (!udp_connected) return; // If we're currently processing a remote device message, ignore this request. - if (processing_remote_device_message && message_type != DGR_MSGTYPE_UPDATE_COMMAND) return; + if (ignore_dgr_sends && message_type != DGR_MSGTYPE_UPDATE_COMMAND) return; // Get a pointer to the device information for this device. struct device_group * device_group = &device_groups[device_group_index]; @@ -192,35 +427,44 @@ void _SendDeviceGroupMessage(uint8_t device_group_index, DevGroupMessageType mes // If we're still sending initial status requests, ignore this request. if (device_group->initial_status_requests_remaining) return; + // Load the message header, sequence and flags. +#ifdef DEVICE_GROUPS_DEBUG + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("DGR: Building %s %spacket"), device_group->group_name, (message_type == DGR_MSGTYP_FULL_STATUS ? PSTR("full status ") : PSTR(""))); +#endif // DEVICE_GROUPS_DEBUG +#ifdef USE_DEVICE_GROUPS_SEND +#endif // USE_DEVICE_GROUPS_SEND + uint16_t original_sequence = device_group->outgoing_sequence; + uint16_t flags = 0; + if (message_type == DGR_MSGTYP_UPDATE_MORE_TO_COME) + flags = DGR_FLAG_MORE_TO_COME; + else if (message_type == DGR_MSGTYP_UPDATE_DIRECT) + flags = DGR_FLAG_DIRECT; + char * message_ptr = BeginDeviceGroupMessage(device_group, flags, building_status_message || message_type == DGR_MSGTYP_PARTIAL_UPDATE); + // 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) { + device_group->last_full_status_sequence = device_group->outgoing_sequence; + device_group->message_length = 0; // 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); + if (device_group->local) SendLocalDeviceGroupMessage(DGR_MSGTYP_PARTIAL_UPDATE, DGR_ITEM_POWER, power); XdrvMailbox.index = device_group_index << 16; XdrvMailbox.command_code = DGR_ITEM_STATUS; XdrvMailbox.topic = (char *)&device_group_index; XdrvCall(FUNC_DEVICE_GROUP_ITEM); building_status_message = false; - // If we have nothing to share with the other members, restore the message sequence and return. + // If we have nothing to share with the other members, just send the EOL item. if (!device_group->message_length) { - if (!--outgoing_sequence) outgoing_sequence = -1; - return; + *message_ptr++ = 0; + device_group->message_length = message_ptr - device_group->message; } - device_group->last_full_status_sequence = outgoing_sequence; // Set the status update flag in the outgoing message. *(message_ptr + 2) |= DGR_FLAG_FULL_STATUS; @@ -229,7 +473,6 @@ void _SendDeviceGroupMessage(uint8_t device_group_index, DevGroupMessageType mes else { #ifdef USE_DEVICE_GROUPS_SEND bool escaped; - bool use_command; char chr; char oper; uint32_t old_value; @@ -240,6 +483,7 @@ void _SendDeviceGroupMessage(uint8_t device_group_index, DevGroupMessageType mes uint32_t value; void * value_ptr; } item_array[32]; +// bool items_added = false; bool shared; uint8_t item; uint32_t value; @@ -247,26 +491,11 @@ void _SendDeviceGroupMessage(uint8_t device_group_index, DevGroupMessageType mes struct item * item_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 - - 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 = BeginDeviceGroupMessage(device_group, value, building_status_message || message_type == DGR_MSGTYP_PARTIAL_UPDATE); - // Build an array of all the items and values in this update. memset(item_array, 0, sizeof(item_array)); item_ptr = item_array; #ifdef USE_DEVICE_GROUPS_SEND - use_command = (message_type == DGR_MSGTYPE_UPDATE_COMMAND); - if (use_command) { + if (message_type == DGR_MSGTYPE_UPDATE_COMMAND) { value_ptr = XdrvMailbox.data; while ((item = strtoul(value_ptr, &value_ptr, 0))) { item_ptr->item = item; @@ -405,7 +634,7 @@ void _SendDeviceGroupMessage(uint8_t device_group_index, DevGroupMessageType mes } } #ifdef DEVICE_GROUPS_DEBUG - AddLog_P2(LOG_LEVEL_DEBUG, PSTR("%u items carried over from previous update"), kept_item_count); + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("DGR: %u items carried over"), kept_item_count); #endif // DEVICE_GROUPS_DEBUG } @@ -416,52 +645,28 @@ void _SendDeviceGroupMessage(uint8_t device_group_index, DevGroupMessageType mes shared = true; if (!device_group_index) shared = DeviceGroupItemShared(false, item); if (shared) { +// items_added = true; *message_ptr++ = item; // For integer items, add the value to the message. if (item <= DGR_ITEM_MAX_32BIT) { value = item_ptr->value; -//#ifdef DEVICE_GROUPS_DEBUG - AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR(">%u=%u"), item, value); -//#endif // DEVICE_GROUPS_DEBUG *message_ptr++ = value & 0xff; if (item > DGR_ITEM_MAX_8BIT) { -#ifdef USE_DEVICE_GROUPS_SEND - old_value = value; -#endif // USE_DEVICE_GROUPS_SEND value >>= 8; *message_ptr++ = value & 0xff; if (item > DGR_ITEM_MAX_16BIT) { value >>= 8; *message_ptr++ = value & 0xff; value >>= 8; - // For the power item, the device count is overlayed onto the highest 8 bits. if (item == DGR_ITEM_POWER) { if (!value) value = (device_group_index == 0 ? devices_present : 1); -#ifdef USE_DEVICE_GROUPS_SEND - else - old_value = old_value & 0xffffff; -#endif // USE_DEVICE_GROUPS_SEND } - *message_ptr++ = value; -#ifdef USE_DEVICE_GROUPS_SEND - device_group->values_32bit[item - DGR_ITEM_MAX_16BIT - 1] = old_value; -#endif // USE_DEVICE_GROUPS_SEND } -#ifdef USE_DEVICE_GROUPS_SEND - else { - device_group->values_16bit[item - DGR_ITEM_MAX_8BIT - 1] = old_value; - } -#endif // USE_DEVICE_GROUPS_SEND } -#ifdef USE_DEVICE_GROUPS_SEND - else { - device_group->values_8bit[item] = value; - } -#endif // USE_DEVICE_GROUPS_SEND } // For string items, add the null-terminated string to the message. @@ -472,9 +677,6 @@ void _SendDeviceGroupMessage(uint8_t device_group_index, DevGroupMessageType mes message_ptr += value; } *message_ptr++ = 0; -//#ifdef DEVICE_GROUPS_DEBUG - AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR(">%u='%s'"), item, item_ptr->value_ptr); -//#endif // DEVICE_GROUPS_DEBUG } // For special items, handle them individually. @@ -485,28 +687,39 @@ void _SendDeviceGroupMessage(uint8_t device_group_index, DevGroupMessageType mes for (int i = 0; i < 5; i++) { *message_ptr++ = (value_ptr ? *value_ptr++ : 0); } -//#ifdef DEVICE_GROUPS_DEBUG - AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR(">%u=%u,%u,%u,%u,%u"), item, *(message_ptr - 5), *(message_ptr - 4), *(message_ptr - 3), *(message_ptr - 2), *(message_ptr - 1)); -//#endif // DEVICE_GROUPS_DEBUG break; } } } } - // Add the EOL item code and calculate the message length. - *message_ptr++ = 0; - device_group->message_length = message_ptr - device_group->message; + // If we added any items, add the EOL item code and calculate the message length. +// if (items_added) { + *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; } + // If there is no message, restore the sequence number and return. + if (!device_group->message_length) { + device_group->outgoing_sequence = original_sequence; + 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(0,0,0,0), device_group->message, device_group->message_length, PSTR("Multicast")); + SendReceiveDeviceGroupPacket(device_group, nullptr, device_group->message, device_group->message_length, false); + +#ifdef USE_DEVICE_GROUPS_SEND + // If the device group is local, handle the message locally. + if (message_type == DGR_MSGTYPE_UPDATE_COMMAND && device_group->local) { + struct XDRVMAILBOX save_XdrvMailbox = XdrvMailbox; + SendReceiveDeviceGroupPacket(device_group, nullptr, device_group->message, device_group->message_length, true); + XdrvMailbox = save_XdrvMailbox; + } +#endif // USE_DEVICE_GROUPS_SEND uint32_t now = millis(); if (message_type == DGR_MSGTYP_UPDATE_MORE_TO_COME) { @@ -514,7 +727,7 @@ void _SendDeviceGroupMessage(uint8_t device_group_index, DevGroupMessageType mes device_group->next_ack_check_time = 0; } else { - device_group->ack_check_interval = 100; + device_group->ack_check_interval = 200; device_group->next_ack_check_time = now + device_group->ack_check_interval; if (device_group->next_ack_check_time < next_check_time) next_check_time = device_group->next_ack_check_time; device_group->member_timeout_time = now + DGR_MEMBER_TIMEOUT; @@ -524,10 +737,10 @@ void _SendDeviceGroupMessage(uint8_t device_group_index, DevGroupMessageType mes if (device_group->next_announcement_time < next_check_time) next_check_time = device_group->next_announcement_time; } -void ProcessDeviceGroupMessage(char * packet, int packet_length) +void ProcessDeviceGroupMessage(char * message, int message_length) { // Make the group name a null-terminated string. - char * message_group_name = packet + sizeof(DEVICE_GROUP_MESSAGE) - 1; + char * message_group_name = message + sizeof(DEVICE_GROUP_MESSAGE) - 1; char * message_ptr = strchr(message_group_name, ' '); if (message_ptr == nullptr) return; *message_ptr = 0; @@ -543,15 +756,15 @@ void ProcessDeviceGroupMessage(char * packet, int packet_length) *message_ptr++ = ' '; // Find the group member. If this is a new group member, add it. + struct device_group_member * device_group_member; 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")); + AddLog_P2(LOG_LEVEL_ERROR, PSTR("DGR: Error allocating member block")); return; } AddLog_P2(LOG_LEVEL_DEBUG, PSTR("DGR: Adding member %s"), IPAddressToString(remote_ip)); @@ -565,205 +778,7 @@ void ProcessDeviceGroupMessage(char * packet, int packet_length) 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 announcement, simply return. - if (flags == DGR_FLAG_ANNOUNCEMENT) return; - - // 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; - } -#ifdef DEVICE_GROUPS_DEBUG - AddLog_P2(LOG_LEVEL_DEBUG, PSTR("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("received_sequence) { - 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("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 0:15 Flags, 16:31 Message sequence - uint32_t data_len String item value length - int32_t payload Integer item value - char *topic Pointer to device group index - 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 | message_sequence << 16; - XdrvMailbox.topic = (char *)&device_group_index; - if (flags & (DGR_FLAG_MORE_TO_COME | DGR_FLAG_DIRECT)) skip_light_fade = true; - - 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_PRESET_LOW: - case DGR_ITEM_BRI_PRESET_HIGH: - case DGR_ITEM_BRI_POWER_ON: - case DGR_ITEM_POWER: - case DGR_ITEM_EVENT: - 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; -#ifdef USE_DEVICE_GROUPS_SEND - device_group->values_32bit[item - DGR_ITEM_MAX_16BIT - 1] = (item == DGR_ITEM_POWER ? value & 0xffffff : value); -#endif // USE_DEVICE_GROUPS_SEND - } -#ifdef USE_DEVICE_GROUPS_SEND - else { - device_group->values_16bit[item - DGR_ITEM_MAX_8BIT - 1] = value; - } -#endif // USE_DEVICE_GROUPS_SEND - } -#ifdef USE_DEVICE_GROUPS_SEND - else { - device_group->values_8bit[item] = value; - } -#endif // USE_DEVICE_GROUPS_SEND - -//#ifdef DEVICE_GROUPS_DEBUG - AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("<%u=%u"), item, value); -//#endif // DEVICE_GROUPS_DEBUG - XdrvMailbox.payload = value; - } - else if (item <= DGR_ITEM_MAX_STRING) { - value = strlen(message_ptr); - if (value >= packet_length - (message_ptr - packet)) goto badmsg; // Malformed message -//#ifdef DEVICE_GROUPS_DEBUG - AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("<%u='%s'"), item, message_ptr); -//#endif // DEVICE_GROUPS_DEBUG - XdrvMailbox.data_len = value; - XdrvMailbox.data = message_ptr; - message_ptr += value + 1; - } - else { - switch (item) { - case DGR_ITEM_LIGHT_CHANNELS: -//#ifdef DEVICE_GROUPS_DEBUG - AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("<%u=%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) { - if (device_group->local) { - uint8_t mask_devices = value >> 24; - if (mask_devices > devices_present) mask_devices = devices_present; - 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 if (item == DGR_ITEM_EVENT) { -#ifdef USE_RULES - CmndEvent(); -#endif - } - XdrvCall(FUNC_DEVICE_GROUP_ITEM); - } - } - - XdrvMailbox.command_code = DGR_ITEM_EOL; - XdrvCall(FUNC_DEVICE_GROUP_ITEM); - skip_light_fade = false; - - processing_remote_device_message = false; -#ifdef DEVICE_GROUPS_DEBUG - AddLog_P2(LOG_LEVEL_DEBUG, PSTR("device_group_members; device_group_member; device_group_member = device_group_member->flink) { - snprintf(buffer, sizeof(buffer), PSTR("%s,{\"IPAddress\":\"%s\",\"ResendCount\":%u,\"LastRcvdSeq\":%u,\"LastAckedSeq\":%u}"), buffer, IPAddressToString(device_group_member->ip_address), device_group_member->unicast_count, device_group_member->received_sequence, device_group_member->acked_sequence); + snprintf_P(buffer, sizeof(buffer), PSTR("%s,{\"IPAddress\":\"%s\",\"ResendCount\":%u,\"LastRcvdSeq\":%u,\"LastAckedSeq\":%u}"), buffer, IPAddressToString(device_group_member->ip_address), device_group_member->unicast_count, device_group_member->received_sequence, device_group_member->acked_sequence); member_count++; } - Response_P(PSTR("{\"" D_CMND_DEVGROUPSTATUS "\":{\"Index\":%u,\"GroupName\":\"%s\",\"MessageSeq\":%u,\"MemberCount\":%d,\"Members\":[%s]}"), device_group_index, device_group->group_name, outgoing_sequence, member_count, &buffer[1]); + Response_P(PSTR("{\"" D_CMND_DEVGROUPSTATUS "\":{\"Index\":%u,\"GroupName\":\"%s\",\"MessageSeq\":%u,\"MemberCount\":%d,\"Members\":[%s]}"), device_group_index, device_group->group_name, device_group->outgoing_sequence, member_count, &buffer[1]); } } @@ -799,11 +814,12 @@ void DeviceGroupsLoop(void) next_check_time = now + 3000; 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]; + device_group->next_announcement_time = -1; device_group->message_length = BeginDeviceGroupMessage(device_group, DGR_FLAG_RESET | DGR_FLAG_STATUS_REQUEST) - device_group->message; device_group->initial_status_requests_remaining = 10; device_group->next_ack_check_time = next_check_time; } - AddLog_P2(LOG_LEVEL_DEBUG, PSTR("DGR: (Re)discovering device groups")); + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("DGR: (Re)discovering members")); } if (device_groups_initialization_failed) return; @@ -830,7 +846,8 @@ AddLog_P2(LOG_LEVEL_DEBUG, PSTR("DGR: Ckecking next_check_time=%u, now=%u"), nex #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 - SendDeviceGroupPacket(IPAddress(0,0,0,0), device_group->message, device_group->message_length, PSTR("Initial")); + SendReceiveDeviceGroupPacket(device_group, nullptr, device_group->message, device_group->message_length, false); + 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; } @@ -854,7 +871,7 @@ AddLog_P2(LOG_LEVEL_DEBUG, PSTR("DGR: Ckecking next_check_time=%u, now=%u"), nex while ((device_group_member = *flink)) { // If we have not received an ack to our last message from this member, ... - if (device_group_member->acked_sequence != outgoing_sequence) { + if (device_group_member->acked_sequence != device_group->outgoing_sequence) { // If we haven't receive an ack from this member in DGR_MEMBER_TIMEOUT ms, assume // they're offline and remove them from the group. @@ -866,10 +883,7 @@ AddLog_P2(LOG_LEVEL_DEBUG, PSTR("DGR: Ckecking next_check_time=%u, now=%u"), nex // Otherwise, unicast the last message directly to this 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")); + SendReceiveDeviceGroupPacket(device_group, device_group_member, device_group->message, device_group->message_length, false); device_group_member->unicast_count++; acked = false; flink = &device_group_member->flink; @@ -910,10 +924,7 @@ AddLog_P2(LOG_LEVEL_DEBUG, PSTR("DGR: Ckecking next_check_time=%u, now=%u"), nex AddLog_P2(LOG_LEVEL_DEBUG, PSTR("DGR: next_announcement_time=%u, now=%u"), device_group->next_announcement_time, now); #endif // DEVICE_GROUPS_DEBUG if (device_group->next_announcement_time <= now) { -#ifdef DEVICE_GROUPS_DEBUG - AddLog_P2(LOG_LEVEL_DEBUG, PSTR("DGR: Sending device group %s announcement"), device_group->group_name); -#endif // DEVICE_GROUPS_DEBUG - SendDeviceGroupPacket(IPAddress(0,0,0,0), device_group->message, BeginDeviceGroupMessage(device_group, DGR_FLAG_ANNOUNCEMENT, true) - device_group->message, PSTR("Announcement")); + SendReceiveDeviceGroupPacket(device_group, nullptr, device_group->message, BeginDeviceGroupMessage(device_group, DGR_FLAG_ANNOUNCEMENT, true) - device_group->message, false); device_group->next_announcement_time = now + DGR_ANNOUNCEMENT_INTERVAL + random(10000); } if (device_group->next_announcement_time < next_check_time) next_check_time = device_group->next_announcement_time; diff --git a/tasmota/tasmota.h b/tasmota/tasmota.h index b769fbf6d..927b350ca 100644 --- a/tasmota/tasmota.h +++ b/tasmota/tasmota.h @@ -315,7 +315,7 @@ enum DevGroupItem { DGR_ITEM_EOL, DGR_ITEM_STATUS, DGR_ITEM_POWER, DGR_ITEM_DIMMER_RANGE, // Add new 32-bit items before this line DGR_ITEM_LAST_32BIT, DGR_ITEM_MAX_32BIT = 191, - DGR_ITEM_EVENT, + DGR_ITEM_EVENT, DGR_ITEM_COMMAND, // Add new string items before this line DGR_ITEM_LAST_STRING, DGR_ITEM_MAX_STRING = 223, DGR_ITEM_LIGHT_CHANNELS }; diff --git a/tasmota/xdrv_04_light.ino b/tasmota/xdrv_04_light.ino index 70d5b06d5..eed833b9f 100644 --- a/tasmota/xdrv_04_light.ino +++ b/tasmota/xdrv_04_light.ino @@ -282,6 +282,7 @@ struct LIGHT { bool fade_initialized = false; // dont't fade at startup bool fade_running = false; #ifdef USE_DEVICE_GROUPS + uint8_t last_scheme = 0; bool devgrp_no_channels_out = false; // don't share channels with device group (e.g. if scheme set by other device) #endif // USE_DEVICE_GROUPS #ifdef USE_LIGHT_PALETTE @@ -1477,9 +1478,6 @@ void LightSetSignal(uint16_t lo, uint16_t hi, uint16_t value) // AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "Light signal %d"), signal); light_controller.changeRGB(signal, 255 - signal, 0, true); // keep bri Settings.light_scheme = 0; -#ifdef USE_DEVICE_GROUPS - LightUpdateScheme(); -#endif // USE_DEVICE_GROUPS if (0 == light_state.getBri()) { light_controller.changeBri(50); } @@ -1833,9 +1831,6 @@ void LightAnimate(void) Light.wakeup_active = 0; Settings.light_scheme = LS_POWER; -#ifdef USE_DEVICE_GROUPS - LightUpdateScheme(); -#endif // USE_DEVICE_GROUPS } } break; @@ -1858,6 +1853,14 @@ void LightAnimate(void) default: XlgtCall(FUNC_SET_SCHEME); } + +#ifdef USE_DEVICE_GROUPS + if (Settings.light_scheme != Light.last_scheme) { + Light.last_scheme = Settings.light_scheme; + SendLocalDeviceGroupMessage(DGR_MSGTYP_UPDATE, DGR_ITEM_LIGHT_SCHEME, Settings.light_scheme); + Light.devgrp_no_channels_out = false; + } +#endif // USE_DEVICE_GROUPS } if ((Settings.light_scheme < LS_MAX) || power_off) { // exclude WS281X Neopixel schemes @@ -2270,7 +2273,7 @@ void LightHandleDevGroupItem(void) break; case DGR_ITEM_LIGHT_SCHEME: if (Settings.light_scheme != value) { - Settings.light_scheme = value; + Light.last_scheme = Settings.light_scheme = value; Light.devgrp_no_channels_out = (value != 0); send_state = true; } @@ -2330,17 +2333,6 @@ void LightHandleDevGroupItem(void) break; } } - -void LightUpdateScheme(void) -{ - static uint8_t last_scheme; - - if (Settings.light_scheme != last_scheme) { - last_scheme = Settings.light_scheme; - SendLocalDeviceGroupMessage(DGR_MSGTYP_UPDATE, DGR_ITEM_LIGHT_SCHEME, Settings.light_scheme); - } - Light.devgrp_no_channels_out = false; -} #endif // USE_DEVICE_GROUPS /*********************************************************************************************\ @@ -2473,9 +2465,6 @@ void CmndSupportColor(void) } #endif // USE_LIGHT_PALETTE Settings.light_scheme = 0; -#ifdef USE_DEVICE_GROUPS - LightUpdateScheme(); -#endif // USE_DEVICE_GROUPS coldim = true; } else { // Color3, 4, 5 and 6 for (uint32_t i = 0; i < LST_RGB; i++) { @@ -2645,9 +2634,6 @@ void CmndScheme(void) #endif // USE_LIGHT_PALETTE } Settings.light_scheme = XdrvMailbox.payload; -#ifdef USE_DEVICE_GROUPS - LightUpdateScheme(); -#endif // USE_DEVICE_GROUPS if (LS_WAKEUP == Settings.light_scheme) { Light.wakeup_active = 3; } @@ -2671,9 +2657,6 @@ void CmndWakeup(void) } Light.wakeup_active = 3; Settings.light_scheme = LS_WAKEUP; -#ifdef USE_DEVICE_GROUPS - LightUpdateScheme(); -#endif // USE_DEVICE_GROUPS LightPowerOn(); ResponseCmndChar(D_JSON_STARTED); } @@ -2753,8 +2736,11 @@ void CmndDimmer(void) } } #if defined(USE_PWM_DIMMER) && defined(USE_DEVICE_GROUPS) - Settings.bri_power_on = light_state.getBri();; - SendLocalDeviceGroupMessage(DGR_MSGTYP_UPDATE, DGR_ITEM_BRI_POWER_ON, Settings.bri_power_on); + uint8_t bri = light_state.getBri(); + if (bri != Settings.bri_power_on) { + Settings.bri_power_on = bri; + SendLocalDeviceGroupMessage(DGR_MSGTYP_PARTIAL_UPDATE, DGR_ITEM_BRI_POWER_ON, Settings.bri_power_on); + } #endif // USE_PWM_DIMMER && USE_DEVICE_GROUPS Light.update = true; if (skip_light_fade) LightAnimate(); diff --git a/tasmota/xdrv_35_pwm_dimmer.ino b/tasmota/xdrv_35_pwm_dimmer.ino index 67890ac92..13af63544 100644 --- a/tasmota/xdrv_35_pwm_dimmer.ino +++ b/tasmota/xdrv_35_pwm_dimmer.ino @@ -344,8 +344,8 @@ void PWMDimmerHandleButton(void) // If this is about the power button, ... if (is_power_button) { - // If no other buttons are pressed and the up or down button was tapped while holding the - // power button before this, ... + // If no other buttons are pressed and the up or down button was not tapped while holding + // the power button before this, ... if (buttons_pressed == 1 && !tap_count) { // If the power is on, adjust the brightness. Set the direction based on the current @@ -602,7 +602,7 @@ void PWMDimmerHandleButton(void) } if (new_bri != bri) { #ifdef USE_DEVICE_GROUPS - SendDeviceGroupMessage(power_button_index, (dgr_item ? DGR_MSGTYP_UPDATE : DGR_MSGTYP_UPDATE_MORE_TO_COME), DGR_ITEM_LIGHT_BRI, new_bri); + SendDeviceGroupMessage(power_button_index, (dgr_item ? DGR_MSGTYP_PARTIAL_UPDATE : DGR_MSGTYP_UPDATE_MORE_TO_COME), DGR_ITEM_LIGHT_BRI, new_bri); #endif // USE_DEVICE_GROUPS #ifdef USE_PWM_DIMMER_REMOTE if (!active_device_is_local) @@ -610,9 +610,15 @@ void PWMDimmerHandleButton(void) else { #endif // USE_PWM_DIMMER_REMOTE skip_light_fade = true; +#ifdef USE_DEVICE_GROUPS + ignore_dgr_sends = true; +#endif // USE_DEVICE_GROUPS light_state.setBri(new_bri); LightAnimate(); skip_light_fade = false; +#ifdef USE_DEVICE_GROUPS + ignore_dgr_sends = false; +#endif // USE_DEVICE_GROUPS Settings.bri_power_on = new_bri; #ifdef USE_PWM_DIMMER_REMOTE }