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.

602 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):
  177. """
  178. Reads a line from the device.
  179. """
  180. def timeout_event():
  181. timeout_event.reading = False
  182. timeout_event.reading = True
  183. got_line = False
  184. ret = None
  185. timer = None
  186. if timeout > 0:
  187. timer = threading.Timer(timeout, timeout_event)
  188. timer.start()
  189. try:
  190. while timeout_event.reading:
  191. buf = self._device.read_data(1)
  192. if buf != '':
  193. self._buffer += buf
  194. if buf == "\n":
  195. if len(self._buffer) > 1:
  196. if self._buffer[-2] == "\r":
  197. self._buffer = self._buffer[:-2]
  198. # ignore if we just got \r\n with nothing else in the buffer.
  199. if len(self._buffer) != 0:
  200. got_line = True
  201. break
  202. else:
  203. self._buffer = self._buffer[:-1]
  204. time.sleep(0.001)
  205. except (usb.core.USBError, FtdiError), err:
  206. timer.cancel()
  207. raise util.CommError('Error reading from device: {0}'.format(str(err)))
  208. else:
  209. if got_line:
  210. ret = self._buffer
  211. self._buffer = ''
  212. self.on_read(ret)
  213. if timer:
  214. if timer.is_alive():
  215. timer.cancel()
  216. else:
  217. raise util.TimeoutError('Timeout while waiting for line terminator.')
  218. return ret
  219. class SerialDevice(Device):
  220. """
  221. AD2USB or AD2SERIAL device exposed with the pyserial interface.
  222. """
  223. # Constants
  224. BAUDRATE = 19200
  225. @staticmethod
  226. def find_all(pattern=None):
  227. """
  228. Returns all serial ports present.
  229. """
  230. devices = []
  231. try:
  232. if pattern:
  233. devices = serial.tools.list_ports.grep(pattern)
  234. else:
  235. devices = serial.tools.list_ports.comports()
  236. except SerialException, err:
  237. raise util.CommError('Error enumerating serial devices: {0}'.format(str(err)))
  238. return devices
  239. def __init__(self, interface=None):
  240. """
  241. Constructor
  242. """
  243. Device.__init__(self)
  244. self._interface = interface
  245. self._id = interface
  246. self._device = serial.Serial(timeout=0, writeTimeout=0) # Timeout = non-blocking to match pyftdi.
  247. def open(self, baudrate=BAUDRATE, interface=None, index=None, no_reader_thread=False):
  248. """
  249. Opens the device.
  250. """
  251. # Set up the defaults
  252. if baudrate is None:
  253. baudrate = SerialDevice.BAUDRATE
  254. if self._interface is None and interface is None:
  255. raise util.NoDeviceError('No device interface specified.')
  256. if interface is not None:
  257. self._interface = interface
  258. self._device.port = self._interface
  259. # Open the device and start up the reader thread.
  260. try:
  261. self._device.open()
  262. self._device.baudrate = baudrate # NOTE: Setting the baudrate before opening the
  263. # port caused issues with Moschip 7840/7820
  264. # USB Serial Driver converter. (mos7840)
  265. #
  266. # Moving it to this point seems to resolve
  267. # all issues with it.
  268. except (serial.SerialException, ValueError), err:
  269. raise util.NoDeviceError('Error opening device on port {0}.'.format(interface))
  270. else:
  271. self._running = True
  272. self.on_open(('N/A', "AD2SERIAL"))
  273. if not no_reader_thread:
  274. self._read_thread.start()
  275. def close(self):
  276. """
  277. Closes the device.
  278. """
  279. try:
  280. self._running = False
  281. self._read_thread.stop()
  282. self._device.close()
  283. except:
  284. pass
  285. self.on_close()
  286. def write(self, data):
  287. """
  288. Writes data to the device.
  289. """
  290. try:
  291. self._device.write(data)
  292. except serial.SerialTimeoutException, err:
  293. pass
  294. except serial.SerialException, err:
  295. raise util.CommError('Error writing to device.')
  296. else:
  297. self.on_write(data)
  298. def read(self):
  299. """
  300. Reads a single character from the device.
  301. """
  302. ret = None
  303. try:
  304. ret = self._device.read(1)
  305. except serial.SerialException, err:
  306. raise util.CommError('Error reading from device: {0}'.format(str(err)))
  307. return ret
  308. def read_line(self, timeout=0.0):
  309. """
  310. Reads a line from the device.
  311. """
  312. def timeout_event():
  313. timeout_event.reading = False
  314. timeout_event.reading = True
  315. got_line = False
  316. ret = None
  317. timer = None
  318. if timeout > 0:
  319. timer = threading.Timer(timeout, timeout_event)
  320. timer.start()
  321. try:
  322. while timeout_event.reading:
  323. buf = self._device.read(1)
  324. if buf != '' and buf != "\xff": # AD2SERIAL specifically apparently sends down \xFF on boot.
  325. self._buffer += buf
  326. if buf == "\n":
  327. if len(self._buffer) > 1:
  328. if self._buffer[-2] == "\r":
  329. self._buffer = self._buffer[:-2]
  330. # ignore if we just got \r\n with nothing else in the buffer.
  331. if len(self._buffer) != 0:
  332. got_line = True
  333. break
  334. else:
  335. self._buffer = self._buffer[:-1]
  336. time.sleep(0.001)
  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):
  417. """
  418. Reads a line from the device.
  419. """
  420. def timeout_event():
  421. timeout_event.reading = False
  422. timeout_event.reading = True
  423. got_line = False
  424. ret = None
  425. timer = None
  426. if timeout > 0:
  427. timer = threading.Timer(timeout, timeout_event)
  428. timer.start()
  429. try:
  430. while timeout_event.reading:
  431. buf = self._device.recv(1)
  432. if buf != '':
  433. self._buffer += buf
  434. if buf == "\n":
  435. if len(self._buffer) > 1:
  436. if self._buffer[-2] == "\r":
  437. self._buffer = self._buffer[:-2]
  438. # ignore if we just got \r\n with nothing else in the buffer.
  439. if len(self._buffer) != 0:
  440. got_line = True
  441. break
  442. else:
  443. self._buffer = self._buffer[:-1]
  444. time.sleep(0.001)
  445. except socket.error, err:
  446. timer.cancel()
  447. raise util.CommError('Error reading from device: {0}'.format(str(err)))
  448. else:
  449. if got_line:
  450. ret = self._buffer
  451. self._buffer = ''
  452. self.on_read(ret)
  453. if timer:
  454. if timer.is_alive():
  455. timer.cancel()
  456. else:
  457. raise util.TimeoutError('Timeout while waiting for line terminator.')
  458. return ret