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 AD2USB devices.
  3. """
  4. import ad2usb
  5. import time
  6. import traceback
  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 AD2USB/AD2SERIAL 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 AD2USB/AD2SERIAL 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)