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.

149 lines
3.9 KiB

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