| @@ -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.") | |||