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.

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