A clone of: https://github.com/nutechsoftware/alarmdecoder This is requires as they dropped support for older firmware releases w/o building in backward compatibility code, and they had previously hardcoded pyserial to a python2 only version.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

148 lines
3.9 KiB

  1. """
  2. Provides utility classes for the Alarm Decoder (AD2) devices.
  3. .. moduleauthor:: Scott Petersen <scott@nutech.com>
  4. """
  5. import time
  6. import threading
  7. class NoDeviceError(Exception):
  8. """
  9. No devices found.
  10. """
  11. pass
  12. class CommError(Exception):
  13. """
  14. There was an error communicating with the device.
  15. """
  16. pass
  17. class TimeoutError(Exception):
  18. """
  19. There was a timeout while trying to communicate with the device.
  20. """
  21. pass
  22. class InvalidMessageError(Exception):
  23. """
  24. The format of the panel message was invalid.
  25. """
  26. pass
  27. class Firmware(object):
  28. """
  29. Represents firmware for the Alarm Decoder devices.
  30. """
  31. # Constants
  32. STAGE_START = 0
  33. STAGE_WAITING = 1
  34. STAGE_BOOT = 2
  35. STAGE_LOAD = 3
  36. STAGE_UPLOADING = 4
  37. STAGE_DONE = 5
  38. @staticmethod
  39. def upload(dev, filename, progress_callback=None):
  40. """
  41. Uploads firmware to an Alarm Decoder device.
  42. :param filename: The firmware filename
  43. :type filename: str
  44. :param progress_callback: Callback function used to report progress.
  45. :type progress_callback: function
  46. :raises: NoDeviceError, TimeoutError
  47. """
  48. def do_upload():
  49. """
  50. Perform the actual firmware upload to the device.
  51. """
  52. with open(filename) as f:
  53. for line in f:
  54. line = line.rstrip()
  55. if line[0] == ':':
  56. dev.write(line + "\r")
  57. res = dev.read_line(timeout=10.0)
  58. if progress_callback is not None:
  59. progress_callback(Firmware.STAGE_UPLOADING)
  60. time.sleep(0.0)
  61. def read_until(pattern, timeout=0.0):
  62. """
  63. Read characters until a specific pattern is found or the timeout is hit.
  64. """
  65. def timeout_event():
  66. timeout_event.reading = False
  67. timeout_event.reading = True
  68. timer = None
  69. if timeout > 0:
  70. timer = threading.Timer(timeout, timeout_event)
  71. timer.start()
  72. buf = ''
  73. position = 0
  74. while timeout_event.reading:
  75. try:
  76. char = dev.read()
  77. if char is not None and char != '':
  78. if char == pattern[position]:
  79. position = position + 1
  80. if position == len(pattern):
  81. break
  82. else:
  83. position = 0
  84. except Exception, err:
  85. pass
  86. if timer:
  87. if timer.is_alive():
  88. timer.cancel()
  89. else:
  90. raise TimeoutError('Timeout while waiting for line terminator.')
  91. def stage_callback(stage):
  92. if progress_callback is not None:
  93. progress_callback(stage)
  94. if dev is None:
  95. raise NoDeviceError('No device specified for firmware upload.')
  96. stage_callback(Firmware.STAGE_START)
  97. if dev.is_reader_alive():
  98. # Close the reader thread and wait for it to die, otherwise
  99. # it interferes with our reading.
  100. dev.stop_reader()
  101. while dev._read_thread.is_alive():
  102. stage_callback(Firmware.STAGE_WAITING)
  103. time.sleep(1)
  104. time.sleep(2)
  105. # Reboot the device and wait for the boot loader.
  106. stage_callback(Firmware.STAGE_BOOT)
  107. dev.write("=")
  108. read_until('......', timeout=15.0)
  109. # Get ourselves into the boot loader and wait for indication
  110. # that it's ready for the firmware upload.
  111. stage_callback(Firmware.STAGE_LOAD)
  112. dev.write("=")
  113. read_until('!load', timeout=15.0)
  114. # And finally do the upload.
  115. do_upload()
  116. stage_callback(Firmware.STAGE_DONE)