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.

367 lines
16 KiB

  1. import time
  2. from builtins import bytes
  3. from unittest import TestCase
  4. from mock import Mock, MagicMock, patch
  5. from alarmdecoder.decoder import AlarmDecoder
  6. from alarmdecoder.devices import USBDevice
  7. from alarmdecoder.messages import Message, RFMessage, LRRMessage, ExpanderMessage
  8. from alarmdecoder.event.event import Event, EventHandler
  9. from alarmdecoder.zonetracking import Zonetracker
  10. from alarmdecoder.panels import ADEMCO, DSC
  11. from alarmdecoder.messages.lrr import LRR_EVENT_TYPE, LRR_EVENT_STATUS
  12. from alarmdecoder.states import FireState
  13. class TestAlarmDecoder(TestCase):
  14. def setUp(self):
  15. self._panicked = False
  16. self._relay_changed = False
  17. self._power_changed = False
  18. self._chime_changed = False
  19. self._ready_changed = False
  20. self._alarmed = False
  21. self._bypassed = False
  22. self._battery = False
  23. self._fire = False
  24. self._armed = False
  25. self._got_config = False
  26. self._message_received = False
  27. self._rfx_message_received = False
  28. self._lrr_message_received = False
  29. self._expander_message_received = False
  30. self._sending_received_status = None
  31. self._alarm_restored = False
  32. self._on_boot_received = False
  33. self._zone_faulted = None
  34. self._zone_restored = None
  35. self._device = Mock(spec=USBDevice)
  36. self._device.on_open = EventHandler(Event(), self._device)
  37. self._device.on_close = EventHandler(Event(), self._device)
  38. self._device.on_read = EventHandler(Event(), self._device)
  39. self._device.on_write = EventHandler(Event(), self._device)
  40. self._decoder = AlarmDecoder(self._device, ignore_lrr_states=False)
  41. self._decoder.on_panic += self.on_panic
  42. self._decoder.on_relay_changed += self.on_relay_changed
  43. self._decoder.on_power_changed += self.on_power_changed
  44. self._decoder.on_ready_changed += self.on_ready_changed
  45. self._decoder.on_chime_changed += self.on_chime_changed
  46. self._decoder.on_alarm += self.on_alarm
  47. self._decoder.on_alarm_restored += self.on_alarm_restored
  48. self._decoder.on_bypass += self.on_bypass
  49. self._decoder.on_low_battery += self.on_battery
  50. self._decoder.on_fire += self.on_fire
  51. self._decoder.on_arm += self.on_arm
  52. self._decoder.on_disarm += self.on_disarm
  53. self._decoder.on_config_received += self.on_config
  54. self._decoder.on_message += self.on_message
  55. self._decoder.on_rfx_message += self.on_rfx_message
  56. self._decoder.on_lrr_message += self.on_lrr_message
  57. self._decoder.on_expander_message += self.on_expander_message
  58. self._decoder.on_sending_received += self.on_sending_received
  59. self._decoder.on_boot += self.on_boot
  60. self._decoder.on_zone_fault += self.on_zone_fault
  61. self._decoder.on_zone_restore += self.on_zone_restore
  62. self._decoder.address_mask = int('ffffffff', 16)
  63. self._decoder.open()
  64. def tearDown(self):
  65. pass
  66. ### Library events
  67. def on_panic(self, sender, *args, **kwargs):
  68. self._panicked = kwargs['status']
  69. def on_relay_changed(self, sender, *args, **kwargs):
  70. self._relay_changed = True
  71. def on_power_changed(self, sender, *args, **kwargs):
  72. self._power_changed = kwargs['status']
  73. def on_ready_changed(self, sender, *args, **kwargs):
  74. self._ready_changed = kwargs['status']
  75. def on_chime_changed(self, sender, *args, **kwargs):
  76. self._chime_changed = kwargs['status']
  77. def on_alarm(self, sender, *args, **kwargs):
  78. self._alarmed = True
  79. def on_alarm_restored(self, sender, *args, **kwargs):
  80. self._alarm_restored = True
  81. def on_bypass(self, sender, *args, **kwargs):
  82. self._bypassed = kwargs['status']
  83. def on_battery(self, sender, *args, **kwargs):
  84. self._battery = kwargs['status']
  85. def on_fire(self, sender, *args, **kwargs):
  86. self._fire = kwargs['status']
  87. def on_arm(self, sender, *args, **kwargs):
  88. self._armed = True
  89. def on_disarm(self, sender, *args, **kwargs):
  90. self._armed = False
  91. def on_config(self, sender, *args, **kwargs):
  92. self._got_config = True
  93. def on_message(self, sender, *args, **kwargs):
  94. self._message_received = True
  95. def on_rfx_message(self, sender, *args, **kwargs):
  96. self._rfx_message_received = True
  97. def on_lrr_message(self, sender, *args, **kwargs):
  98. self._lrr_message_received = True
  99. def on_expander_message(self, sender, *args, **kwargs):
  100. self._expander_message_received = True
  101. def on_sending_received(self, sender, *args, **kwargs):
  102. self._sending_received_status = kwargs['status']
  103. def on_boot(self, sender, *args, **kwargs):
  104. self._on_boot_received = True
  105. def on_zone_fault(self, sender, *args, **kwargs):
  106. self._zone_faulted = kwargs['zone']
  107. def on_zone_restore(self, sender, *args, **kwargs):
  108. self._zone_restored = kwargs['zone']
  109. ### Tests
  110. def test_open(self):
  111. self._decoder.open()
  112. self._device.open.assert_called()
  113. def test_close(self):
  114. self._decoder.open()
  115. self._decoder.close()
  116. self._device.close.assert_called()
  117. def test_send(self):
  118. self._decoder.send('test')
  119. self._device.write.assert_called_with(b'test')
  120. def test_get_config(self):
  121. self._decoder.get_config()
  122. self._device.write.assert_called_with(b"C\r")
  123. def test_save_config(self):
  124. self._decoder.save_config()
  125. self._device.write.assert_called()
  126. def test_reboot(self):
  127. self._decoder.reboot()
  128. self._device.write.assert_called_with(b'=')
  129. def test_fault(self):
  130. self._decoder.fault_zone(1)
  131. self._device.write.assert_called_with(bytes("L{0:02}{1}\r".format(1, 1), 'utf-8'))
  132. def test_fault_wireproblem(self):
  133. self._decoder.fault_zone(1, simulate_wire_problem=True)
  134. self._device.write.assert_called_with(bytes("L{0:02}{1}\r".format(1, 2), 'utf-8'))
  135. def test_clear_zone(self):
  136. self._decoder.clear_zone(1)
  137. self._device.write.assert_called_with(bytes("L{0:02}0\r".format(1), 'utf-8'))
  138. def test_message(self):
  139. msg = self._decoder._handle_message(b'[0000000000000000----],000,[f707000600e5800c0c020000]," "')
  140. self.assertIsInstance(msg, Message)
  141. self._decoder._on_read(self, data=b'[0000000000000000----],000,[f707000600e5800c0c020000]," "')
  142. self.assertTrue(self._message_received)
  143. def test_message_kpm(self):
  144. msg = self._decoder._handle_message(b'!KPM:[0000000000000000----],000,[f707000600e5800c0c020000]," "')
  145. self.assertIsInstance(msg, Message)
  146. self._decoder._on_read(self, data=b'[0000000000000000----],000,[f707000600e5800c0c020000]," "')
  147. self.assertTrue(self._message_received)
  148. def test_expander_message(self):
  149. msg = self._decoder._handle_message(b'!EXP:07,01,01')
  150. self.assertIsInstance(msg, ExpanderMessage)
  151. self._decoder._on_read(self, data=b'!EXP:07,01,01')
  152. self.assertTrue(self._expander_message_received)
  153. def test_relay_message(self):
  154. msg = self._decoder._handle_message(b'!REL:12,01,01')
  155. self.assertIsInstance(msg, ExpanderMessage)
  156. self.assertTrue(self._relay_changed)
  157. def test_rfx_message(self):
  158. msg = self._decoder._handle_message(b'!RFX:0180036,80')
  159. self.assertIsInstance(msg, RFMessage)
  160. self.assertTrue(self._rfx_message_received)
  161. def test_panic_v1(self):
  162. # LRR v1
  163. msg = self._decoder._handle_message(b'!LRR:012,1,ALARM_PANIC')
  164. self.assertIsInstance(msg, LRRMessage)
  165. self.assertTrue(self._panicked)
  166. msg = self._decoder._handle_message(b'!LRR:012,1,CANCEL')
  167. self.assertIsInstance(msg, LRRMessage)
  168. self.assertFalse(self._panicked)
  169. def test_panic_v2(self):
  170. # LRR v2
  171. msg = self._decoder._handle_message(b'!LRR:099,1,CID_1123,ff') # Panic
  172. self.assertIsInstance(msg, LRRMessage)
  173. self.assertTrue(self._panicked)
  174. msg = self._decoder._handle_message(b'!LRR:001,1,CID_1406,ff') # Cancel
  175. self.assertIsInstance(msg, LRRMessage)
  176. self.assertFalse(self._panicked)
  177. def test_config_message(self):
  178. msg = self._decoder._handle_message(b'!CONFIG>MODE=A&CONFIGBITS=ff04&ADDRESS=18&LRR=N&COM=N&EXP=NNNNN&REL=NNNN&MASK=ffffffff&DEDUPLICATE=N')
  179. self.assertEquals(self._decoder.mode, ADEMCO)
  180. self.assertEquals(self._decoder.address, 18)
  181. self.assertEquals(self._decoder.configbits, int('ff04', 16))
  182. self.assertEquals(self._decoder.address_mask, int('ffffffff', 16))
  183. self.assertEquals(self._decoder.emulate_zone, [False for x in range(5)])
  184. self.assertEquals(self._decoder.emulate_relay, [False for x in range(4)])
  185. self.assertFalse(self._decoder.emulate_lrr)
  186. self.assertFalse(self._decoder.emulate_com)
  187. self.assertFalse(self._decoder.deduplicate)
  188. self.assertTrue(self._got_config)
  189. def test_power_changed_event(self):
  190. msg = self._decoder._handle_message(b'[0000000100000000----],000,[f707000600e5800c0c020000]," "')
  191. self.assertFalse(self._power_changed) # Not set first time we hit it.
  192. msg = self._decoder._handle_message(b'[0000000000000000----],000,[f707000600e5800c0c020000]," "')
  193. self.assertFalse(self._power_changed)
  194. msg = self._decoder._handle_message(b'[0000000100000000----],000,[f707000600e5800c0c020000]," "')
  195. self.assertTrue(self._power_changed)
  196. def test_ready_changed_event(self):
  197. msg = self._decoder._handle_message(b'[0000000000000000----],000,[f707000600e5800c0c020000]," "')
  198. self.assertFalse(self._ready_changed) # Not set first time we hit it.
  199. msg = self._decoder._handle_message(b'[1000000000000000----],000,[f707000600e5800c0c020000]," "')
  200. self.assertTrue(self._ready_changed)
  201. msg = self._decoder._handle_message(b'[0000000000000000----],000,[f707000600e5800c0c020000]," "')
  202. self.assertFalse(self._ready_changed)
  203. def test_chime_changed_event(self):
  204. msg = self._decoder._handle_message(b'[0000000000000000----],000,[f707000600e5800c0c020000]," "')
  205. self.assertFalse(self._chime_changed) # Not set first time we hit it.
  206. msg = self._decoder._handle_message(b'[0000000010000000----],000,[f707000600e5800c0c020000]," "')
  207. self.assertTrue(self._chime_changed)
  208. msg = self._decoder._handle_message(b'[0000000000000000----],000,[f707000600e5800c0c020000]," "')
  209. self.assertFalse(self._chime_changed)
  210. def test_alarm_event(self):
  211. msg = self._decoder._handle_message(b'[0000000000100000----],000,[f707000600e5800c0c020000]," "')
  212. self.assertFalse(self._alarmed) # Not set first time we hit it.
  213. msg = self._decoder._handle_message(b'[0000000000000000----],000,[f707000600e5800c0c020000]," "')
  214. self.assertFalse(self._alarmed)
  215. self.assertTrue(self._alarm_restored)
  216. msg = self._decoder._handle_message(b'[0000000000100000----],000,[f707000600e5800c0c020000]," "')
  217. self.assertTrue(self._alarmed)
  218. def test_zone_bypassed_event(self):
  219. msg = self._decoder._handle_message(b'[0000000000000000----],000,[f707000600e5800c0c020000]," "')
  220. self.assertFalse(self._bypassed)
  221. msg = self._decoder._handle_message(b'[0000001000000000----],000,[f707000600e5800c0c020000]," "')
  222. self.assertTrue(self._bypassed)
  223. def test_armed_away_event(self):
  224. msg = self._decoder._handle_message(b'[0100000000000000----],000,[f707000600e5800c0c020000]," "')
  225. self.assertFalse(self._armed) # Not set first time we hit it.
  226. msg = self._decoder._handle_message(b'[0100000000000000----],000,[f707000600e5800c0c020000]," "')
  227. self.assertFalse(self._armed)
  228. msg = self._decoder._handle_message(b'[0000000000000000----],000,[f707000600e5800c0c020000]," "')
  229. self.assertFalse(self._armed)
  230. msg = self._decoder._handle_message(b'[0100000000000000----],000,[f707000600e5800c0c020000]," "')
  231. self.assertTrue(self._armed)
  232. self._armed = False
  233. msg = self._decoder._handle_message(b'[0010000000000000----],000,[f707000600e5800c0c020000]," "')
  234. self.assertTrue(self._armed)
  235. msg = self._decoder._handle_message(b'[0000000000000000----],000,[f707000600e5800c0c020000]," "')
  236. self.assertFalse(self._armed)
  237. def test_battery_low_event(self):
  238. msg = self._decoder._handle_message(b'[0000000000010000----],000,[f707000600e5800c0c020000]," "')
  239. self.assertTrue(self._battery)
  240. # force the timeout to expire.
  241. with patch.object(time, 'time', return_value=self._decoder._battery_status[1] + 35):
  242. msg = self._decoder._handle_message(b'[0000000000000000----],000,[f707000600e5800c0c020000]," "')
  243. self.assertFalse(self._battery)
  244. def test_fire_alarm_event(self):
  245. msg = self._decoder._handle_message(b'[0000000000000000----],000,[f707000600e5800c0c020000]," "')
  246. self.assertFalse(self._fire) # Not set the first time we hit it.
  247. msg = self._decoder._handle_message(b'[0000000000000100----],000,[f707000600e5800c0c020000]," "')
  248. self.assertTrue(self._fire)
  249. def test_fire_lrr(self):
  250. self._fire = False
  251. msg = self._decoder._handle_message(b'!LRR:095,1,CID_1110,ff') # Fire: Non-specific
  252. self.assertIsInstance(msg, LRRMessage)
  253. self.assertTrue(self._fire)
  254. msg = self._decoder._handle_message(b'!LRR:001,1,CID_1406,ff') # Open/Close: Cancel
  255. self.assertIsInstance(msg, LRRMessage)
  256. self.assertFalse(self._fire)
  257. def test_hit_for_faults(self):
  258. self._decoder._handle_message(b'[0000000000000000----],000,[f707000600e5800c0c020000],"Hit * for faults "')
  259. self._decoder._device.write.assert_called_with(b'*')
  260. def test_sending_received(self):
  261. self._decoder._on_read(self, data=b'!Sending.done')
  262. self.assertTrue(self._sending_received_status)
  263. self._decoder._on_read(self, data=b'!Sending.....done')
  264. self.assertFalse(self._sending_received_status)
  265. def test_boot(self):
  266. self._decoder._on_read(self, data=b'!Ready')
  267. self.assertTrue(self._on_boot_received)
  268. def test_zone_fault_and_restore(self):
  269. self._decoder._on_read(self, data=b'[00010001000000000A--],003,[f70000051003000008020000000000],"FAULT 03 "')
  270. self.assertEquals(self._zone_faulted, 3)
  271. self._decoder._on_read(self, data=b'[00010001000000000A--],004,[f70000051003000008020000000000],"FAULT 04 "')
  272. self.assertEquals(self._zone_faulted, 4)
  273. self._decoder._on_read(self, data=b'[00010001000000000A--],005,[f70000051003000008020000000000],"FAULT 05 "')
  274. self.assertEquals(self._zone_faulted, 5)
  275. self._decoder._on_read(self, data=b'[00010001000000000A--],004,[f70000051003000008020000000000],"FAULT 04 "')
  276. self.assertEquals(self._zone_restored, 3)