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.

809 lines
22 KiB

  1. """
  2. Contains different types of devices belonging to the AD2USB family.
  3. .. moduleauthor:: Scott Petersen <scott@nutech.com>
  4. """
  5. import usb.core
  6. import usb.util
  7. import time
  8. import threading
  9. import serial
  10. import serial.tools.list_ports
  11. import socket
  12. from OpenSSL import SSL
  13. from pyftdi.pyftdi.ftdi import *
  14. from pyftdi.pyftdi.usbtools import *
  15. from . import util
  16. from .event import event
  17. class Device(object):
  18. """
  19. Generic parent device to all AD2USB products.
  20. """
  21. # Generic device events
  22. on_open = event.Event('Called when the device has been opened')
  23. on_close = event.Event('Called when the device has been closed')
  24. on_read = event.Event('Called when a line has been read from the device')
  25. on_write = event.Event('Called when data has been written to the device')
  26. def __init__(self):
  27. """
  28. Constructor
  29. """
  30. self._id = ''
  31. self._buffer = ''
  32. self._interface = None
  33. self._device = None
  34. self._running = False
  35. self._read_thread = Device.ReadThread(self)
  36. @property
  37. def id(self):
  38. """
  39. Retrieve the device ID.
  40. :returns: The identification string for the device.
  41. """
  42. return self._id
  43. @id.setter
  44. def id(self, value):
  45. """
  46. Sets the device ID.
  47. :param value: The device identification.
  48. :type value: str
  49. """
  50. self._id = value
  51. def is_reader_alive(self):
  52. """
  53. Indicates whether or not the reader thread is alive.
  54. :returns: Whether or not the reader thread is alive.
  55. """
  56. return self._read_thread.is_alive()
  57. def stop_reader(self):
  58. """
  59. Stops the reader thread.
  60. """
  61. self._read_thread.stop()
  62. class ReadThread(threading.Thread):
  63. """
  64. Reader thread which processes messages from the device.
  65. """
  66. READ_TIMEOUT = 10
  67. """Timeout for the reader thread."""
  68. def __init__(self, device):
  69. """
  70. Constructor
  71. :param device: The device used by the reader thread.
  72. :type device: devices.Device
  73. """
  74. threading.Thread.__init__(self)
  75. self._device = device
  76. self._running = False
  77. def stop(self):
  78. """
  79. Stops the running thread.
  80. """
  81. self._running = False
  82. def run(self):
  83. """
  84. The actual read process.
  85. """
  86. self._running = True
  87. while self._running:
  88. try:
  89. self._device.read_line(timeout=self.READ_TIMEOUT)
  90. except util.TimeoutError, err:
  91. pass
  92. except Exception, err:
  93. self._running = False
  94. raise err
  95. time.sleep(0.01)
  96. class USBDevice(Device):
  97. """
  98. AD2USB device exposed with PyFTDI's interface.
  99. """
  100. # Constants
  101. FTDI_VENDOR_ID = 0x0403
  102. """Vendor ID used to recognize AD2USB devices."""
  103. FTDI_PRODUCT_ID = 0x6001
  104. """Product ID used to recognize AD2USB devices."""
  105. BAUDRATE = 115200
  106. """Default baudrate for AD2USB devices."""
  107. @staticmethod
  108. def find_all():
  109. """
  110. Returns all FTDI devices matching our vendor and product IDs.
  111. :returns: list of devices
  112. :raises: util.CommError
  113. """
  114. devices = []
  115. try:
  116. devices = Ftdi.find_all([(USBDevice.FTDI_VENDOR_ID, USBDevice.FTDI_PRODUCT_ID)], nocache=True)
  117. except (usb.core.USBError, FtdiError), err:
  118. raise util.CommError('Error enumerating AD2USB devices: {0}'.format(str(err)), err)
  119. return devices
  120. def __init__(self, vid=FTDI_VENDOR_ID, pid=FTDI_PRODUCT_ID, serial=None, description=None, interface=0):
  121. """
  122. Constructor
  123. :param vid: Vendor ID
  124. :type vid: int
  125. :param pid: Product ID
  126. :type pid: int
  127. :param serial: The serial number
  128. :type serial: str
  129. :param description: Description of the device.
  130. :type description: str
  131. :param interface: The interface to use
  132. :type interface: int
  133. """
  134. Device.__init__(self)
  135. self._device = Ftdi()
  136. self._interface = interface
  137. self._vendor_id = vid
  138. self._product_id = pid
  139. self._serial_number = serial
  140. self._description = description
  141. def open(self, baudrate=BAUDRATE, interface=None, index=0, no_reader_thread=False):
  142. """
  143. Opens the device.
  144. :param baudrate: The baudrate to use.
  145. :type baudrate: int
  146. :param interface: The interface to use.
  147. :type interface: int
  148. :param no_reader_thread: Whether or not to automatically start the reader thread.
  149. :type no_reader_thread: bool
  150. :raises: util.NoDeviceError
  151. """
  152. # Set up defaults
  153. if baudrate is None:
  154. baudrate = USBDevice.BAUDRATE
  155. if self._interface is None and interface is None:
  156. self._interface = 0
  157. if interface is not None:
  158. self._interface = interface
  159. if index is None:
  160. index = 0
  161. # Open the device and start up the thread.
  162. try:
  163. self._device.open(self._vendor_id,
  164. self._product_id,
  165. self._interface,
  166. index,
  167. self._serial_number,
  168. self._description)
  169. self._device.set_baudrate(baudrate)
  170. self._id = 'USB {0}:{1}'.format(self._device.usb_dev.bus, self._device.usb_dev.address)
  171. except (usb.core.USBError, FtdiError), err:
  172. raise util.NoDeviceError('Error opening device: {0}'.format(str(err)), err)
  173. else:
  174. self._running = True
  175. if not no_reader_thread:
  176. self._read_thread.start()
  177. self.on_open((self._serial_number, self._description))
  178. def close(self):
  179. """
  180. Closes the device.
  181. """
  182. try:
  183. self._running = False
  184. self._read_thread.stop()
  185. self._device.close()
  186. # HACK: Probably should fork pyftdi and make this call in .close().
  187. self._device.usb_dev.attach_kernel_driver(self._interface)
  188. except:
  189. pass
  190. self.on_close()
  191. def write(self, data):
  192. """
  193. Writes data to the device.
  194. :param data: Data to write
  195. :type data: str
  196. :raises: util.CommError
  197. """
  198. try:
  199. self._device.write_data(data)
  200. self.on_write(data)
  201. except FtdiError, err:
  202. raise util.CommError('Error writing to device: {0}'.format(str(err)), err)
  203. def read(self):
  204. """
  205. Reads a single character from the device.
  206. :returns: The character read from the device.
  207. :raises: util.CommError
  208. """
  209. ret = None
  210. try:
  211. ret = self._device.read_data(1)
  212. except (usb.core.USBError, FtdiError), err:
  213. raise util.CommError('Error reading from device: {0}'.format(str(err)), err)
  214. return ret
  215. def read_line(self, timeout=0.0, purge_buffer=False):
  216. """
  217. Reads a line from the device.
  218. :param timeout: Read timeout
  219. :type timeout: float
  220. :param purge_buffer: Indicates whether to purge the buffer prior to reading.
  221. :type purge_buffer: bool
  222. :returns: The line that was read.
  223. :raises: util.CommError, util.TimeoutError
  224. """
  225. if purge_buffer:
  226. self._buffer = ''
  227. def timeout_event():
  228. timeout_event.reading = False
  229. timeout_event.reading = True
  230. got_line = False
  231. ret = None
  232. timer = None
  233. if timeout > 0:
  234. timer = threading.Timer(timeout, timeout_event)
  235. timer.start()
  236. try:
  237. while timeout_event.reading:
  238. buf = self._device.read_data(1)
  239. if buf != '':
  240. self._buffer += buf
  241. if buf == "\n":
  242. if len(self._buffer) > 1:
  243. if self._buffer[-2] == "\r":
  244. self._buffer = self._buffer[:-2]
  245. # ignore if we just got \r\n with nothing else in the buffer.
  246. if len(self._buffer) != 0:
  247. got_line = True
  248. break
  249. else:
  250. self._buffer = self._buffer[:-1]
  251. except (usb.core.USBError, FtdiError), err:
  252. timer.cancel()
  253. raise util.CommError('Error reading from device: {0}'.format(str(err)), err)
  254. else:
  255. if got_line:
  256. ret = self._buffer
  257. self._buffer = ''
  258. self.on_read(ret)
  259. if timer:
  260. if timer.is_alive():
  261. timer.cancel()
  262. else:
  263. raise util.TimeoutError('Timeout while waiting for line terminator.')
  264. return ret
  265. class SerialDevice(Device):
  266. """
  267. AD2USB or AD2SERIAL device exposed with the pyserial interface.
  268. """
  269. # Constants
  270. BAUDRATE = 19200
  271. """Default baudrate for Serial devices."""
  272. @staticmethod
  273. def find_all(pattern=None):
  274. """
  275. Returns all serial ports present.
  276. :param pattern: Pattern to search for when retrieving serial ports.
  277. :type pattern: str
  278. :returns: list of devices
  279. :raises: util.CommError
  280. """
  281. devices = []
  282. try:
  283. if pattern:
  284. devices = serial.tools.list_ports.grep(pattern)
  285. else:
  286. devices = serial.tools.list_ports.comports()
  287. except SerialException, err:
  288. raise util.CommError('Error enumerating serial devices: {0}'.format(str(err)), err)
  289. return devices
  290. def __init__(self, interface=None):
  291. """
  292. Constructor
  293. :param interface: The device to open.
  294. :type interface: str
  295. """
  296. Device.__init__(self)
  297. self._interface = interface
  298. self._id = interface
  299. self._device = serial.Serial(timeout=0, writeTimeout=0) # Timeout = non-blocking to match pyftdi.
  300. def open(self, baudrate=BAUDRATE, interface=None, index=None, no_reader_thread=False):
  301. """
  302. Opens the device.
  303. :param baudrate: The baudrate to use with the device.
  304. :type baudrate: int
  305. :param interface: The device to open.
  306. :type interface: str
  307. :param index: Unused.
  308. :type index: int
  309. :param no_reader_thread: Whether or not to automatically start the reader thread.
  310. :type no_reader_thread: bool
  311. :raises: util.NoDeviceError
  312. """
  313. # Set up the defaults
  314. if baudrate is None:
  315. baudrate = SerialDevice.BAUDRATE
  316. if self._interface is None and interface is None:
  317. raise util.NoDeviceError('No device interface specified.')
  318. if interface is not None:
  319. self._interface = interface
  320. self._device.port = self._interface
  321. # Open the device and start up the reader thread.
  322. try:
  323. self._device.open()
  324. self._device.baudrate = baudrate # NOTE: Setting the baudrate before opening the
  325. # port caused issues with Moschip 7840/7820
  326. # USB Serial Driver converter. (mos7840)
  327. #
  328. # Moving it to this point seems to resolve
  329. # all issues with it.
  330. except (serial.SerialException, ValueError), err:
  331. raise util.NoDeviceError('Error opening device on port {0}.'.format(interface), err)
  332. else:
  333. self._running = True
  334. self.on_open(('N/A', "AD2SERIAL"))
  335. if not no_reader_thread:
  336. self._read_thread.start()
  337. def close(self):
  338. """
  339. Closes the device.
  340. """
  341. try:
  342. self._running = False
  343. self._read_thread.stop()
  344. self._device.close()
  345. except:
  346. pass
  347. self.on_close()
  348. def write(self, data):
  349. """
  350. Writes data to the device.
  351. :param data: The data to write.
  352. :type data: str
  353. :raises: util.CommError
  354. """
  355. try:
  356. self._device.write(data)
  357. except serial.SerialTimeoutException, err:
  358. pass
  359. except serial.SerialException, err:
  360. raise util.CommError('Error writing to device.', err)
  361. else:
  362. self.on_write(data)
  363. def read(self):
  364. """
  365. Reads a single character from the device.
  366. :returns: The character read from the device.
  367. :raises: util.CommError
  368. """
  369. ret = None
  370. try:
  371. ret = self._device.read(1)
  372. except serial.SerialException, err:
  373. raise util.CommError('Error reading from device: {0}'.format(str(err)), err)
  374. return ret
  375. def read_line(self, timeout=0.0, purge_buffer=False):
  376. """
  377. Reads a line from the device.
  378. :param timeout: The read timeout.
  379. :type timeout: float
  380. :param purge_buffer: Indicates whether to purge the buffer prior to reading.
  381. :type purge_buffer: bool
  382. :returns: The line read.
  383. :raises: util.CommError, util.TimeoutError
  384. """
  385. def timeout_event():
  386. timeout_event.reading = False
  387. timeout_event.reading = True
  388. got_line = False
  389. ret = None
  390. timer = None
  391. if timeout > 0:
  392. timer = threading.Timer(timeout, timeout_event)
  393. timer.start()
  394. try:
  395. while timeout_event.reading:
  396. buf = self._device.read(1)
  397. if buf != '' and buf != "\xff": # AD2SERIAL specifically apparently sends down \xFF on boot.
  398. self._buffer += buf
  399. if buf == "\n":
  400. if len(self._buffer) > 1:
  401. if self._buffer[-2] == "\r":
  402. self._buffer = self._buffer[:-2]
  403. # ignore if we just got \r\n with nothing else in the buffer.
  404. if len(self._buffer) != 0:
  405. got_line = True
  406. break
  407. else:
  408. self._buffer = self._buffer[:-1]
  409. except (OSError, serial.SerialException), err:
  410. timer.cancel()
  411. raise util.CommError('Error reading from device: {0}'.format(str(err)), err)
  412. else:
  413. if got_line:
  414. ret = self._buffer
  415. self._buffer = ''
  416. self.on_read(ret)
  417. if timer:
  418. if timer.is_alive():
  419. timer.cancel()
  420. else:
  421. raise util.TimeoutError('Timeout while waiting for line terminator.')
  422. return ret
  423. class SocketDevice(Device):
  424. """
  425. Device that supports communication with an AD2USB that is exposed via ser2sock or another
  426. Serial to IP interface.
  427. """
  428. @property
  429. def ssl_certificate(self):
  430. """
  431. Retrieves the SSL client certificate path used for authentication.
  432. :returns: The certificate path
  433. """
  434. return self._ssl_certificate
  435. @ssl_certificate.setter
  436. def ssl_certificate(self, value):
  437. """
  438. Sets the SSL client certificate to use for authentication.
  439. :param value: The path to the SSL certificate.
  440. :type value: str
  441. """
  442. self._ssl_certificate = value
  443. @property
  444. def ssl_key(self):
  445. """
  446. Retrieves the SSL client certificate key used for authentication.
  447. :returns: The key path
  448. """
  449. return self._ssl_key
  450. @ssl_key.setter
  451. def ssl_key(self, value):
  452. """
  453. Sets the SSL client certificate key to use for authentication.
  454. :param value: The path to the SSL key.
  455. :type value: str
  456. """
  457. self._ssl_key = value
  458. @property
  459. def ssl_ca(self):
  460. """
  461. Retrieves the SSL Certificate Authority certificate used for authentication.
  462. :returns: The CA path
  463. """
  464. return self._ssl_ca
  465. @ssl_ca.setter
  466. def ssl_ca(self, value):
  467. """
  468. Sets the SSL Certificate Authority certificate used for authentication.
  469. :param value: The path to the SSL CA certificate.
  470. :type value: str
  471. """
  472. self._ssl_ca = value
  473. def __init__(self, interface=("localhost", 10000), use_ssl=False, ssl_certificate=None, ssl_key=None, ssl_ca=None):
  474. """
  475. Constructor
  476. """
  477. Device.__init__(self)
  478. self._interface = interface
  479. self._host, self._port = interface
  480. self._use_ssl = use_ssl
  481. self._ssl_certificate = ssl_certificate
  482. self._ssl_key = ssl_key
  483. self._ssl_ca = ssl_ca
  484. def open(self, baudrate=None, interface=None, index=0, no_reader_thread=False):
  485. """
  486. Opens the device.
  487. :param baudrate: The baudrate to use
  488. :type baudrate: int
  489. :param interface: The hostname and port to connect to.
  490. :type interface: tuple
  491. :param index: Unused
  492. :type index: int
  493. :param no_reader_thread: Whether or not to automatically open the reader thread.
  494. :type no_reader_thread: bool
  495. :raises: util.NoDeviceError, util.CommError
  496. """
  497. if interface is not None:
  498. self._interface = interface
  499. self._host, self._port = interface
  500. try:
  501. self._device = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  502. if self._use_ssl:
  503. try:
  504. ctx = SSL.Context(SSL.TLSv1_METHOD)
  505. ctx.use_privatekey_file(self.ssl_key)
  506. ctx.use_certificate_file(self.ssl_certificate)
  507. ctx.load_verify_locations(self.ssl_ca, None)
  508. ctx.set_verify(SSL.VERIFY_PEER, self._verify_ssl_callback)
  509. self._device = SSL.Connection(ctx, self._device)
  510. except SSL.Error, err:
  511. raise util.CommError('Error setting up SSL connection.', err)
  512. self._device.connect((self._host, self._port))
  513. self._id = '{0}:{1}'.format(self._host, self._port)
  514. except socket.error, err:
  515. raise util.NoDeviceError('Error opening device at {0}:{1}'.format(self._host, self._port), err)
  516. else:
  517. self._running = True
  518. self.on_open(('N/A', "AD2SOCKET"))
  519. if not no_reader_thread:
  520. self._read_thread.start()
  521. def close(self):
  522. """
  523. Closes the device.
  524. """
  525. self._running = False
  526. try:
  527. self._read_thread.stop()
  528. self._device.shutdown(socket.SHUT_RDWR) # Make sure that it closes immediately.
  529. self._device.close()
  530. except:
  531. pass
  532. self.on_close()
  533. def write(self, data):
  534. """
  535. Writes data to the device.
  536. :param data: The data to write.
  537. :type data: str
  538. :returns: The number of bytes sent.
  539. :raises: util.CommError
  540. """
  541. data_sent = None
  542. try:
  543. data_sent = self._device.send(data)
  544. if data_sent == 0:
  545. raise util.CommError('Error writing to device.')
  546. self.on_write(data)
  547. except (SSL.Error, socket.error), err:
  548. raise util.CommError('Error writing to device.', err)
  549. return data_sent
  550. def read(self):
  551. """
  552. Reads a single character from the device.
  553. :returns: The character read from the device.
  554. :raises: util.CommError
  555. """
  556. data = None
  557. try:
  558. data = self._device.recv(1)
  559. except socket.error, err:
  560. raise util.CommError('Error while reading from device: {0}'.format(str(err)), err)
  561. return data
  562. def read_line(self, timeout=0.0, purge_buffer=False):
  563. """
  564. Reads a line from the device.
  565. :param timeout: The read timeout.
  566. :type timeout: float
  567. :param purge_buffer: Indicates whether to purge the buffer prior to reading.
  568. :type purge_buffer: bool
  569. :returns: The line read from the device.
  570. :raises: util.CommError, util.TimeoutError
  571. """
  572. if purge_buffer:
  573. self._buffer = ''
  574. def timeout_event():
  575. timeout_event.reading = False
  576. timeout_event.reading = True
  577. got_line = False
  578. ret = None
  579. timer = None
  580. if timeout > 0:
  581. timer = threading.Timer(timeout, timeout_event)
  582. timer.start()
  583. try:
  584. while timeout_event.reading:
  585. buf = self._device.recv(1)
  586. if buf != '':
  587. self._buffer += buf
  588. if buf == "\n":
  589. if len(self._buffer) > 1:
  590. if self._buffer[-2] == "\r":
  591. self._buffer = self._buffer[:-2]
  592. # ignore if we just got \r\n with nothing else in the buffer.
  593. if len(self._buffer) != 0:
  594. got_line = True
  595. break
  596. else:
  597. self._buffer = self._buffer[:-1]
  598. except socket.error, err:
  599. timer.cancel()
  600. raise util.CommError('Error reading from device: {0}'.format(str(err)), err)
  601. else:
  602. if got_line:
  603. ret = self._buffer
  604. self._buffer = ''
  605. self.on_read(ret)
  606. if timer:
  607. if timer.is_alive():
  608. timer.cancel()
  609. else:
  610. raise util.TimeoutError('Timeout while waiting for line terminator.')
  611. return ret
  612. def _verify_ssl_callback(self, connection, x509, errnum, errdepth, ok):
  613. #print ok
  614. return ok