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.

369 lines
9.8 KiB

  1. """
  2. Contains different types of devices belonging to the AD2USB family.
  3. """
  4. import usb.core
  5. import usb.util
  6. import time
  7. import threading
  8. import serial
  9. import serial.tools.list_ports
  10. import traceback
  11. from pyftdi.pyftdi.ftdi import *
  12. from pyftdi.pyftdi.usbtools import *
  13. from . import util
  14. from .event import event
  15. class Device(object):
  16. """
  17. Generic parent device to all AD2USB products.
  18. """
  19. on_open = event.Event('Called when the device has been opened')
  20. on_close = event.Event('Called when the device has been closed')
  21. on_read = event.Event('Called when a line has been read from the device')
  22. on_write = event.Event('Called when data has been written to the device')
  23. def __init__(self):
  24. pass
  25. def __del__(self):
  26. pass
  27. class ReadThread(threading.Thread):
  28. """
  29. Reader thread which processes messages from the device.
  30. """
  31. def __init__(self, device):
  32. """
  33. Constructor
  34. """
  35. threading.Thread.__init__(self)
  36. self._device = device
  37. self._running = False
  38. def stop(self):
  39. """
  40. Stops the running thread.
  41. """
  42. self._running = False
  43. def run(self):
  44. """
  45. The actual read process.
  46. """
  47. self._running = True
  48. while self._running:
  49. try:
  50. self._device.read_line(timeout=10)
  51. except util.CommError, err:
  52. traceback.print_exc(err)
  53. except util.TimeoutError, err:
  54. pass
  55. time.sleep(0.01)
  56. class USBDevice(Device):
  57. """
  58. AD2USB device exposed with PyFTDI's interface.
  59. """
  60. FTDI_VENDOR_ID = 0x0403
  61. FTDI_PRODUCT_ID = 0x6001
  62. BAUDRATE = 115200
  63. @staticmethod
  64. def find_all():
  65. """
  66. Returns all FTDI devices matching our vendor and product IDs.
  67. """
  68. devices = []
  69. try:
  70. devices = Ftdi.find_all([(USBDevice.FTDI_VENDOR_ID, USBDevice.FTDI_PRODUCT_ID)], nocache=True)
  71. except (usb.core.USBError, FtdiError), err:
  72. raise util.CommError('Error enumerating AD2USB devices: {0}'.format(str(err)))
  73. return devices
  74. def __init__(self, vid=FTDI_VENDOR_ID, pid=FTDI_PRODUCT_ID, serial=None, description=None, interface=0):
  75. """
  76. Constructor
  77. """
  78. Device.__init__(self)
  79. self._vendor_id = vid
  80. self._product_id = pid
  81. self._serial_number = serial
  82. self._description = description
  83. self._buffer = ''
  84. self._device = Ftdi()
  85. self._running = False
  86. self._interface = interface
  87. self._read_thread = Device.ReadThread(self)
  88. def open(self, baudrate=BAUDRATE, interface=None, index=0):
  89. """
  90. Opens the device.
  91. """
  92. self._running = True
  93. if baudrate is None:
  94. baudrate = USBDevice.BAUDRATE
  95. if self._interface is None and interface is None:
  96. self._interface = 0
  97. if interface is not None:
  98. self._interface = interface
  99. if index is None:
  100. index = 0
  101. try:
  102. self._device.open(self._vendor_id,
  103. self._product_id,
  104. self._interface,
  105. index,
  106. self._serial_number,
  107. self._description)
  108. self._device.set_baudrate(baudrate)
  109. except (usb.core.USBError, FtdiError), err:
  110. self.on_close()
  111. raise util.CommError('Error opening AD2USB device: {0}'.format(str(err)))
  112. else:
  113. self._read_thread.start()
  114. self.on_open((self._serial_number, self._description))
  115. def close(self):
  116. """
  117. Closes the device.
  118. """
  119. try:
  120. self._running = False
  121. self._read_thread.stop()
  122. self._device.close()
  123. # HACK: Probably should fork pyftdi and make this call in .close().
  124. self._device.usb_dev.attach_kernel_driver(self._interface)
  125. except (FtdiError, usb.core.USBError):
  126. pass
  127. self.on_close()
  128. def close_reader(self):
  129. """
  130. Stops the reader thread.
  131. """
  132. self._read_thread.stop()
  133. def write(self, data):
  134. """
  135. Writes data to the device.
  136. """
  137. self._device.write_data(data)
  138. self.on_write(data)
  139. def read(self):
  140. """
  141. Reads a single character from the device.
  142. """
  143. return self._device.read_data(1)
  144. def read_line(self, timeout=0.0):
  145. """
  146. Reads a line from the device.
  147. """
  148. start_time = time.time()
  149. got_line = False
  150. ret = None
  151. try:
  152. while self._running:
  153. buf = self._device.read_data(1)
  154. if buf != '':
  155. self._buffer += buf
  156. if buf == "\n":
  157. if len(self._buffer) > 1:
  158. if self._buffer[-2] == "\r":
  159. self._buffer = self._buffer[:-2]
  160. # ignore if we just got \r\n with nothing else in the buffer.
  161. if len(self._buffer) != 0:
  162. got_line = True
  163. break
  164. else:
  165. self._buffer = self._buffer[:-1]
  166. if timeout > 0 and time.time() - start_time > timeout:
  167. raise util.TimeoutError('Timeout while waiting for line terminator.')
  168. except (usb.core.USBError, FtdiError), err:
  169. raise util.CommError('Error reading from AD2USB device: {0}'.format(str(err)))
  170. else:
  171. if got_line:
  172. ret = self._buffer
  173. self._buffer = ''
  174. self.on_read(ret)
  175. return ret
  176. class SerialDevice(Device):
  177. """
  178. AD2USB or AD2SERIAL device exposed with the pyserial interface.
  179. """
  180. BAUDRATE = 19200
  181. @staticmethod
  182. def find_all():
  183. """
  184. Returns all serial ports present.
  185. """
  186. devices = []
  187. try:
  188. devices = serial.tools.list_ports.comports()
  189. except Exception, err:
  190. raise util.CommError('Error enumerating AD2SERIAL devices: {0}'.format(str(err)))
  191. return devices
  192. def __init__(self, interface=None):
  193. """
  194. Constructor
  195. """
  196. Device.__init__(self)
  197. self._device = serial.Serial(timeout=0) # Timeout = non-blocking to match pyftdi.
  198. self._read_thread = Device.ReadThread(self)
  199. self._buffer = ''
  200. self._running = False
  201. self._interface = interface
  202. def __del__(self):
  203. """
  204. Destructor
  205. """
  206. pass
  207. def open(self, baudrate=BAUDRATE, interface=None, index=None):
  208. """
  209. Opens the device.
  210. """
  211. if baudrate is None:
  212. baudrate = SerialDevice.BAUDRATE
  213. if self._interface is None and interface is None:
  214. raise util.NoDeviceError('No AD2SERIAL device interface specified.')
  215. if interface is not None:
  216. self._interface = interface
  217. self._device.baudrate = baudrate
  218. self._device.port = self._interface
  219. try:
  220. self._device.open()
  221. self._running = True
  222. except (serial.SerialException, ValueError), err:
  223. self.on_close()
  224. raise util.NoDeviceError('Error opening AD2SERIAL device on port {0}.'.format(interface))
  225. else:
  226. self.on_open((None, "AD2SERIAL")) # TODO: Fixme.
  227. self._read_thread.start()
  228. def close(self):
  229. """
  230. Closes the device.
  231. """
  232. try:
  233. self._running = False
  234. self._read_thread.stop()
  235. self._device.close()
  236. except Exception, err:
  237. pass
  238. self.on_close()
  239. def close_reader(self):
  240. """
  241. Stops the reader thread.
  242. """
  243. self._read_thread.stop()
  244. def write(self, data):
  245. """
  246. Writes data to the device.
  247. """
  248. try:
  249. self._device.write(data)
  250. except serial.SerialTimeoutException, err:
  251. pass
  252. else:
  253. self.on_write(data)
  254. def read(self):
  255. """
  256. Reads a single character from the device.
  257. """
  258. return self._device.read(1)
  259. def read_line(self, timeout=0.0):
  260. """
  261. Reads a line from the device.
  262. """
  263. start_time = time.time()
  264. got_line = False
  265. ret = None
  266. try:
  267. while self._running:
  268. buf = self._device.read(1)
  269. if buf != '' and buf != "\xff": # WTF is this \xff and why is it in my buffer?!
  270. self._buffer += buf
  271. if buf == "\n":
  272. if len(self._buffer) > 1:
  273. if self._buffer[-2] == "\r":
  274. self._buffer = self._buffer[:-2]
  275. # ignore if we just got \r\n with nothing else in the buffer.
  276. if len(self._buffer) != 0:
  277. got_line = True
  278. break
  279. else:
  280. self._buffer = self._buffer[:-1]
  281. if timeout > 0 and time.time() - start_time > timeout:
  282. raise util.TimeoutError('Timeout while waiting for line terminator.')
  283. except (OSError, serial.SerialException), err:
  284. raise util.CommError('Error reading from AD2SERIAL device: {0}'.format(str(err)))
  285. else:
  286. if got_line:
  287. ret = self._buffer
  288. self._buffer = ''
  289. self.on_read(ret)
  290. return ret