|
|
@@ -1,204 +0,0 @@ |
|
|
|
""" |
|
|
|
Provides utility classes for the `AlarmDecoder`_ (AD2) devices. |
|
|
|
|
|
|
|
.. _AlarmDecoder: http://www.alarmdecoder.com |
|
|
|
|
|
|
|
.. moduleauthor:: Scott Petersen <scott@nutech.com> |
|
|
|
""" |
|
|
|
|
|
|
|
import time |
|
|
|
import threading |
|
|
|
|
|
|
|
|
|
|
|
class NoDeviceError(Exception): |
|
|
|
""" |
|
|
|
No devices found. |
|
|
|
""" |
|
|
|
pass |
|
|
|
|
|
|
|
|
|
|
|
class CommError(Exception): |
|
|
|
""" |
|
|
|
There was an error communicating with the device. |
|
|
|
""" |
|
|
|
pass |
|
|
|
|
|
|
|
|
|
|
|
class TimeoutError(Exception): |
|
|
|
""" |
|
|
|
There was a timeout while trying to communicate with the device. |
|
|
|
""" |
|
|
|
pass |
|
|
|
|
|
|
|
|
|
|
|
class InvalidMessageError(Exception): |
|
|
|
""" |
|
|
|
The format of the panel message was invalid. |
|
|
|
""" |
|
|
|
pass |
|
|
|
|
|
|
|
|
|
|
|
class UploadError(Exception): |
|
|
|
""" |
|
|
|
Generic firmware upload error. |
|
|
|
""" |
|
|
|
pass |
|
|
|
|
|
|
|
|
|
|
|
class UploadChecksumError(UploadError): |
|
|
|
""" |
|
|
|
The firmware upload failed due to a checksum error. |
|
|
|
""" |
|
|
|
pass |
|
|
|
|
|
|
|
|
|
|
|
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 |
|
|
|
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, 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, err: |
|
|
|
retry -= 1 |
|
|
|
else: |
|
|
|
retry = 0 |
|
|
|
found_loader = True |
|
|
|
|
|
|
|
# And finally do the upload. |
|
|
|
if found_loader: |
|
|
|
try: |
|
|
|
do_upload() |
|
|
|
except UploadError, 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.") |