2020-02-21 15:09:21 +00:00
/*
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/>.
*/
2020-04-10 17:24:08 +01:00
2020-02-21 15:09:21 +00:00
# ifdef USE_DEVICE_GROUPS
2020-02-25 01:10:57 +00:00
//#define DEVICE_GROUPS_DEBUG
2020-03-12 17:51:54 +00:00
# define DGR_MEMBER_TIMEOUT 45000
# define DGR_ANNOUNCEMENT_INTERVAL 60000
2020-02-21 15:09:21 +00:00
extern bool udp_connected ;
struct device_group_member {
struct device_group_member * flink ;
IPAddress ip_address ;
uint16_t received_sequence ;
uint16_t acked_sequence ;
2020-04-15 14:40:18 +01:00
uint32_t unicast_count ;
2020-02-21 15:09:21 +00:00
} ;
struct device_group {
2020-03-12 17:51:54 +00:00
uint32_t next_announcement_time ;
2020-02-21 15:09:21 +00:00
uint32_t next_ack_check_time ;
2020-03-12 17:51:54 +00:00
uint32_t member_timeout_time ;
2020-02-21 15:09:21 +00:00
uint16_t last_full_status_sequence ;
uint16_t message_length ;
2020-03-12 17:51:54 +00:00
uint16_t ack_check_interval ;
2020-02-21 15:09:21 +00:00
uint8_t message_header_length ;
uint8_t initial_status_requests_remaining ;
bool local ;
char group_name [ TOPSZ ] ;
char message [ 128 ] ;
struct device_group_member * device_group_members ;
2020-04-07 19:25:58 +01:00
# ifdef USE_DEVICE_GROUPS_SEND
uint8_t values_8bit [ DGR_ITEM_LAST_8BIT ] ;
uint16_t values_16bit [ DGR_ITEM_LAST_16BIT - DGR_ITEM_MAX_8BIT - 1 ] ;
uint32_t values_32bit [ DGR_ITEM_LAST_32BIT - DGR_ITEM_MAX_16BIT - 1 ] ;
# endif // USE_DEVICE_GROUPS_SEND
2020-02-21 15:09:21 +00:00
} ;
struct device_group * device_groups ;
2020-03-12 17:51:54 +00:00
uint32_t next_check_time = 0 ;
2020-02-21 15:09:21 +00:00
uint16_t outgoing_sequence = 0 ;
2020-02-25 00:40:44 +00:00
bool device_groups_initialized = false ;
bool device_groups_initialization_failed = false ;
2020-02-21 15:09:21 +00:00
bool building_status_message = false ;
bool processing_remote_device_message = false ;
bool udp_was_connected = false ;
void DeviceGroupsInit ( void )
{
2020-04-07 19:25:58 +01:00
// If there are more device group names set than the number of device groups needed by the
// mdoule, use the device group name count as the device group count.
for ( ; device_group_count < MAX_DEV_GROUP_NAMES ; device_group_count + + ) {
if ( ! * SettingsText ( SET_DEV_GROUP_NAME1 + device_group_count ) ) break ;
}
2020-04-06 18:29:50 +01:00
// Initialize the device information for each device group.
2020-02-24 22:34:45 +00:00
device_groups = ( struct device_group * ) calloc ( device_group_count , sizeof ( struct device_group ) ) ;
if ( device_groups = = nullptr ) {
2020-04-15 14:40:18 +01:00
AddLog_P2 ( LOG_LEVEL_ERROR , PSTR ( " DGR: Error allocating %u-element device group array " ) , device_group_count ) ;
2020-02-25 00:40:44 +00:00
device_groups_initialization_failed = true ;
2020-02-24 22:34:45 +00:00
return ;
}
2020-02-21 15:09:21 +00:00
2020-02-24 22:34:45 +00:00
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 ] ;
2020-04-06 18:29:50 +01:00
strcpy ( device_group - > group_name , SettingsText ( SET_DEV_GROUP_NAME1 + device_group_index ) ) ;
// If the device group name is not set, use the MQTT group topic (with the device group index +
// 1 appended for device group indices > 0).
if ( ! device_group - > group_name [ 0 ] ) {
strcpy ( device_group - > group_name , SettingsText ( SET_MQTT_GRP_TOPIC ) ) ;
if ( device_group_index ) {
char str [ 10 ] ;
sprintf_P ( str , PSTR ( " %u " ) , device_group_index + 1 ) ;
strcat ( device_group - > group_name , str ) ;
}
}
2020-02-24 22:34:45 +00:00
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 ;
}
2020-02-21 15:09:21 +00:00
2020-02-24 22:34:45 +00:00
device_groups [ 0 ] . local = true ;
2020-02-21 15:09:21 +00:00
2020-02-24 22:34:45 +00:00
// 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 ;
2020-02-21 15:09:21 +00:00
}
2020-02-24 22:34:45 +00:00
2020-02-25 00:40:44 +00:00
device_groups_initialized = true ;
2020-02-21 15:09:21 +00:00
}
char * IPAddressToString ( const IPAddress & ip_address )
{
static char buffer [ 16 ] ;
2020-02-24 22:34:45 +00:00
sprintf_P ( buffer , PSTR ( " %u.%u.%u.%u " ) , ip_address [ 0 ] , ip_address [ 1 ] , ip_address [ 2 ] , ip_address [ 3 ] ) ;
2020-02-21 15:09:21 +00:00
return buffer ;
}
2020-03-12 17:51:54 +00:00
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 ;
* message_ptr + + = flags & 0xff ;
* message_ptr + + = flags > > 8 ;
return message_ptr ;
}
2020-02-21 15:09:21 +00:00
// Return true if we're configured to share the specified item.
2020-04-07 19:25:58 +01:00
bool DeviceGroupItemShared ( bool incoming , uint8_t item )
2020-02-21 15:09:21 +00:00
{
uint8_t mask = 0 ;
switch ( item ) {
case DGR_ITEM_LIGHT_BRI :
2020-03-13 17:08:44 +00:00
case DGR_ITEM_BRI_POWER_ON :
2020-02-21 15:09:21 +00:00
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 ;
2020-03-13 17:08:44 +00:00
case DGR_ITEM_BRI_PRESET_LOW :
case DGR_ITEM_BRI_PRESET_HIGH :
mask = DGR_SHARE_DIMMER_SETTINGS ;
2020-02-21 15:09:21 +00:00
break ;
2020-04-19 04:37:05 +01:00
case DGR_ITEM_EVENT :
mask = DGR_SHARE_EVENT ;
break ;
2020-02-21 15:09:21 +00:00
}
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 )
{
2020-03-12 17:51:54 +00:00
if ( ! ip ) ip = IPAddress ( 239 , 255 , 255 , 250 ) ;
2020-02-21 15:09:21 +00:00
for ( int attempt = 1 ; attempt < = 5 ; attempt + + ) {
if ( PortUdp . beginPacket ( ip , 1900 ) ) {
2020-04-10 17:24:08 +01:00
PortUdp_write ( packet , len ) ;
2020-02-21 15:09:21 +00:00
if ( PortUdp . endPacket ( ) ) return ;
}
delay ( 10 ) ;
}
2020-04-15 14:40:18 +01:00
AddLog_P2 ( LOG_LEVEL_ERROR , PSTR ( " DGR: Error sending %s packet " ) , label ) ;
2020-02-21 15:09:21 +00:00
}
2020-04-06 18:29:50 +01:00
void _SendDeviceGroupMessage ( uint8_t device_group_index , DevGroupMessageType message_type , . . . )
2020-02-21 15:09:21 +00:00
{
2020-02-25 00:40:44 +00:00
// If device groups are not enabled, ignore this request.
if ( ! Settings . flag4 . device_groups_enabled ) return ;
2020-02-21 15:09:21 +00:00
// 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.
2020-04-07 19:25:58 +01:00
if ( processing_remote_device_message & & message_type ! = DGR_MSGTYPE_UPDATE_COMMAND ) return ;
2020-02-21 15:09:21 +00:00
// Get a pointer to the device information for this device.
2020-04-15 14:40:18 +01:00
struct device_group * device_group = & device_groups [ device_group_index ] ;
2020-02-21 15:09:21 +00:00
// 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
2020-02-24 22:34:45 +00:00
// control. As long as we're building it, we may as well multicast the status update to all
2020-02-21 15:09:21 +00:00
// 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
2020-02-24 22:34:45 +00:00
AddLog_P2 ( LOG_LEVEL_DEBUG , PSTR ( " Building device group %s full status packet " ) , device_group - > group_name ) ;
2020-02-21 15:09:21 +00:00
# endif // DEVICE_GROUPS_DEBUG
device_group - > message_length = 0 ;
2020-02-25 00:40:44 +00:00
SendDeviceGroupMessage ( device_group_index , DGR_MSGTYP_PARTIAL_UPDATE , DGR_ITEM_POWER , power ) ;
2020-04-07 19:25:58 +01:00
XdrvMailbox . index = device_group_index < < 16 ;
2020-02-21 15:09:21 +00:00
XdrvMailbox . command_code = DGR_ITEM_STATUS ;
2020-04-06 18:29:50 +01:00
XdrvMailbox . topic = ( char * ) & device_group_index ;
2020-03-12 17:51:54 +00:00
XdrvCall ( FUNC_DEVICE_GROUP_ITEM ) ;
2020-02-21 15:09:21 +00:00
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 {
2020-04-07 19:25:58 +01:00
# ifdef USE_DEVICE_GROUPS_SEND
2020-04-19 04:37:05 +01:00
bool escaped ;
2020-04-07 19:25:58 +01:00
bool use_command ;
2020-04-19 04:37:05 +01:00
char chr ;
2020-04-07 19:25:58 +01:00
char oper ;
uint32_t old_value ;
2020-04-19 04:37:05 +01:00
char * out_ptr ;
2020-04-07 19:25:58 +01:00
# endif // USE_DEVICE_GROUPS_SEND
2020-04-19 04:37:05 +01:00
struct item {
uint8_t item ;
uint32_t value ;
void * value_ptr ;
} item_array [ 32 ] ;
2020-02-21 15:09:21 +00:00
bool shared ;
uint8_t item ;
uint32_t value ;
2020-04-07 19:25:58 +01:00
char * value_ptr ;
2020-04-19 04:37:05 +01:00
struct item * item_ptr ;
2020-02-21 15:09:21 +00:00
va_list ap ;
# ifdef DEVICE_GROUPS_DEBUG
2020-02-24 22:34:45 +00:00
AddLog_P2 ( LOG_LEVEL_DEBUG , PSTR ( " Building device group %s packet " ) , device_group - > group_name ) ;
2020-02-21 15:09:21 +00:00
# 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
2020-02-24 22:34:45 +00:00
AddLog_P2 ( LOG_LEVEL_DEBUG , PSTR ( " >sequence=%u, flags=%u " ) , outgoing_sequence , value ) ;
2020-02-21 15:09:21 +00:00
# endif // DEVICE_GROUPS_DEBUG
2020-03-12 17:51:54 +00:00
message_ptr = BeginDeviceGroupMessage ( device_group , value , building_status_message | | message_type = = DGR_MSGTYP_PARTIAL_UPDATE ) ;
2020-02-21 15:09:21 +00:00
2020-04-19 04:37:05 +01:00
// Build an array of all the items and values in this update.
memset ( item_array , 0 , sizeof ( item_array ) ) ;
item_ptr = item_array ;
2020-04-07 19:25:58 +01:00
# ifdef USE_DEVICE_GROUPS_SEND
2020-04-19 04:37:05 +01:00
use_command = ( message_type = = DGR_MSGTYPE_UPDATE_COMMAND ) ;
if ( use_command ) {
value_ptr = XdrvMailbox . data ;
while ( ( item = strtoul ( value_ptr , & value_ptr , 0 ) ) ) {
item_ptr - > item = item ;
if ( * value_ptr = = ' = ' ) {
value_ptr + + ;
if ( item < = DGR_ITEM_MAX_32BIT ) {
oper = 0 ;
if ( * value_ptr = = ' @ ' ) {
oper = value_ptr [ 1 ] ;
value_ptr + = 2 ;
}
value = strtoul ( value_ptr , & value_ptr , 0 ) ;
if ( oper ) {
old_value = ( item < = DGR_ITEM_MAX_8BIT ? device_group - > values_8bit [ item ] : ( item < = DGR_ITEM_MAX_16BIT ? device_group - > values_16bit [ item - DGR_ITEM_MAX_8BIT - 1 ] : device_group - > values_32bit [ item - DGR_ITEM_MAX_16BIT - 1 ] ) ) ;
value = ( oper = = ' + ' ? old_value + value : ( oper = = ' - ' ? old_value - value : ( oper = = ' ^ ' ? old_value ^ ( value ? value : 0xffffffff ) : old_value ) ) ) ;
}
item_ptr - > value = value ;
}
else if ( item < = DGR_ITEM_MAX_STRING ) {
item_ptr - > value_ptr = out_ptr = value_ptr ;
escaped = false ;
while ( ( chr = * value_ptr + + ) ) {
if ( chr = = ' ' & & ! escaped ) break ;
if ( ! ( escaped = ( chr = = ' \\ ' & & ! escaped ) ) ) * out_ptr + + = chr ;
}
* out_ptr = 0 ;
2020-04-07 19:25:58 +01:00
}
else {
switch ( item ) {
case DGR_ITEM_LIGHT_CHANNELS :
2020-04-19 04:37:05 +01:00
item_ptr - > value_ptr = out_ptr = value_ptr ;
for ( int i = 0 ; i < 5 ; i + + ) {
* out_ptr = strtoul ( value_ptr , & value_ptr , 0 ) ;
if ( * value_ptr = = ' , ' ) value_ptr + + ;
}
2020-04-07 19:25:58 +01:00
break ;
}
2020-02-21 15:09:21 +00:00
}
}
2020-04-19 04:37:05 +01:00
item_ptr + + ;
}
}
else {
2020-04-07 19:25:58 +01:00
# endif // USE_DEVICE_GROUPS_SEND
2020-04-19 04:37:05 +01:00
va_start ( ap , message_type ) ;
while ( ( item = va_arg ( ap , int ) ) ) {
item_ptr - > item = item ;
if ( item < = DGR_ITEM_MAX_32BIT )
item_ptr - > value = va_arg ( ap , int ) ;
else if ( item < = DGR_ITEM_MAX_STRING )
item_ptr - > value_ptr = va_arg ( ap , char * ) ;
else {
switch ( item ) {
case DGR_ITEM_LIGHT_CHANNELS :
item_ptr - > value_ptr = va_arg ( ap , uint8_t * ) ;
break ;
}
}
item_ptr + + ;
2020-02-21 15:09:21 +00:00
}
va_end ( ap ) ;
2020-04-19 04:37:05 +01:00
# ifdef USE_DEVICE_GROUPS_SEND
}
# endif // USE_DEVICE_GROUPS_SEND
// 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 ) {
int kept_item_count = 0 ;
2020-02-21 15:09:21 +00:00
2020-04-07 19:25:58 +01:00
// Rebuild the previous update message, removing any items whose values are included in this
// new update.
2020-02-21 15:09:21 +00:00
char * previous_message_ptr = message_ptr ;
while ( item = * previous_message_ptr + + ) {
// Search for this item in the new update.
2020-04-19 04:37:05 +01:00
for ( item_ptr = item_array ; item_ptr - > item ; item_ptr + + ) {
if ( item_ptr - > item = = item ) break ;
2020-02-21 15:09:21 +00:00
}
// If this item was found in the new update skip over it and it's value.
2020-04-19 04:37:05 +01:00
if ( item_ptr - > item ) {
2020-02-21 15:09:21 +00:00
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 + + ;
}
}
}
2020-04-19 04:37:05 +01:00
else if ( item < = DGR_ITEM_MAX_STRING ) {
previous_message_ptr + = strlen ( previous_message_ptr ) + 1 ;
}
2020-02-21 15:09:21 +00:00
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 ) {
2020-04-19 04:37:05 +01:00
value = strlen ( previous_message_ptr ) + 1 ;
2020-02-21 15:09:21 +00:00
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
2020-02-25 00:40:44 +00:00
AddLog_P2 ( LOG_LEVEL_DEBUG , PSTR ( " %u items carried over from previous update " ) , kept_item_count ) ;
2020-02-21 15:09:21 +00:00
# endif // DEVICE_GROUPS_DEBUG
}
// Itertate through the passed items adding them and their values to the message.
2020-04-19 04:37:05 +01:00
for ( item_ptr = item_array ; ( item = item_ptr - > item ) ; item_ptr + + ) {
2020-04-07 19:25:58 +01:00
2020-04-19 04:37:05 +01:00
// If this item is shared with the group add it to the message.
2020-04-07 19:25:58 +01:00
shared = true ;
if ( ! device_group_index ) shared = DeviceGroupItemShared ( false , item ) ;
2020-04-19 04:37:05 +01:00
if ( shared ) {
* 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
2020-02-21 15:09:21 +00:00
* message_ptr + + = value & 0xff ;
if ( item > DGR_ITEM_MAX_8BIT ) {
2020-04-07 19:25:58 +01:00
# ifdef USE_DEVICE_GROUPS_SEND
old_value = value ;
# endif // USE_DEVICE_GROUPS_SEND
2020-02-21 15:09:21 +00:00
value > > = 8 ;
* message_ptr + + = value & 0xff ;
2020-04-07 19:25:58 +01:00
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
2020-02-21 15:09:21 +00:00
}
2020-04-07 19:25:58 +01:00
# ifdef USE_DEVICE_GROUPS_SEND
else {
device_group - > values_8bit [ item ] = value ;
2020-02-21 15:09:21 +00:00
}
2020-04-07 19:25:58 +01:00
# endif // USE_DEVICE_GROUPS_SEND
2020-02-21 15:09:21 +00:00
}
2020-04-07 19:25:58 +01:00
2020-04-19 04:37:05 +01:00
// For string items, add the null-terminated string to the message.
else if ( item < = DGR_ITEM_MAX_STRING ) {
if ( item_ptr - > value_ptr ) {
value = strlen ( ( const char * ) item_ptr - > value_ptr ) ;
memcpy ( message_ptr , item_ptr - > value_ptr , value ) ;
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
2020-02-21 15:09:21 +00:00
}
2020-04-07 19:25:58 +01:00
2020-04-19 04:37:05 +01:00
// For special items, handle them individually.
else {
switch ( item ) {
case DGR_ITEM_LIGHT_CHANNELS :
value_ptr = ( char * ) item_ptr - > value_ptr ;
for ( int i = 0 ; i < 5 ; i + + ) {
* message_ptr + + = ( value_ptr ? * value_ptr + + : 0 ) ;
2020-04-07 19:25:58 +01:00
}
2020-04-19 04:37:05 +01:00
//#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 ;
}
2020-04-07 19:25:58 +01:00
}
}
2020-02-21 15:09:21 +00:00
}
// 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 ;
}
2020-02-24 22:34:45 +00:00
// Multicast the packet.
2020-02-21 15:09:21 +00:00
# ifdef DEVICE_GROUPS_DEBUG
2020-04-15 14:40:18 +01:00
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 ) ;
2020-02-21 15:09:21 +00:00
# endif // DEVICE_GROUPS_DEBUG
2020-04-10 17:24:08 +01:00
SendDeviceGroupPacket ( IPAddress ( 0 , 0 , 0 , 0 ) , device_group - > message , device_group - > message_length , PSTR ( " Multicast " ) ) ;
2020-02-21 15:09:21 +00:00
2020-03-12 17:51:54 +00:00
uint32_t now = millis ( ) ;
2020-02-21 15:09:21 +00:00
if ( message_type = = DGR_MSGTYP_UPDATE_MORE_TO_COME ) {
2020-04-06 18:29:50 +01:00
device_group - > message_length = 0 ;
2020-03-12 17:51:54 +00:00
device_group - > next_ack_check_time = 0 ;
2020-02-21 15:09:21 +00:00
}
else {
2020-03-12 17:51:54 +00:00
device_group - > ack_check_interval = 100 ;
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 ;
2020-02-21 15:09:21 +00:00
}
2020-03-12 17:51:54 +00:00
device_group - > next_announcement_time = now + DGR_ANNOUNCEMENT_INTERVAL ;
if ( device_group - > next_announcement_time < next_check_time ) next_check_time = device_group - > next_announcement_time ;
2020-02-21 15:09:21 +00:00
}
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 ;
2020-04-06 18:29:50 +01:00
uint8_t device_group_index = 0 ;
2020-02-21 15:09:21 +00:00
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 ) {
2020-04-15 14:40:18 +01:00
AddLog_P2 ( LOG_LEVEL_ERROR , PSTR ( " DGR: Error allocating device group member block " ) ) ;
2020-02-21 15:09:21 +00:00
return ;
}
2020-04-15 14:40:18 +01:00
AddLog_P2 ( LOG_LEVEL_DEBUG , PSTR ( " DGR: Adding member %s " ) , IPAddressToString ( remote_ip ) ) ;
2020-02-21 15:09:21 +00:00
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).
2020-02-24 22:34:45 +00:00
message_ptr = strstr_P ( message_ptr , PSTR ( " \n \n " ) ) ;
2020-02-21 15:09:21 +00:00
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
2020-02-24 22:34:45 +00:00
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 ) ;
2020-02-21 15:09:21 +00:00
# endif // DEVICE_GROUPS_DEBUG
2020-03-12 17:51:54 +00:00
// If this is an announcement, simply return.
if ( flags = = DGR_FLAG_ANNOUNCEMENT ) return ;
2020-02-21 15:09:21 +00:00
// 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
2020-02-24 22:34:45 +00:00
AddLog_P2 ( LOG_LEVEL_DEBUG , PSTR ( " <ack " ) ) ;
2020-02-21 15:09:21 +00:00
# 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 ;
2020-03-13 11:17:19 +00:00
* ( message_ptr - 1 ) = 0 ;
2020-02-24 22:34:45 +00:00
SendDeviceGroupPacket ( remote_ip , packet , message_ptr - packet , PSTR ( " Ack " ) ) ;
2020-02-21 15:09:21 +00:00
}
// 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
2020-02-24 22:34:45 +00:00
AddLog_P2 ( LOG_LEVEL_DEBUG , PSTR ( " <status request " ) ) ;
2020-02-21 15:09:21 +00:00
# endif // DEVICE_GROUPS_DEBUG
return ;
}
// If we already processed this or a later message from this group member, ignore this message.
2020-03-12 17:51:54 +00:00
if ( message_sequence < = device_group_member - > received_sequence ) {
if ( message_sequence = = device_group_member - > received_sequence | | device_group_member - > received_sequence - message_sequence > 64536 ) {
2020-02-21 15:09:21 +00:00
# ifdef DEVICE_GROUPS_DEBUG
2020-03-12 17:51:54 +00:00
AddLog_P2 ( LOG_LEVEL_DEBUG , PSTR ( " <old message " ) ) ;
2020-02-21 15:09:21 +00:00
# endif // DEVICE_GROUPS_DEBUG
2020-03-12 17:51:54 +00:00
return ;
}
2020-02-21 15:09:21 +00:00
}
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
2020-04-06 18:29:50 +01:00
uint32_t index 0 : 15 Flags , 16 : 31 Message sequence
2020-02-21 15:09:21 +00:00
uint32_t data_len String item value length
int32_t payload Integer item value
2020-04-06 18:29:50 +01:00
char * topic Pointer to device group index
2020-02-21 15:09:21 +00:00
char * data Pointer to non - integer item value
char * command nullptr
*/
XdrvMailbox . command = nullptr ; // Indicates the source is a device group update
2020-04-06 18:29:50 +01:00
XdrvMailbox . index = flags | message_sequence < < 16 ;
XdrvMailbox . topic = ( char * ) & device_group_index ;
2020-03-05 23:51:22 +00:00
if ( flags & ( DGR_FLAG_MORE_TO_COME | DGR_FLAG_DIRECT ) ) skip_light_fade = true ;
2020-02-21 15:09:21 +00:00
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 :
2020-04-19 04:37:05 +01:00
case DGR_ITEM_EVENT :
2020-02-21 15:09:21 +00:00
case DGR_ITEM_LIGHT_CHANNELS :
break ;
default :
2020-04-15 14:40:18 +01:00
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 ) ) ;
2020-02-21 15:09:21 +00:00
}
# 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 ;
2020-04-07 19:25:58 +01:00
# 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 ;
2020-02-21 15:09:21 +00:00
}
2020-04-07 19:25:58 +01:00
# endif // USE_DEVICE_GROUPS_SEND
}
# ifdef USE_DEVICE_GROUPS_SEND
else {
device_group - > values_8bit [ item ] = value ;
2020-02-21 15:09:21 +00:00
}
2020-04-07 19:25:58 +01:00
# endif // USE_DEVICE_GROUPS_SEND
2020-04-19 04:37:05 +01:00
//#ifdef DEVICE_GROUPS_DEBUG
AddLog_P2 ( LOG_LEVEL_DEBUG_MORE , PSTR ( " <%u=%u " ) , item , value ) ;
//#endif // DEVICE_GROUPS_DEBUG
2020-02-21 15:09:21 +00:00
XdrvMailbox . payload = value ;
}
else if ( item < = DGR_ITEM_MAX_STRING ) {
2020-04-19 04:37:05 +01:00
value = strlen ( message_ptr ) ;
2020-02-21 15:09:21 +00:00
if ( value > = packet_length - ( message_ptr - packet ) ) goto badmsg ; // Malformed message
2020-04-19 04:37:05 +01:00
//#ifdef DEVICE_GROUPS_DEBUG
AddLog_P2 ( LOG_LEVEL_DEBUG_MORE , PSTR ( " <%u='%s' " ) , item , message_ptr ) ;
//#endif // DEVICE_GROUPS_DEBUG
2020-02-21 15:09:21 +00:00
XdrvMailbox . data_len = value ;
XdrvMailbox . data = message_ptr ;
2020-04-19 04:37:05 +01:00
message_ptr + = value + 1 ;
2020-02-21 15:09:21 +00:00
}
else {
switch ( item ) {
case DGR_ITEM_LIGHT_CHANNELS :
2020-04-19 04:37:05 +01:00
//#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
2020-02-21 15:09:21 +00:00
XdrvMailbox . data = message_ptr ;
message_ptr + = 5 ;
break ;
}
}
2020-04-07 19:25:58 +01:00
if ( DeviceGroupItemShared ( true , item ) ) {
2020-02-21 15:09:21 +00:00
if ( item = = DGR_ITEM_POWER ) {
2020-03-17 03:03:31 +00:00
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 ) ;
}
2020-02-21 15:09:21 +00:00
}
}
2020-04-19 04:37:05 +01:00
else if ( item = = DGR_ITEM_EVENT ) {
CmndEvent ( ) ;
}
2020-04-04 16:27:40 +01:00
XdrvCall ( FUNC_DEVICE_GROUP_ITEM ) ;
2020-02-21 15:09:21 +00:00
}
}
XdrvMailbox . command_code = DGR_ITEM_EOL ;
2020-03-12 17:51:54 +00:00
XdrvCall ( FUNC_DEVICE_GROUP_ITEM ) ;
2020-03-05 23:51:22 +00:00
skip_light_fade = false ;
2020-02-21 15:09:21 +00:00
processing_remote_device_message = false ;
# ifdef DEVICE_GROUPS_DEBUG
2020-02-24 22:34:45 +00:00
AddLog_P2 ( LOG_LEVEL_DEBUG , PSTR ( " <processed " ) ) ;
2020-02-21 15:09:21 +00:00
# endif // DEVICE_GROUPS_DEBUG
return ;
badmsg :
2020-04-15 14:40:18 +01:00
AddLog_P2 ( LOG_LEVEL_ERROR , PSTR ( " DGR: Malformed message received from %s " ) , IPAddressToString ( remote_ip ) ) ;
2020-02-21 15:09:21 +00:00
# ifdef DEVICE_GROUPS_DEBUG
2020-02-24 22:34:45 +00:00
AddLog_P2 ( LOG_LEVEL_DEBUG , PSTR ( " packet_length=%u, offset=%u " ) , packet_length , message_ptr - packet ) ;
2020-02-21 15:09:21 +00:00
# endif // DEVICE_GROUPS_DEBUG
processing_remote_device_message = false ;
}
2020-04-15 14:40:18 +01:00
void DeviceGroupStatus ( uint8_t device_group_index )
{
if ( Settings . flag4 . device_groups_enabled & & device_group_index < device_group_count ) {
char buffer [ 1024 ] ;
int member_count = 0 ;
struct device_group * device_group = & device_groups [ device_group_index ] ;
buffer [ 0 ] = buffer [ 1 ] = 0 ;
for ( struct device_group_member * device_group_member = device_group - > 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 ) ;
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 ] ) ;
}
}
2020-02-21 15:09:21 +00:00
void DeviceGroupsLoop ( void )
{
2020-02-24 22:34:45 +00:00
if ( ! Settings . flag4 . device_groups_enabled ) return ;
2020-02-21 15:09:21 +00:00
if ( udp_connected ) {
2020-03-12 17:51:54 +00:00
uint32_t now = millis ( ) ;
// If UDP was not connected before, (re)initialize.
2020-02-21 15:09:21 +00:00
if ( ! udp_was_connected ) {
udp_was_connected = true ;
2020-02-25 00:40:44 +00:00
if ( ! device_groups_initialized ) DeviceGroupsInit ( ) ;
if ( device_groups_initialization_failed ) return ;
2020-04-15 14:40:18 +01:00
// Load the status request message for all device groups. This message will be multicast 10
// times at 200ms intervals.
2020-04-07 19:25:58 +01:00
next_check_time = now + 3000 ;
2020-02-21 15:09:21 +00:00
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 ] ;
2020-03-12 17:51:54 +00:00
device_group - > message_length = BeginDeviceGroupMessage ( device_group , DGR_FLAG_RESET | DGR_FLAG_STATUS_REQUEST ) - device_group - > message ;
2020-04-15 14:40:18 +01:00
device_group - > initial_status_requests_remaining = 10 ;
2020-03-12 17:51:54 +00:00
device_group - > next_ack_check_time = next_check_time ;
2020-02-21 15:09:21 +00:00
}
2020-04-15 14:40:18 +01:00
AddLog_P2 ( LOG_LEVEL_DEBUG , PSTR ( " DGR: (Re)discovering device groups " ) ) ;
2020-02-21 15:09:21 +00:00
}
2020-02-25 00:40:44 +00:00
if ( device_groups_initialization_failed ) return ;
2020-03-12 17:51:54 +00:00
// If it's time to check on things, iterate through the device groups.
if ( next_check_time < = now ) {
# ifdef DEVICE_GROUPS_DEBUG
AddLog_P2 ( LOG_LEVEL_DEBUG , PSTR ( " DGR: Ckecking next_check_time=%u, now=%u " ) , next_check_time , now ) ;
# endif // DEVICE_GROUPS_DEBUG
next_check_time = now + DGR_ANNOUNCEMENT_INTERVAL * 2 ;
2020-02-21 15:09:21 +00:00
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 ] ;
2020-03-12 17:51:54 +00:00
// If we're still waiting for acks to the last update from this device group, ...
2020-02-21 15:09:21 +00:00
if ( device_group - > next_ack_check_time ) {
2020-03-12 17:51:54 +00:00
// If it's time to check for acks, ...
2020-02-21 15:09:21 +00:00
if ( device_group - > next_ack_check_time < = now ) {
2020-03-12 17:51:54 +00:00
// If we're still sending the initial status request message, send it.
2020-02-21 15:09:21 +00:00
if ( device_group - > initial_status_requests_remaining ) {
2020-04-07 19:25:58 +01:00
if ( - - device_group - > initial_status_requests_remaining ) {
2020-02-21 15:09:21 +00:00
# ifdef DEVICE_GROUPS_DEBUG
2020-04-15 14:40:18 +01:00
AddLog_P2 ( LOG_LEVEL_DEBUG , PSTR ( " DGR: Sending initial status request for group %s " ) , device_group - > group_name ) ;
2020-02-21 15:09:21 +00:00
# endif // DEVICE_GROUPS_DEBUG
2020-04-10 17:24:08 +01:00
SendDeviceGroupPacket ( IPAddress ( 0 , 0 , 0 , 0 ) , device_group - > message , device_group - > message_length , PSTR ( " Initial " ) ) ;
2020-02-21 15:09:21 +00:00
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 ;
}
2020-03-12 17:51:54 +00:00
2020-04-15 14:40:18 +01:00
// If we've sent the initial status request message the set number of times, send our
// status to all the members.
2020-02-21 15:09:21 +00:00
else {
device_group - > next_ack_check_time = 0 ;
_SendDeviceGroupMessage ( device_group_index , DGR_MSGTYP_FULL_STATUS ) ;
}
}
2020-03-12 17:51:54 +00:00
// If we're done initializing, iterate through the group memebers, ...
2020-02-21 15:09:21 +00:00
else {
2020-03-12 17:51:54 +00:00
# ifdef DEVICE_GROUPS_DEBUG
2020-04-15 14:40:18 +01:00
AddLog_P2 ( LOG_LEVEL_DEBUG , PSTR ( " DGR: Checking for ack's " ) ) ;
2020-03-12 17:51:54 +00:00
# endif // DEVICE_GROUPS_DEBUG
2020-02-21 15:09:21 +00:00
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 ) ) {
2020-03-12 17:51:54 +00:00
// If we have not received an ack to our last message from this member, ...
2020-02-21 15:09:21 +00:00
if ( device_group_member - > acked_sequence ! = outgoing_sequence ) {
2020-03-12 17:51:54 +00:00
// 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.
if ( device_group - > member_timeout_time < now ) {
2020-04-15 14:40:18 +01:00
AddLog_P2 ( LOG_LEVEL_DEBUG , PSTR ( " DGR: Removing member %s " ) , IPAddressToString ( device_group_member - > ip_address ) ) ;
2020-02-21 15:09:21 +00:00
* flink = device_group_member - > flink ;
free ( device_group_member ) ;
}
2020-03-12 17:51:54 +00:00
// Otherwise, unicast the last message directly to this member.
2020-02-21 15:09:21 +00:00
else {
# ifdef DEVICE_GROUPS_DEBUG
2020-04-15 14:40:18 +01:00
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 ) ;
2020-02-21 15:09:21 +00:00
# endif // DEVICE_GROUPS_DEBUG
2020-02-24 22:34:45 +00:00
SendDeviceGroupPacket ( device_group_member - > ip_address , device_group - > message , device_group - > message_length , PSTR ( " Unicast " ) ) ;
2020-04-15 14:40:18 +01:00
device_group_member - > unicast_count + + ;
2020-02-21 15:09:21 +00:00
acked = false ;
flink = & device_group_member - > flink ;
}
}
else {
flink = & device_group_member - > flink ;
}
}
2020-03-12 17:51:54 +00:00
// If we've received an ack to the last message from all members, clear the ack check
// time and zero-out the message length.
2020-02-21 15:09:21 +00:00
if ( acked ) {
device_group - > next_ack_check_time = 0 ;
2020-03-12 17:51:54 +00:00
device_group - > message_length = 0 ; // Let _SendDeviceGroupMessage know we're done with this update
2020-02-21 15:09:21 +00:00
}
2020-03-12 17:51:54 +00:00
// If there are still members we haven't received an ack from, set the next ack check
// time. We start at 200ms and double the interval each pass with a maximum interval of
// 5 seconds.
2020-02-21 15:09:21 +00:00
else {
2020-03-12 17:51:54 +00:00
device_group - > ack_check_interval * = 2 ;
if ( device_group - > ack_check_interval > 5000 ) device_group - > ack_check_interval = 5000 ;
device_group - > next_ack_check_time = now + device_group - > ack_check_interval ;
2020-02-21 15:09:21 +00:00
}
}
}
2020-03-12 17:51:54 +00:00
if ( device_group - > next_ack_check_time < next_check_time ) next_check_time = device_group - > next_ack_check_time ;
}
2020-04-06 18:29:50 +01:00
// If it's time to send a multicast announcement for this group, send it. This is to
2020-03-12 17:51:54 +00:00
// announcement ourself to any members that have somehow not heard about us. We send it at
// the announcement interval plus a random number of milliseconds so that even if all the
// devices booted at the same time, they don't all multicast their announcements at the same
// time.
2020-03-13 11:17:19 +00:00
# ifdef DEVICE_GROUPS_DEBUG
AddLog_P2 ( LOG_LEVEL_DEBUG , PSTR ( " DGR: next_announcement_time=%u, now=%u " ) , device_group - > next_announcement_time , now ) ;
# endif // DEVICE_GROUPS_DEBUG
2020-03-12 17:51:54 +00:00
if ( device_group - > next_announcement_time < = now ) {
# ifdef DEVICE_GROUPS_DEBUG
2020-04-15 14:40:18 +01:00
AddLog_P2 ( LOG_LEVEL_DEBUG , PSTR ( " DGR: Sending device group %s announcement " ) , device_group - > group_name ) ;
2020-03-12 17:51:54 +00:00
# endif // DEVICE_GROUPS_DEBUG
2020-04-10 17:24:08 +01:00
SendDeviceGroupPacket ( IPAddress ( 0 , 0 , 0 , 0 ) , device_group - > message , BeginDeviceGroupMessage ( device_group , DGR_FLAG_ANNOUNCEMENT , true ) - device_group - > message , PSTR ( " Announcement " ) ) ;
2020-03-12 17:51:54 +00:00
device_group - > next_announcement_time = now + DGR_ANNOUNCEMENT_INTERVAL + random ( 10000 ) ;
2020-02-21 15:09:21 +00:00
}
2020-03-12 17:51:54 +00:00
if ( device_group - > next_announcement_time < next_check_time ) next_check_time = device_group - > next_announcement_time ;
2020-02-21 15:09:21 +00:00
}
}
}
else {
udp_was_connected = false ;
}
}
# endif // USE_DEVICE_GROUPS