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.

498 lines
12 KiB

  1. """
  2. Provides the full AD2USB class and factory.
  3. """
  4. import time
  5. import threading
  6. from .event import event
  7. from . import devices
  8. from . import util
  9. class Overseer(object):
  10. """
  11. Factory for creation of AD2USB devices as well as provide4s attach/detach events."
  12. """
  13. # Factory events
  14. on_attached = event.Event('Called when an AD2USB device has been detected.')
  15. on_detached = event.Event('Called when an AD2USB device has been removed.')
  16. __devices = []
  17. @classmethod
  18. def find_all(cls):
  19. """
  20. Returns all AD2USB devices located on the system.
  21. """
  22. cls.__devices = devices.USBDevice.find_all()
  23. return cls.__devices
  24. @classmethod
  25. def devices(cls):
  26. """
  27. Returns a cached list of AD2USB devices located on the system.
  28. """
  29. return cls.__devices
  30. @classmethod
  31. def create(cls, device=None):
  32. """
  33. Factory method that returns the requested AD2USB device, or the first device.
  34. """
  35. cls.find_all()
  36. if len(cls.__devices) == 0:
  37. raise util.NoDeviceError('No AD2USB devices present.')
  38. if device is None:
  39. device = cls.__devices[0]
  40. vendor, product, sernum, ifcount, description = device
  41. device = devices.USBDevice(serial=sernum, description=description)
  42. return AD2USB(device)
  43. def __init__(self, attached_event=None, detached_event=None):
  44. """
  45. Constructor
  46. """
  47. self._detect_thread = Overseer.DetectThread(self)
  48. if attached_event:
  49. self.on_attached += attached_event
  50. if detached_event:
  51. self.on_detached += detached_event
  52. Overseer.find_all()
  53. self.start()
  54. def __del__(self):
  55. """
  56. Destructor
  57. """
  58. pass
  59. def close(self):
  60. """
  61. Clean up and shut down.
  62. """
  63. self.stop()
  64. def start(self):
  65. """
  66. Starts the detection thread, if not already running.
  67. """
  68. if not self._detect_thread.is_alive():
  69. self._detect_thread.start()
  70. def stop(self):
  71. """
  72. Stops the detection thread.
  73. """
  74. self._detect_thread.stop()
  75. def get_device(self, device=None):
  76. """
  77. Factory method that returns the requested AD2USB device, or the first device.
  78. """
  79. return Overseer.create(device)
  80. class DetectThread(threading.Thread):
  81. """
  82. Thread that handles detection of added/removed devices.
  83. """
  84. def __init__(self, overseer):
  85. """
  86. Constructor
  87. """
  88. threading.Thread.__init__(self)
  89. self._overseer = overseer
  90. self._running = False
  91. def stop(self):
  92. """
  93. Stops the thread.
  94. """
  95. self._running = False
  96. def run(self):
  97. """
  98. The actual detection process.
  99. """
  100. self._running = True
  101. last_devices = set()
  102. while self._running:
  103. try:
  104. Overseer.find_all()
  105. current_devices = set(Overseer.devices())
  106. new_devices = [d for d in current_devices if d not in last_devices]
  107. removed_devices = [d for d in last_devices if d not in current_devices]
  108. last_devices = current_devices
  109. for d in new_devices:
  110. self._overseer.on_attached(d)
  111. for d in removed_devices:
  112. self._overseer.on_detached(d)
  113. except util.CommError, err:
  114. pass
  115. time.sleep(0.25)
  116. class AD2USB(object):
  117. """
  118. High-level wrapper around AD2USB/AD2SERIAL devices.
  119. """
  120. # High-level Events
  121. on_open = event.Event('Called when the device has been opened')
  122. on_close = event.Event('Called when the device has been closed')
  123. on_message = event.Event('Called when a message has been received from the device.')
  124. # Low-level Events
  125. on_read = event.Event('Called when a line has been read from the device')
  126. on_write = event.Event('Called when data has been written to the device')
  127. def __init__(self, device):
  128. """
  129. Constructor
  130. """
  131. self._device = device
  132. def __del__(self):
  133. """
  134. Destructor
  135. """
  136. pass
  137. def open(self, baudrate=None, interface=None, index=None):
  138. """
  139. Opens the device.
  140. """
  141. self._wire_events()
  142. self._device.open(baudrate=baudrate, interface=interface, index=index)
  143. def close(self):
  144. """
  145. Closes the device.
  146. """
  147. self._device.close()
  148. self._device = None
  149. def _wire_events(self):
  150. """
  151. Wires up the internal device events.
  152. """
  153. self._device.on_open += self._on_open
  154. self._device.on_close += self._on_close
  155. self._device.on_read += self._on_read
  156. self._device.on_write += self._on_write
  157. def _handle_message(self, data):
  158. """
  159. Parses messages from the panel.
  160. """
  161. if data[0] == '!': # TEMP: Remove this.
  162. return None
  163. msg = Message()
  164. # parse and build stuff
  165. def _on_open(self, sender, args):
  166. """
  167. Internal handler for opening the device.
  168. """
  169. self.on_open(args)
  170. def _on_close(self, sender, args):
  171. """
  172. Internal handler for closing the device.
  173. """
  174. self.on_close(args)
  175. def _on_read(self, sender, args):
  176. """
  177. Internal handler for reading from the device.
  178. """
  179. msg = self._handle_message(args)
  180. if msg:
  181. self.on_message(msg)
  182. self.on_read(args)
  183. def _on_write(self, sender, args):
  184. """
  185. Internal handler for writing to the device.
  186. """
  187. self.on_write(args)
  188. class Message(object):
  189. """
  190. Represents a message from the alarm panel.
  191. """
  192. def __init__(self, data=None):
  193. """
  194. Constructor
  195. """
  196. self._ignore_packet = False
  197. self._ready = False
  198. self._armed_away = False
  199. self._armed_home = False
  200. self._backlight = False
  201. self._programming_mode = False
  202. self._beeps = -1
  203. self._bypass = False
  204. self._ac = False
  205. self._chime_mode = False
  206. self._alarm_event_occurred = False
  207. self._alarm_bell = False
  208. self._numeric = ""
  209. self._text = ""
  210. self._cursor = -1
  211. self._raw = ""
  212. if data is not None:
  213. self._parse_message(data)
  214. def _parse_message(self, data):
  215. pattern = '("(?:[^"]|"")*"|[^,]*),("(?:[^"]|"")*"|[^,]*),("(?:[^"]|"")*"|[^,]*),("(?:[^"]|"")*"|[^,]*)'
  216. pass
  217. @property
  218. def ignore_packet(self):
  219. """
  220. Indicates whether or not this message should be ignored.
  221. """
  222. return self._ignore_packet
  223. @ignore_packet.setter
  224. def ignore_packet(self, value):
  225. """
  226. Sets the value indicating whether or not this packet should be ignored.
  227. """
  228. self._ignore_packet = value
  229. @property
  230. def ready(self):
  231. """
  232. Indicates whether or not the panel is ready.
  233. """
  234. return self._ready
  235. @ready.setter
  236. def ready(self, value):
  237. """
  238. Sets the value indicating whether or not the panel is ready.
  239. """
  240. self._ready = value
  241. @property
  242. def armed_away(self):
  243. """
  244. Indicates whether or not the panel is armed in away mode.
  245. """
  246. return self._armed_away
  247. @armed_away.setter
  248. def armed_away(self, value):
  249. """
  250. Sets the value indicating whether or not the panel is armed in away mode.
  251. """
  252. self._armed_away = value
  253. @property
  254. def armed_home(self):
  255. """
  256. Indicates whether or not the panel is armed in home/stay mode.
  257. """
  258. return self._armed_home
  259. @armed_home.setter
  260. def armed_home(self, value):
  261. """
  262. Sets the value indicating whether or not the panel is armed in home/stay mode.
  263. """
  264. self._armed_home = value
  265. @property
  266. def backlight(self):
  267. """
  268. Indicates whether or not the panel backlight is on.
  269. """
  270. return self._backlight
  271. @backlight.setter
  272. def backlight(self, value):
  273. """
  274. Sets the value indicating whether or not the panel backlight is on.
  275. """
  276. self._backlight = value
  277. @property
  278. def programming_mode(self):
  279. """
  280. Indicates whether or not the panel is in programming mode.
  281. """
  282. return self._programming_mode
  283. @programming_mode.setter
  284. def programming_mode(self, value):
  285. """
  286. Sets the value indicating whether or not the panel is in programming mode.
  287. """
  288. self._programming_mode = value
  289. @property
  290. def beeps(self):
  291. """
  292. Returns the number of beeps associated with this message.
  293. """
  294. return self._beeps
  295. @beeps.setter
  296. def beeps(self, value):
  297. """
  298. Sets the number of beeps associated with this message.
  299. """
  300. self._beeps = value
  301. @property
  302. def bypass(self):
  303. """
  304. Indicates whether or not zones have been bypassed.
  305. """
  306. return self._bypass
  307. @bypass.setter
  308. def bypass(self, value):
  309. """
  310. Sets the value indicating whether or not zones have been bypassed.
  311. """
  312. self._bypass = value
  313. @property
  314. def ac(self):
  315. """
  316. Indicates whether or not the system is on AC power.
  317. """
  318. return self._ac
  319. @ac.setter
  320. def ac(self, value):
  321. """
  322. Sets the value indicating whether or not the system is on AC power.
  323. """
  324. self._ac = value
  325. @property
  326. def chime_mode(self):
  327. """
  328. Indicates whether or not panel chimes are enabled.
  329. """
  330. return self._chime_mode
  331. @chime_mode.setter
  332. def chime_mode(self, value):
  333. """
  334. Sets the value indicating whether or not the panel chimes are enabled.
  335. """
  336. self._chime_mode = value
  337. @property
  338. def alarm_event_occurred(self):
  339. """
  340. Indicates whether or not an alarm event has occurred.
  341. """
  342. return self._alarm_event_occurred
  343. @alarm_event_occurred.setter
  344. def alarm_event_occurred(self, value):
  345. """
  346. Sets the value indicating whether or not an alarm event has occurred.
  347. """
  348. self._alarm_event_occurred = value
  349. @property
  350. def alarm_bell(self):
  351. """
  352. Indicates whether or not an alarm is currently sounding.
  353. """
  354. return self._alarm_bell
  355. @alarm_bell.setter
  356. def alarm_bell(self, value):
  357. """
  358. Sets the value indicating whether or not an alarm is currently sounding.
  359. """
  360. self._alarm_bell = value
  361. @property
  362. def numeric(self):
  363. """
  364. Numeric indicator of associated with message. For example: If zone #3 is faulted, this value is 003.
  365. """
  366. return self._numeric
  367. @numeric.setter
  368. def numeric(self, value):
  369. """
  370. Sets the numeric indicator associated with this message.
  371. """
  372. self._numeric = value
  373. @property
  374. def text(self):
  375. """
  376. Alphanumeric text associated with this message.
  377. """
  378. return self._text
  379. @text.setter
  380. def text(self, value):
  381. """
  382. Sets the alphanumeric text associated with this message.
  383. """
  384. self._text = value
  385. @property
  386. def cursor(self):
  387. """
  388. Indicates which text position has the cursor underneath it.
  389. """
  390. return self._cursor
  391. @cursor.setter
  392. def cursor(self, value):
  393. """
  394. Sets the value indicating which text position has the cursor underneath it.
  395. """
  396. self._cursor = value
  397. @property
  398. def raw(self):
  399. """
  400. Raw representation of the message data from the panel.
  401. """
  402. return self._raw
  403. @raw.setter
  404. def raw(self, value):
  405. """
  406. Sets the raw representation of the message data from the panel.
  407. """
  408. self._raw = value