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.

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