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.

561 lines
16 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 socket
  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. self._id = ''
  26. self._buffer = ''
  27. self._interface = None
  28. self._device = None
  29. self._running = False
  30. self._read_thread = Device.ReadThread(self) # NOTE: not sure this is going to work..
  31. def __del__(self):
  32. pass
  33. @property
  34. def id(self):
  35. return self._id
  36. @id.setter
  37. def id(self, value):
  38. self._id = value
  39. def is_reader_alive(self):
  40. """
  41. Indicates whether or not the reader thread is alive.
  42. """
  43. return self._read_thread.is_alive()
  44. def stop_reader(self):
  45. """
  46. Stops the reader thread.
  47. """
  48. self._read_thread.stop()
  49. class ReadThread(threading.Thread):
  50. """
  51. Reader thread which processes messages from the device.
  52. """
  53. READ_TIMEOUT = 10
  54. def __init__(self, device):
  55. """
  56. Constructor
  57. """
  58. threading.Thread.__init__(self)
  59. self._device = device
  60. self._running = False
  61. def stop(self):
  62. """
  63. Stops the running thread.
  64. """
  65. self._running = False
  66. def run(self):
  67. """
  68. The actual read process.
  69. """
  70. self._running = True
  71. while self._running:
  72. try:
  73. self._device.read_line(timeout=self.READ_TIMEOUT)
  74. except util.TimeoutError, err:
  75. pass
  76. time.sleep(0.01)
  77. class USBDevice(Device):
  78. """
  79. AD2USB device exposed with PyFTDI's interface.
  80. """
  81. # Constants
  82. FTDI_VENDOR_ID = 0x0403
  83. FTDI_PRODUCT_ID = 0x6001
  84. BAUDRATE = 115200
  85. @staticmethod
  86. def find_all():
  87. """
  88. Returns all FTDI devices matching our vendor and product IDs.
  89. """
  90. devices = []
  91. try:
  92. devices = Ftdi.find_all([(USBDevice.FTDI_VENDOR_ID, USBDevice.FTDI_PRODUCT_ID)], nocache=True)
  93. except (usb.core.USBError, FtdiError), err:
  94. raise util.CommError('Error enumerating AD2USB devices: {0}'.format(str(err)))
  95. return devices
  96. def __init__(self, vid=FTDI_VENDOR_ID, pid=FTDI_PRODUCT_ID, serial=None, description=None, interface=0):
  97. """
  98. Constructor
  99. """
  100. Device.__init__(self)
  101. self._device = Ftdi()
  102. self._interface = interface
  103. self._vendor_id = vid
  104. self._product_id = pid
  105. self._serial_number = serial
  106. self._description = description
  107. def open(self, baudrate=BAUDRATE, interface=None, index=0, no_reader_thread=False):
  108. """
  109. Opens the device.
  110. """
  111. # Set up defaults
  112. if baudrate is None:
  113. baudrate = USBDevice.BAUDRATE
  114. if self._interface is None and interface is None:
  115. self._interface = 0
  116. if interface is not None:
  117. self._interface = interface
  118. if index is None:
  119. index = 0
  120. # Open the device and start up the thread.
  121. try:
  122. self._device.open(self._vendor_id,
  123. self._product_id,
  124. self._interface,
  125. index,
  126. self._serial_number,
  127. self._description)
  128. self._device.set_baudrate(baudrate)
  129. self._id = 'USB {0}:{1}'.format(self._device.usb_dev.bus, self._device.usb_dev.address)
  130. except (usb.core.USBError, FtdiError), err:
  131. self.on_close()
  132. raise util.NoDeviceError('Error opening AD2USB device: {0}'.format(str(err)))
  133. else:
  134. self._running = True
  135. if not no_reader_thread:
  136. self._read_thread.start()
  137. self.on_open((self._serial_number, self._description))
  138. def close(self):
  139. """
  140. Closes the device.
  141. """
  142. try:
  143. self._running = False
  144. self._read_thread.stop()
  145. self._device.close()
  146. # HACK: Probably should fork pyftdi and make this call in .close().
  147. self._device.usb_dev.attach_kernel_driver(self._interface)
  148. except (FtdiError, usb.core.USBError):
  149. pass
  150. self.on_close()
  151. def write(self, data):
  152. """
  153. Writes data to the device.
  154. """
  155. try:
  156. self._device.write_data(data)
  157. self.on_write(data)
  158. except FtdiError, err:
  159. raise util.CommError('Error writing to AD2USB device.')
  160. def read(self):
  161. """
  162. Reads a single character from the device.
  163. """
  164. return self._device.read_data(1)
  165. def read_line(self, timeout=0.0):
  166. """
  167. Reads a line from the device.
  168. """
  169. def timeout_event():
  170. timeout_event.reading = False
  171. timeout_event.reading = True
  172. got_line = False
  173. ret = None
  174. timer = None
  175. if timeout > 0:
  176. timer = threading.Timer(timeout, timeout_event)
  177. timer.start()
  178. try:
  179. while timeout_event.reading:
  180. buf = self._device.read_data(1)
  181. if buf != '':
  182. self._buffer += buf
  183. if buf == "\n":
  184. if len(self._buffer) > 1:
  185. if self._buffer[-2] == "\r":
  186. self._buffer = self._buffer[:-2]
  187. # ignore if we just got \r\n with nothing else in the buffer.
  188. if len(self._buffer) != 0:
  189. got_line = True
  190. break
  191. else:
  192. self._buffer = self._buffer[:-1]
  193. time.sleep(0.001)
  194. except (usb.core.USBError, FtdiError), err:
  195. timer.cancel()
  196. raise util.CommError('Error reading from AD2USB device: {0}'.format(str(err)))
  197. else:
  198. if got_line:
  199. ret = self._buffer
  200. self._buffer = ''
  201. self.on_read(ret)
  202. if timer:
  203. if timer.is_alive():
  204. timer.cancel()
  205. else:
  206. raise util.TimeoutError('Timeout while waiting for line terminator.')
  207. return ret
  208. class SerialDevice(Device):
  209. """
  210. AD2USB or AD2SERIAL device exposed with the pyserial interface.
  211. """
  212. # Constants
  213. BAUDRATE = 19200
  214. @staticmethod
  215. def find_all(pattern=None):
  216. """
  217. Returns all serial ports present.
  218. """
  219. devices = []
  220. try:
  221. if pattern:
  222. devices = serial.tools.list_ports.grep(pattern)
  223. else:
  224. devices = serial.tools.list_ports.comports()
  225. except Exception, err:
  226. raise util.CommError('Error enumerating AD2SERIAL devices: {0}'.format(str(err)))
  227. return devices
  228. def __init__(self, interface=None):
  229. """
  230. Constructor
  231. """
  232. Device.__init__(self)
  233. self._interface = interface
  234. self._id = interface
  235. self._device = serial.Serial(timeout=0, writeTimeout=0) # Timeout = non-blocking to match pyftdi.
  236. def open(self, baudrate=BAUDRATE, interface=None, index=None, no_reader_thread=False):
  237. """
  238. Opens the device.
  239. """
  240. # Set up the defaults
  241. if baudrate is None:
  242. baudrate = SerialDevice.BAUDRATE
  243. if self._interface is None and interface is None:
  244. raise util.NoDeviceError('No AD2SERIAL device interface specified.')
  245. if interface is not None:
  246. self._interface = interface
  247. self._device.port = self._interface
  248. # Open the device and start up the reader thread.
  249. try:
  250. self._device.open()
  251. self._device.baudrate = baudrate # NOTE: Setting the baudrate before opening the
  252. # port caused issues with Moschip 7840/7820
  253. # USB Serial Driver converter. (mos7840)
  254. #
  255. # Moving it to this point seems to resolve
  256. # all issues with it.
  257. except (serial.SerialException, ValueError), err:
  258. self.on_close()
  259. raise util.NoDeviceError('Error opening AD2SERIAL device on port {0}.'.format(interface))
  260. else:
  261. self._running = True
  262. self.on_open(('N/A', "AD2SERIAL"))
  263. if not no_reader_thread:
  264. self._read_thread.start()
  265. def close(self):
  266. """
  267. Closes the device.
  268. """
  269. try:
  270. self._running = False
  271. self._read_thread.stop()
  272. self._device.close()
  273. except Exception, err:
  274. pass
  275. self.on_close()
  276. def write(self, data):
  277. """
  278. Writes data to the device.
  279. """
  280. try:
  281. self._device.write(data)
  282. except serial.SerialTimeoutException, err:
  283. pass
  284. except serial.SerialException, err:
  285. raise util.CommError('Error writing to serial device.')
  286. else:
  287. self.on_write(data)
  288. def read(self):
  289. """
  290. Reads a single character from the device.
  291. """
  292. return self._device.read(1)
  293. def read_line(self, timeout=0.0):
  294. """
  295. Reads a line from the device.
  296. """
  297. def timeout_event():
  298. timeout_event.reading = False
  299. timeout_event.reading = True
  300. got_line = False
  301. ret = None
  302. timer = None
  303. if timeout > 0:
  304. timer = threading.Timer(timeout, timeout_event)
  305. timer.start()
  306. try:
  307. while timeout_event.reading:
  308. buf = self._device.read(1)
  309. if buf != '' and buf != "\xff": # AD2SERIAL specifically apparently sends down \xFF on boot.
  310. self._buffer += buf
  311. if buf == "\n":
  312. if len(self._buffer) > 1:
  313. if self._buffer[-2] == "\r":
  314. self._buffer = self._buffer[:-2]
  315. # ignore if we just got \r\n with nothing else in the buffer.
  316. if len(self._buffer) != 0:
  317. got_line = True
  318. break
  319. else:
  320. self._buffer = self._buffer[:-1]
  321. time.sleep(0.001)
  322. except (OSError, serial.SerialException), err:
  323. timer.cancel()
  324. raise util.CommError('Error reading from AD2SERIAL device: {0}'.format(str(err)))
  325. else:
  326. if got_line:
  327. ret = self._buffer
  328. self._buffer = ''
  329. self.on_read(ret)
  330. if timer:
  331. if timer.is_alive():
  332. timer.cancel()
  333. else:
  334. raise util.TimeoutError('Timeout while waiting for line terminator.')
  335. return ret
  336. class SocketDevice(Device):
  337. """
  338. Device that supports communication with an AD2USB that is exposed via ser2sock or another
  339. Serial to IP interface.
  340. """
  341. def __init__(self, interface=("localhost", 10000)):
  342. """
  343. Constructor
  344. """
  345. Device.__init__(self)
  346. self._interface = interface
  347. self._host, self._port = interface
  348. def open(self, baudrate=None, interface=None, index=0, no_reader_thread=False):
  349. """
  350. Opens the device.
  351. """
  352. if interface is not None:
  353. self._interface = interface
  354. self._host, self._port = interface
  355. try:
  356. self._device = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  357. self._device.connect((self._host, self._port))
  358. self._id = '{0}:{1}'.format(self._host, self._port)
  359. except socket.error, err:
  360. self.on_close()
  361. raise util.NoDeviceError('Error opening AD2SOCKET device at {0}:{1}'.format(self._host, self._port))
  362. else:
  363. self._running = True
  364. self.on_open(('N/A', "AD2SOCKET"))
  365. if not no_reader_thread:
  366. self._read_thread.start()
  367. def close(self):
  368. """
  369. Closes the device.
  370. """
  371. self._running = False
  372. try:
  373. self._read_thread.stop()
  374. self._device.shutdown(socket.SHUT_RDWR) # Make sure that it closes immediately.
  375. self._device.close()
  376. except:
  377. pass
  378. self.on_close()
  379. def write(self, data):
  380. """
  381. Writes data to the device.
  382. """
  383. data_sent = self._device.send(data)
  384. if data_sent == 0:
  385. raise util.CommError('Error while sending data.')
  386. else:
  387. self.on_write(data)
  388. return data_sent
  389. def read(self):
  390. """
  391. Reads a single character from the device.
  392. """
  393. try:
  394. data = self._device.recv(1)
  395. except socket.error, err:
  396. raise util.CommError('Error while reading from device: {0}'.format(str(err)))
  397. return data
  398. def read_line(self, timeout=0.0):
  399. """
  400. Reads a line from the device.
  401. """
  402. def timeout_event():
  403. timeout_event.reading = False
  404. timeout_event.reading = True
  405. got_line = False
  406. ret = None
  407. timer = None
  408. if timeout > 0:
  409. timer = threading.Timer(timeout, timeout_event)
  410. timer.start()
  411. try:
  412. while timeout_event.reading:
  413. buf = self._device.recv(1)
  414. if buf != '':
  415. self._buffer += buf
  416. if buf == "\n":
  417. if len(self._buffer) > 1:
  418. if self._buffer[-2] == "\r":
  419. self._buffer = self._buffer[:-2]
  420. # ignore if we just got \r\n with nothing else in the buffer.
  421. if len(self._buffer) != 0:
  422. got_line = True
  423. break
  424. else:
  425. self._buffer = self._buffer[:-1]
  426. time.sleep(0.001)
  427. except socket.error, err:
  428. timer.cancel()
  429. raise util.CommError('Error reading from Socket device: {0}'.format(str(err)))
  430. else:
  431. if got_line:
  432. ret = self._buffer
  433. self._buffer = ''
  434. self.on_read(ret)
  435. if timer:
  436. if timer.is_alive():
  437. timer.cancel()
  438. else:
  439. raise util.TimeoutError('Timeout while waiting for line terminator.')
  440. return ret