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.

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