11"""Insteon Device Classes."""
22import asyncio
33import async_timeout
4- import collections
4+ from collections import namedtuple
55from concurrent .futures import CancelledError
6- from contextlib import suppress
76import datetime
87from enum import Enum
98import logging
2019 COMMAND_GET_INSTEON_ENGINE_VERSION_0X0D_0X00 ,
2120 COMMAND_PING_0X0F_0X00 ,
2221 COMMAND_EXTENDED_READ_WRITE_ALDB_0X2F_0X00 ,
22+ MESSAGE_ACK ,
23+ MESSAGE_TYPE_BROADCAST_MESSAGE ,
2324 MESSAGE_TYPE_DIRECT_MESSAGE ,
2425 MESSAGE_FLAG_DIRECT_MESSAGE_NAK_0XA0 ,
2526 MESSAGE_STANDARD_MESSAGE_RECEIVED_0X50 ,
2627 MESSAGE_EXTENDED_MESSAGE_RECEIVED_0X51
2728)
2829from insteonplm .messagecallback import MessageCallback
30+ from insteonplm .messages .allLinkComplete import AllLinkComplete
2931from insteonplm .messages .extendedReceive import ExtendedReceive
3032from insteonplm .messages .standardReceive import StandardReceive
3133from insteonplm .messages .extendedSend import ExtendedSend
4143ALDB_ALL_RECORD_RETRIES = 5
4244
4345
46+ LoadAction = namedtuple ('LoadAction' , 'mem_addr rec_count retries' )
47+ DeviceInfo = namedtuple ('DeviceInfo' , 'address cat subcat firmware' )
48+
49+
4450def create (plm , address , cat , subcat , firmware = None ):
4551 """Create a device from device info data."""
4652 from insteonplm .devices .ipdb import IPDB
@@ -74,7 +80,7 @@ class Device(object):
7480 def __init__ (self , plm , address , cat , subcat , product_key = 0x00 ,
7581 description = '' , model = '' ):
7682 """Initialize the Device class."""
77- self .log = logging .getLogger (__name__ )
83+ self .log = logging .getLogger ()
7884
7985 self ._plm = plm
8086
@@ -99,7 +105,9 @@ def __init__(self, plm, address, cat, subcat, product_key=0x00,
99105 self ._recent_messages = asyncio .Queue (loop = self ._plm .loop )
100106 self ._send_msg_queue = asyncio .Queue (loop = self ._plm .loop )
101107 self ._directACK_received_queue = asyncio .Queue (loop = self ._plm .loop )
108+ self ._device_info_queue = asyncio .Queue (loop = self ._plm .loop )
102109 self ._send_msg_lock = asyncio .Lock (loop = self ._plm .loop )
110+ self ._device_info_retries = 0
103111
104112 self ._register_messages ()
105113
@@ -172,6 +180,10 @@ def async_refresh_state(self):
172180
173181 def id_request (self ):
174182 """Request a device ID from a device."""
183+ import inspect
184+ curframe = inspect .currentframe ()
185+ calframe = inspect .getouterframes (curframe , 2 )
186+ self .log .debug ('caller name: %s' , calframe [1 ][3 ])
175187 msg = StandardSend (self .address , COMMAND_ID_REQUEST_0X10_0X00 )
176188 self ._plm .send_msg (msg )
177189
@@ -310,28 +322,176 @@ def _handle_pre_nak(self, msg):
310322 self .log .warning ('Checking status to confirm if message was processed' )
311323 self .async_refresh_state ()
312324
325+ def _handle_get_device_info_ack (self , msg ):
326+ self .log .debug ('Starting _handle_get_device_info_ack with message:' )
327+ self .log .debug (msg )
328+ asyncio .ensure_future (self ._wait_device_info (msg ), loop = self ._plm .loop )
329+
330+ def _handle_assign_to_all_link_group (self , msg ):
331+ cat = 0xff
332+ subcat = 0
333+ product_key = 0
334+ if msg .code == StandardReceive .code and msg .flags .isBroadcast :
335+ self .log .debug ('Received broadcast ALDB group assigment request.' )
336+ cat = msg .targetLow
337+ subcat = msg .targetMed
338+ product_key = msg .targetHi
339+ self ._add_device_from_prod_data (msg .address , cat ,
340+ subcat , product_key )
341+ elif msg .code == AllLinkComplete .code :
342+ if msg .linkcode in [0 , 1 , 3 ]:
343+ self .log .debug ('Received ALDB complete response.' )
344+ cat = msg .category
345+ subcat = msg .subcategory
346+ product_key = msg .firmware
347+ self ._add_device_from_prod_data (msg .address , cat ,
348+ subcat , product_key )
349+ self ._update_aldb_records (msg .linkcode , msg .address , msg .group )
350+ else :
351+ self .log .debug ('Received ALDB delete response.' )
352+ self ._update_aldb_records (msg .linkcode , msg .address , msg .group )
353+
354+ def _add_device_from_prod_data (self , address , cat , subcat , product_key ):
355+ self .log .debug ('Received Device ID with address: %s '
356+ 'cat: 0x%x subcat: 0x%x' , address , cat , subcat )
357+ device = self ._plm .devices .create_device_from_category (
358+ self ._plm , address , cat , subcat , product_key )
359+ if device :
360+ if not self ._plm .devices [device .id ]:
361+ self ._plm .devices [device .id ] = device
362+ self .log .info ('Device with id %s added to device list.' ,
363+ device .id )
364+ self .log .info ('Total Devices Found: %d' , len (self ._plm .devices ))
365+ self ._plm .aldb_device_handled (self ._address )
366+ self ._plm .devices .save_device_info ()
367+ else :
368+ self .log .error ('Device %s not in the IPDB.' ,
369+ Address (address ).human )
370+ self ._plm .device_not_active (self .address )
371+
372+ def _update_aldb_records (self , linkcode , address , group ):
373+ """Refresh the IM and device ALDB records."""
374+ device = self .devices [Address (address ).id ]
375+ if device and device .aldb .status in [ALDBStatus .LOADED ,
376+ ALDBStatus .PARTIAL ]:
377+ for mem_addr in device .aldb :
378+ rec = device .aldb [mem_addr ]
379+ if linkcode in [0 , 1 , 3 ]:
380+ if rec .control_flags .is_high_water_mark :
381+ self .log .info ('Removing HWM recordd %04x' , mem_addr )
382+ device .aldb .pop (mem_addr )
383+ elif not rec .control_flags .is_in_use :
384+ self .log .info ('Removing not in use recordd %04x' ,
385+ mem_addr )
386+ device .aldb .pop (mem_addr )
387+ else :
388+ if rec .address == self .address and rec .group == group :
389+ self .log .info ('Removing record %04x with addr %s and '
390+ 'group %d' , mem_addr , rec .address ,
391+ rec .group )
392+ device .aldb .pop (mem_addr )
393+ device .read_aldb ()
394+ device .aldb .add_loaded_callback (self ._refresh_aldb ())
395+
396+ @asyncio .coroutine
397+ def _wait_device_info (self , msg ):
398+ self .log .debug ('Starting _wait_device_info for device %s in %s' ,
399+ msg .address .human , self ._address .human )
400+ try :
401+ with async_timeout .timeout (6 ):
402+ device_info_msg = yield from self ._device_info_queue .get ()
403+ self .log .debug ('_wait_device_info got device_id message %s' ,
404+ device_info_msg )
405+ except asyncio .TimeoutError :
406+ self .log .debug ('_wait_device_info timeout reached for message %s in %s' ,
407+ msg .address .human , self ._address .human )
408+ if self ._device_info_retries < 5 :
409+ self ._device_info_retries += 1
410+ self .log .debug ('Device %s id_request retry %d' ,
411+ self .address .human , self ._device_info_retries )
412+ self .id_request ()
413+ else :
414+ self .log .warning ('Device %s did not respond to an ID request' ,
415+ self .address .human )
416+ self .log .warning ('Use a device override to define the device '
417+ 'type' )
418+ self ._plm .device_not_active (self .address )
419+ return
420+ self ._device_info_retries = 0
421+ address = device_info_msg .address
422+ cat = device_info_msg .targetLow
423+ subcat = device_info_msg .targetMed
424+ product_key = device_info_msg .targetHi
425+ self ._add_device_from_prod_data (address , cat , subcat , product_key )
426+
427+ def _handle_device_info_response (self , msg ):
428+ self .log .debug ('Got device_id for %s in %s' , msg .address .human ,
429+ self ._address .human )
430+ self ._device_info_queue .put_nowait (msg )
431+
432+ def _handle_all_link_complete (self , msg ):
433+ last_record = None
434+ for mem_addr in self .aldb :
435+ aldb_rec = self .aldb [mem_addr ]
436+ if aldb_rec .control_flags .is_high_water_mark :
437+ last_record = mem_addr
438+ if last_record :
439+ self .aldb .pop (last_record )
440+ self .read_aldb ()
441+
313442 def _register_messages (self ):
314- ext_msg_aldb_record = ExtendedReceive .template (
315- address = self ._address ,
316- commandtuple = COMMAND_EXTENDED_READ_WRITE_ALDB_0X2F_0X00 ,
317- userdata = Userdata .template ({'d2' : 1 }),
318- flags = MessageFlags .template (
319- messageType = MESSAGE_TYPE_DIRECT_MESSAGE ,
320- extended = 1 ))
321- std_msg_pre_nak = StandardReceive .template (
322- flags = MessageFlags .template (
323- messageType = MESSAGE_FLAG_DIRECT_MESSAGE_NAK_0XA0 ),
324- cmd2 = 0xfc )
325- ext_msg_pre_nak = ExtendedReceive .template (
326- flags = MessageFlags .template (
327- messageType = MESSAGE_FLAG_DIRECT_MESSAGE_NAK_0XA0 ),
328- cmd2 = 0xfc )
329- self ._message_callbacks .add (ext_msg_aldb_record ,
330- self ._handle_aldb_record_received )
331- self ._message_callbacks .add (std_msg_pre_nak ,
332- self ._handle_pre_nak )
333- self ._message_callbacks .add (ext_msg_pre_nak ,
334- self ._handle_pre_nak )
443+ self .log .debug ('Registering messages for %s' , self ._address .human )
444+ std_device_info_request_ack = StandardSend .template (
445+ address = self ._address ,
446+ commandtuple = COMMAND_ID_REQUEST_0X10_0X00 ,
447+ acknak = MESSAGE_ACK )
448+ self .log .debug ('ID ACK: %s' , std_device_info_request_ack )
449+ self ._message_callbacks .add (std_device_info_request_ack ,
450+ self ._handle_get_device_info_ack )
451+
452+ std_device_info_response = StandardReceive .template (
453+ address = self ._address ,
454+ commandtuple = COMMAND_ASSIGN_TO_ALL_LINK_GROUP_0X01_NONE ,
455+ flags = MessageFlags .template (
456+ messageType = MESSAGE_TYPE_BROADCAST_MESSAGE ))
457+ self ._message_callbacks .add (std_device_info_response ,
458+ self ._handle_device_info_response )
459+
460+ std_assign_all_link = StandardReceive .template (
461+ address = self ._address ,
462+ commandtuple = COMMAND_ASSIGN_TO_ALL_LINK_GROUP_0X01_NONE ,
463+ flags = MessageFlags .template (MESSAGE_TYPE_DIRECT_MESSAGE ))
464+ self ._message_callbacks .add (std_assign_all_link ,
465+ self ._handle_assign_to_all_link_group )
466+
467+ ext_msg_aldb_record = ExtendedReceive .template (
468+ address = self ._address ,
469+ commandtuple = COMMAND_EXTENDED_READ_WRITE_ALDB_0X2F_0X00 ,
470+ userdata = Userdata .template ({'d2' : 1 }),
471+ flags = MessageFlags .template (
472+ messageType = MESSAGE_TYPE_DIRECT_MESSAGE ,
473+ extended = 1 ))
474+ self ._message_callbacks .add (ext_msg_aldb_record ,
475+ self ._handle_aldb_record_received )
476+
477+ std_msg_pre_nak = StandardReceive .template (
478+ flags = MessageFlags .template (
479+ messageType = MESSAGE_FLAG_DIRECT_MESSAGE_NAK_0XA0 ),
480+ cmd2 = 0xfc )
481+ self ._message_callbacks .add (std_msg_pre_nak ,
482+ self ._handle_pre_nak )
483+
484+ ext_msg_pre_nak = ExtendedReceive .template (
485+ flags = MessageFlags .template (
486+ messageType = MESSAGE_FLAG_DIRECT_MESSAGE_NAK_0XA0 ),
487+ cmd2 = 0xfc )
488+ self ._message_callbacks .add (ext_msg_pre_nak ,
489+ self ._handle_pre_nak )
490+
491+ template_all_link_complete = AllLinkComplete (None , None , None ,
492+ None , None , None )
493+ self ._message_callbacks .add (template_all_link_complete ,
494+ self ._handle_all_link_complete )
335495
336496 # Send / Receive message processing
337497 def receive_message (self , msg ):
@@ -419,7 +579,6 @@ def _send_msg(self, msg, callback=None, on_timeout=False):
419579 asyncio .ensure_future (self ._process_send_queue (), loop = self ._plm .loop )
420580 self .log .debug ('Ending Device._send_msg' )
421581
422-
423582 @asyncio .coroutine
424583 def _process_send_queue (self ):
425584 self .log .debug ('Starting Device._process_send_queue' )
@@ -432,8 +591,8 @@ def _process_send_queue(self):
432591 if callback :
433592 self ._sent_msg_wait_for_directACK = msg_info
434593 self ._plm .send_msg (msg )
435- #if self._send_msg_lock.locked():
436- # self._send_msg_lock.release()
594+ # if self._send_msg_lock.locked():
595+ # self._send_msg_lock.release()
437596 self .log .debug ('Ending Device._process_send_queue' )
438597
439598 @asyncio .coroutine
@@ -807,9 +966,6 @@ class ALDBVersion(Enum):
807966 v2cs = 20
808967
809968
810- LoadAction = collections .namedtuple ('LoadAction' , 'mem_addr rec_count retries' )
811-
812-
813969class ALDB (object ):
814970 """Represents a device All-Link database."""
815971
0 commit comments