2727 MESSAGE_TYPE_ALL_LINK_CLEANUP ,
2828 MESSAGE_FLAG_DIRECT_MESSAGE_NAK_0XA0 ,
2929 MESSAGE_STANDARD_MESSAGE_RECEIVED_0X50 ,
30- MESSAGE_EXTENDED_MESSAGE_RECEIVED_0X51
31- )
30+ MESSAGE_EXTENDED_MESSAGE_RECEIVED_0X51 ,
31+ MESSAGE_TYPE_ALL_LINK_BROADCAST )
3232from insteonplm .messagecallback import MessageCallback
3333from insteonplm .messages .allLinkComplete import AllLinkComplete
3434from insteonplm .messages .extendedReceive import ExtendedReceive
4343DIRECT_ACK_WAIT_TIMEOUT = 3
4444ALDB_RECORD_TIMEOUT = 10
4545ALDB_RECORD_RETRIES = 20
46- ALDB_ALL_RECORD_TIMEOUT = 30
46+ ALDB_ALL_RECORD_TIMEOUT = 120
4747ALDB_ALL_RECORD_RETRIES = 5
4848
4949
@@ -215,9 +215,9 @@ def ALL_Link_cleanup(self, group, cmd_tuple):
215215 self .address .human , group )
216216 flags = MessageFlags .template (MESSAGE_TYPE_ALL_LINK_CLEANUP , 0 , 3 , 3 )
217217 msg = StandardSend (self ._address , cmd_tuple , cmd2 = group , flags = flags )
218- self ._send_msg (msg , self ._handle_ALL_Link_cleanup_ack )
218+ self ._send_msg (msg , self ._handle_ALL_Link_cleanup )
219219
220- def _handle_ALL_Link_cleanup_ack (self , msg ):
220+ def _handle_ALL_Link_cleanup (self , msg ):
221221 _LOGGER .debug ('Received ALL-Link Cleanup ACK from %s; ALL-Link group '
222222 '0x%x; looking for responder states' ,
223223 msg .address .human , msg .cmd2 )
@@ -226,16 +226,10 @@ def _handle_ALL_Link_cleanup_ack(self, msg):
226226 responders = self ._find_group_responder_states (msg .target , msg .cmd2 )
227227
228228 for responder in responders :
229- _LOGGER .debug ('Calling %s:0x%x:handle_ALL_Link_cleanup_ack ' ,
229+ _LOGGER .debug ('Calling %s:0x%02x:handle_ALL_Link_cleanup ' ,
230230 self ._address .human , responder )
231- if hasattr (self .states [responder ], 'handle_ALL_Link_cleanup_ack' ):
232- self .states [responder ].handle_ALL_Link_cleanup_ack (
233- msg , responders [responder ])
234- else :
235- _LOGGER .warning ('Device %s:0x%x has no '
236- 'handle_ALL_Link_cleanup_ack() method. '
237- 'Cannot perform ALL-Link cleanup' ,
238- self ._address .human , responder )
231+ self .states [responder ].handle_ALL_Link_cleanup (
232+ msg , responders [responder ])
239233
240234 def _find_group_responder_states (self , ctl , ctl_group ):
241235 _LOGGER .debug ('Looking for responder to controller %s:0x%x' ,
@@ -729,11 +723,21 @@ def _register_messages(self):
729723 self ._message_callbacks .add (template_all_link_complete ,
730724 self ._handle_all_link_complete )
731725
726+ template_All_Link_broadcast = StandardReceive .template (
727+ flags = MessageFlags .template (MESSAGE_TYPE_ALL_LINK_CLEANUP , None ))
728+ self ._message_callbacks .add (template_All_Link_broadcast ,
729+ self ._handle_All_Link_broadcast )
730+
731+ template_All_Link_cleanup = StandardReceive .template (
732+ flags = MessageFlags .template (MESSAGE_TYPE_ALL_LINK_BROADCAST , None ))
733+ self ._message_callbacks .add (template_All_Link_cleanup ,
734+ self ._handle_All_Link_broadcast )
735+
732736 # Send / Receive message processing
733737 def receive_message (self , msg ):
734738 """Receive a messages sent to this device."""
735739 _LOGGER .debug ('Starting Device.receive_message for %s' ,
736- msg .address .human )
740+ self .address .human )
737741 if hasattr (msg , 'isack' ) and msg .isack :
738742 _LOGGER .debug ('Got Message ACK %s' , id (msg ))
739743 if self ._sent_msg_wait_for_directACK .get ('callback' ) is not None :
@@ -800,8 +804,24 @@ def _is_duplicate(self, msg):
800804 if msg .matches_pattern (prev_msg ):
801805 ret_val = True
802806
807+ # Add current message to recent list
803808 self ._recent_messages .put_nowait (
804809 {"msg" : msg , "received" : datetime .datetime .now ()})
810+
811+ # If an ALL-Link Broadcast was successfully received the ALL-Link
812+ # cleanup should be considered a duplicate if not a 0x06 status report
813+ if (msg .flags .isAllLinkBroadcast and msg .cmd1 != MESSAGE_ACK ):
814+ # Fabricate duplicate all-link cleanup
815+ dup_target = self ._plm .address
816+ dup_group = msg .targetHi
817+ dup_flags = MessageFlags .template (MESSAGE_TYPE_ALL_LINK_CLEANUP ,
818+ 0 , 3 , 3 )
819+ dup_msg = StandardReceive (msg .address , dup_target ,
820+ {'cmd1' : msg .cmd1 , 'cmd2' : dup_group },
821+ flags = dup_flags )
822+ self ._recent_messages .put_nowait (
823+ {"msg" : dup_msg , "received" : datetime .datetime .now ()})
824+
805825 return ret_val
806826
807827 def _send_msg (self , msg , callback = None , on_timeout = False ):
@@ -870,6 +890,88 @@ async def _wait_for_direct_ACK(self):
870890 def _aldb_loaded_callback (self ):
871891 self ._plm .devices .save_device_info ()
872892
893+ def _handle_All_Link_broadcast (self , msg ):
894+ """Process ALL-Link broadcast or cleanup received from devices.
895+
896+ Parameters:
897+ msg.address: controller's address
898+ msg.targetHi: group triggered on controller for broadcast msg
899+ msg.cmd2: group triggers on controller for clean up msg
900+
901+ Searches for controller's address and group triggered in this devices
902+ ALDB to find this devices responding group.
903+
904+ """
905+ if msg .cmd1 != MESSAGE_ACK : # Ignore 0x06 status reports
906+
907+ # Don't bother searching the ALDB if there isn't at least one
908+ # state that can respond to controllers
909+ responder = False
910+ for state in self ._stateList :
911+ if self ._stateList [state ].is_responder :
912+ responder = True
913+ break
914+
915+ if responder :
916+ ctl_group = msg .targetHi
917+ _LOGGER .debug ('_handle_All_Link_broadcast msg: %s for '
918+ 'device %s' , id (msg ), self .address .human )
919+ if not ctl_group :
920+ # Not a broadcast message so must be a cleanup
921+ ctl_group = msg .cmd2
922+
923+ resp_groups = self ._find_responder_groups (msg .address ,
924+ ctl_group )
925+ for resp_group in resp_groups :
926+ if self ._stateList [resp_group ]:
927+ self ._stateList [resp_group ].handle_ALL_Link_cleanup (
928+ msg , resp_groups [resp_group ])
929+ else :
930+ _LOGGER .warning ('ALDB Responder record maps to '
931+ 'nonexistent state' )
932+
933+ def _find_responder_groups (self , ctl_address , ctl_group ):
934+ """Identify which PLM group responds to ctl / ctl_group.
935+
936+ Parameters:
937+ ctl_address: address object of controller that sent ALL-Link command
938+ ctl_group: group that was triggered on the controller
939+
940+ Returns:
941+ dictionary of all responding groups on this device
942+ key: responding group on this device from data3
943+ value: recall_level from data1
944+
945+ NOTE: The IM class overrides this method for the PLM's underlying
946+ device object.
947+
948+ """
949+ _LOGGER .debug ('_find_responder_groups: looking for ctl: %s, '
950+ 'ctl_group: 0x%02x on device %s' , ctl_address .human ,
951+ ctl_group , self .address .human )
952+ groups = {}
953+ if self ._aldb :
954+ for rec_num in self ._aldb :
955+ rec = self ._aldb [rec_num ]
956+ if (rec .control_flags .is_responder and
957+ rec .group == ctl_group and
958+ rec .address == ctl_address ):
959+ # Recall level is stored in data1
960+ # This device's group is stored in data3
961+ data3 = rec .data3 if rec .data3 != 0 else 1
962+ groups [data3 ] = rec .data1
963+ _LOGGER .debug ('Found %d responders on device %s' , len (groups ),
964+ self .address .human )
965+ else :
966+ if self ._aldb .status == ALDBStatus .LOADED :
967+ _LOGGER .warning ('No responders found; ALDB is empty %s' ,
968+ self .address .human )
969+ else :
970+ _LOGGER .warning ('No responders found; ALDB is not loaded %s' ,
971+ self .address .human )
972+
973+ return groups
974+
873975
874976# pylint: disable=too-many-instance-attributes
875977class X10Device ():
0 commit comments