Source code for alarmdecoder.util

"""
Provides utility classes for the `AlarmDecoder`_ (AD2) devices.

.. _AlarmDecoder: http://www.alarmdecoder.com

.. moduleauthor:: Scott Petersen <scott@nutech.com>
"""

import time
import threading
from io import open


[docs]class NoDeviceError(Exception): """ No devices found. """ pass
[docs]class CommError(Exception): """ There was an error communicating with the device. """ pass
[docs]class TimeoutError(Exception): """ There was a timeout while trying to communicate with the device. """ pass
[docs]class InvalidMessageError(Exception): """ The format of the panel message was invalid. """ pass
[docs]class UploadError(Exception): """ Generic firmware upload error. """ pass
[docs]class UploadChecksumError(UploadError): """ The firmware upload failed due to a checksum error. """ pass
[docs]class Firmware(object): """ Represents firmware for the `AlarmDecoder`_ devices. """ # Constants STAGE_START = 0 STAGE_WAITING = 1 STAGE_BOOT = 2 STAGE_LOAD = 3 STAGE_UPLOADING = 4 STAGE_DONE = 5 STAGE_ERROR = 98 STAGE_DEBUG = 99 # FIXME: Rewrite this monstrosity. @staticmethod
[docs] def upload(dev, filename, progress_callback=None, debug=False): """ Uploads firmware to an `AlarmDecoder`_ device. :param filename: firmware filename :type filename: string :param progress_callback: callback function used to report progress :type progress_callback: function :raises: :py:class:`~alarmdecoder.util.NoDeviceError`, :py:class:`~alarmdecoder.util.TimeoutError` """ def do_upload(): """ Perform the actual firmware upload to the device. """ with open(filename) as upload_file: line_cnt = 0 for line in upload_file: line_cnt += 1 line = line.rstrip() if line[0] == ':': dev.write(line + "\r") response = dev.read_line(timeout=5.0, purge_buffer=True) if debug: stage_callback(Firmware.STAGE_DEBUG, data="line={0} - line={1} response={2}".format(line_cnt, line, response)); if '!ce' in response: raise UploadChecksumError("Checksum error on line " + str(line_cnt) + " of " + filename); elif '!no' in response: raise UploadError("Incorrect data sent to bootloader.") elif '!ok' in response: break else: if progress_callback is not None: progress_callback(Firmware.STAGE_UPLOADING) time.sleep(0.0) def read_until(pattern, timeout=0.0): """ Read characters until a specific pattern is found or the timeout is hit. """ def timeout_event(): """Handles the read timeout event.""" timeout_event.reading = False timeout_event.reading = True timer = None if timeout > 0: timer = threading.Timer(timeout, timeout_event) timer.start() position = 0 dev.purge() while timeout_event.reading: try: char = dev.read() if char is not None and char != '': if char == pattern[position]: position = position + 1 if position == len(pattern): break else: position = 0 except Exception as err: pass if timer: if timer.is_alive(): timer.cancel() else: raise TimeoutError('Timeout while waiting for line terminator.') def stage_callback(stage, **kwargs): """Callback to update progress for the specified stage.""" if progress_callback is not None: progress_callback(stage, **kwargs) if dev is None: raise NoDeviceError('No device specified for firmware upload.') stage_callback(Firmware.STAGE_START) if dev.is_reader_alive(): # Close the reader thread and wait for it to die, otherwise # it interferes with our reading. dev.stop_reader() while dev._read_thread.is_alive(): stage_callback(Firmware.STAGE_WAITING) time.sleep(0.5) # Reboot the device and wait for the boot loader. retry = 3 found_loader = False while retry > 0: try: stage_callback(Firmware.STAGE_BOOT) dev.write("=") read_until('!boot', timeout=15.0) # Get ourselves into the boot loader and wait for indication # that it's ready for the firmware upload. stage_callback(Firmware.STAGE_LOAD) dev.write("=") read_until('!load', timeout=15.0) except TimeoutError as err: retry -= 1 else: retry = 0 found_loader = True # And finally do the upload. if found_loader: try: do_upload() except UploadError as err: stage_callback(Firmware.STAGE_ERROR, error=str(err)) else: stage_callback(Firmware.STAGE_DONE) else: stage_callback(Firmware.STAGE_ERROR, error="Error entering bootloader.")