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.

288 lines
8.3 KiB

  1. import usb.core
  2. import usb.util
  3. import time
  4. import threading
  5. import serial
  6. import serial.tools.list_ports
  7. import traceback
  8. from pyftdi.pyftdi.ftdi import *
  9. from pyftdi.pyftdi.usbtools import *
  10. from . import util
  11. from .event import event
  12. class Device(object):
  13. on_open = event.Event('Called when the device has been opened')
  14. on_close = event.Event('Called when the device has been closed')
  15. on_read = event.Event('Called when a line has been read from the device')
  16. on_write = event.Event('Called when data has been written to the device')
  17. def __init__(self):
  18. pass
  19. def __del__(self):
  20. pass
  21. class ReadThread(threading.Thread):
  22. def __init__(self, device):
  23. threading.Thread.__init__(self)
  24. self._device = device
  25. self._running = False
  26. def stop(self):
  27. self._running = False
  28. def run(self):
  29. self._running = True
  30. while self._running:
  31. try:
  32. self._device.read_line(timeout=10)
  33. except util.CommError, err:
  34. traceback.print_exc(err)
  35. except util.TimeoutError, err:
  36. pass
  37. time.sleep(0.01)
  38. class USBDevice(Device):
  39. FTDI_VENDOR_ID = 0x0403
  40. FTDI_PRODUCT_ID = 0x6001
  41. BAUDRATE = 115200
  42. @staticmethod
  43. def find_all():
  44. devices = []
  45. try:
  46. devices = Ftdi.find_all([(USBDevice.FTDI_VENDOR_ID, USBDevice.FTDI_PRODUCT_ID)], nocache=True)
  47. except (usb.core.USBError, FtdiError), err:
  48. raise util.CommError('Error enumerating AD2USB devices: {0}'.format(str(err)))
  49. return devices
  50. def __init__(self, vid=FTDI_VENDOR_ID, pid=FTDI_PRODUCT_ID, serial=None, description=None, interface=0):
  51. Device.__init__(self)
  52. self._vendor_id = vid
  53. self._product_id = pid
  54. self._serial_number = serial
  55. self._description = description
  56. self._buffer = ''
  57. self._device = Ftdi()
  58. self._running = False
  59. self._interface = interface
  60. self._read_thread = Device.ReadThread(self)
  61. def open(self, baudrate=BAUDRATE, interface=None, index=0):
  62. self._running = True
  63. if baudrate is None:
  64. baudrate = USBDevice.BAUDRATE
  65. if self._interface is None and interface is None:
  66. self._interface = 0
  67. if interface is not None:
  68. self._interface = interface
  69. if index is None:
  70. index = 0
  71. try:
  72. self._device.open(self._vendor_id,
  73. self._product_id,
  74. self._interface,
  75. index,
  76. self._serial_number,
  77. self._description)
  78. self._device.set_baudrate(baudrate)
  79. except (usb.core.USBError, FtdiError), err:
  80. self.on_close()
  81. raise util.CommError('Error opening AD2USB device: {0}'.format(str(err)))
  82. else:
  83. self._read_thread.start()
  84. self.on_open((self._serial_number, self._description))
  85. def close(self):
  86. try:
  87. self._running = False
  88. self._read_thread.stop()
  89. self._device.close()
  90. # HACK: Probably should fork pyftdi and make this call in .close().
  91. self._device.usb_dev.attach_kernel_driver(self._interface)
  92. except (FtdiError, usb.core.USBError):
  93. pass
  94. self.on_close()
  95. def close_reader(self):
  96. self._read_thread.stop()
  97. def write(self, data):
  98. self._device.write_data(data)
  99. self.on_write(data)
  100. def read(self):
  101. return self._device.read_data(1)
  102. def read_line(self, timeout=0.0):
  103. start_time = time.time()
  104. got_line = False
  105. ret = None
  106. try:
  107. while self._running:
  108. buf = self._device.read_data(1)
  109. if buf != '':
  110. self._buffer += buf
  111. if buf == "\n":
  112. if len(self._buffer) > 1:
  113. if self._buffer[-2] == "\r":
  114. self._buffer = self._buffer[:-2]
  115. # ignore if we just got \r\n with nothing else in the buffer.
  116. if len(self._buffer) != 0:
  117. got_line = True
  118. break
  119. else:
  120. self._buffer = self._buffer[:-1]
  121. if timeout > 0 and time.time() - start_time > timeout:
  122. raise util.TimeoutError('Timeout while waiting for line terminator.')
  123. except (usb.core.USBError, FtdiError), err:
  124. raise util.CommError('Error reading from AD2USB device: {0}'.format(str(err)))
  125. else:
  126. if got_line:
  127. ret = self._buffer
  128. self._buffer = ''
  129. self.on_read(ret)
  130. return ret
  131. class SerialDevice(Device):
  132. BAUDRATE = 19200
  133. @staticmethod
  134. def find_all():
  135. devices = []
  136. try:
  137. devices = serial.tools.list_ports.comports()
  138. except Exception, err:
  139. raise util.CommError('Error enumerating AD2SERIAL devices: {0}'.format(str(err)))
  140. return devices
  141. def __init__(self, interface=None):
  142. Device.__init__(self)
  143. self._device = serial.Serial(timeout=0) # Timeout = non-blocking to match pyftdi.
  144. self._read_thread = Device.ReadThread(self)
  145. self._buffer = ''
  146. self._running = False
  147. self._interface = interface
  148. def __del__(self):
  149. pass
  150. def open(self, baudrate=BAUDRATE, interface=None, index=None):
  151. if baudrate is None:
  152. baudrate = SerialDevice.BAUDRATE
  153. if self._interface is None and interface is None:
  154. raise util.NoDeviceError('No AD2SERIAL device interface specified.')
  155. if interface is not None:
  156. self._interface = interface
  157. self._device.baudrate = baudrate
  158. self._device.port = self._interface
  159. try:
  160. self._device.open()
  161. self._running = True
  162. except (serial.SerialException, ValueError), err:
  163. self.on_close()
  164. raise util.NoDeviceError('Error opening AD2SERIAL device on port {0}.'.format(interface))
  165. else:
  166. self.on_open((None, "AD2SERIAL")) # TODO: Fixme.
  167. self._read_thread.start()
  168. def close(self):
  169. try:
  170. self._running = False
  171. self._read_thread.stop()
  172. self._device.close()
  173. except Exception, err:
  174. pass
  175. self.on_close()
  176. def close_reader(self):
  177. self._read_thread.stop()
  178. def write(self, data):
  179. try:
  180. self._device.write(data)
  181. except serial.SerialTimeoutException, err:
  182. pass
  183. else:
  184. self.on_write(data)
  185. def read(self):
  186. return self._device.read(1)
  187. def read_line(self, timeout=0.0):
  188. start_time = time.time()
  189. got_line = False
  190. ret = None
  191. try:
  192. while self._running:
  193. buf = self._device.read(1)
  194. if buf != '' and buf != "\xff": # WTF is this \xff and why is it in my buffer?!
  195. self._buffer += buf
  196. if buf == "\n":
  197. if len(self._buffer) > 1:
  198. if self._buffer[-2] == "\r":
  199. self._buffer = self._buffer[:-2]
  200. # ignore if we just got \r\n with nothing else in the buffer.
  201. if len(self._buffer) != 0:
  202. got_line = True
  203. break
  204. else:
  205. self._buffer = self._buffer[:-1]
  206. if timeout > 0 and time.time() - start_time > timeout:
  207. raise util.TimeoutError('Timeout while waiting for line terminator.')
  208. except (OSError, serial.SerialException), err:
  209. raise util.CommError('Error reading from AD2SERIAL device: {0}'.format(str(err)))
  210. else:
  211. if got_line:
  212. ret = self._buffer
  213. self._buffer = ''
  214. self.on_read(ret)
  215. return ret