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.

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