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.

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