Skip to content

Commit 90d4f11

Browse files
authored
Message handling (#89)
* Refactor message write process and remvoe some logging * Clean up message processing * flake8 and pylint fixes * Startup process changes * Reset get_next_all_link_rec NAK count on record received * Refresh ALDB on All-Link complete * Bump version to 0.12.1 * Update for multiple get_next_all_link_record NAKs
1 parent 9e98b25 commit 90d4f11

File tree

9 files changed

+391
-268
lines changed

9 files changed

+391
-268
lines changed

insteonplm/__init__.py

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
import binascii
99
from contextlib import suppress
1010
import logging
11-
import serial
1211
from serial.aio import create_serial_connection
1312

1413
from insteonplm.plm import PLM, Hub
@@ -176,7 +175,7 @@ def _connect(self):
176175
if self.host:
177176
connected = yield from self._connect_http()
178177
else:
179-
connected = yield from self._connect_serial()
178+
connected = yield from self._connect_serial()
180179
_LOGGER.debug('ending Connection._connect')
181180
return connected
182181

@@ -333,7 +332,6 @@ def _async_write(self, url):
333332
_LOGGER.error('Reconnect to Hub (TimeoutError)')
334333
yield from self._stop_reader(True)
335334

336-
337335
if self._read_write_lock.locked():
338336
self._read_write_lock.release()
339337
return return_status
@@ -391,7 +389,7 @@ def _ensure_reader(self):
391389
bin_buffer = binascii.unhexlify(buffer)
392390
self._protocol.data_received(bin_buffer)
393391
yield from asyncio.sleep(1, loop=self._loop)
394-
392+
395393
except asyncio.CancelledError:
396394
_LOGGER.debug('Stop connection to Hub (loop stopped)')
397395
yield from self._stop_reader(False)
@@ -468,16 +466,16 @@ def _stop_reader(self, reconnect=False):
468466
_LOGGER.debug("We want to reconnect so we do...")
469467
self._protocol.connection_lost(True)
470468

471-
def _log_error(self, error):
469+
def _log_error(self, status):
472470
# TODO: handle other status codes
473-
if response.status == 401:
471+
if status == 401:
474472
_LOGGER.error('Athentication error, check your configuration')
475473
_LOGGER.error('If configuration is correct and restart the Hub')
476474
_LOGGER.error('System must be restared to reconnect to hub')
477-
elif response.status == 404:
475+
elif status == 404:
478476
_LOGGER.error('Hub not found at http://%s:%d, check configuration',
479477
self._host, self._port)
480-
elif response.status in range(500, 600):
478+
elif status in range(500, 600):
481479
_LOGGER.error('Hub returned a server error')
482480
_LOGGER.error('Restart the Hub and try again')
483481
else:

insteonplm/devices/__init__.py

Lines changed: 186 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
"""Insteon Device Classes."""
22
import asyncio
33
import async_timeout
4-
import collections
4+
from collections import namedtuple
55
from concurrent.futures import CancelledError
6-
from contextlib import suppress
76
import datetime
87
from enum import Enum
98
import logging
@@ -20,12 +19,15 @@
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
)
2829
from insteonplm.messagecallback import MessageCallback
30+
from insteonplm.messages.allLinkComplete import AllLinkComplete
2931
from insteonplm.messages.extendedReceive import ExtendedReceive
3032
from insteonplm.messages.standardReceive import StandardReceive
3133
from insteonplm.messages.extendedSend import ExtendedSend
@@ -41,6 +43,10 @@
4143
ALDB_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+
4450
def 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-
813969
class ALDB(object):
814970
"""Represents a device All-Link database."""
815971

insteonplm/devices/switchedLightingControl.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,6 @@
66
OnOffKeypadA,
77
OnOffKeypad,
88
OnOffKeypadLed)
9-
from insteonplm.messages.userdata import Userdata
10-
from insteonplm.messages.extendedSend import ExtendedSend
11-
from insteonplm.constants import COMMAND_EXTENDED_GET_SET_0X2E_0X00
129

1310

1411
class SwitchedLightingControl(Device):

0 commit comments

Comments
 (0)