Wrapper around alarmdecoder to make it Twisted compatible.
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.
 
 

170 lines
4.9 KiB

  1. #!/usr/bin/env python
  2. from alarmdecoder.event import event
  3. from twisted.internet import reactor
  4. from twisted.internet.defer import inlineCallbacks, Deferred, returnValue
  5. from twisted.protocols import basic
  6. from twisted.test import proto_helpers
  7. from twisted.trial import unittest
  8. import alarmdecoder
  9. import mock
  10. import twisted.internet.serialport
  11. __all__ = [ 'AlarmDecoderProtocol', 'adtwist' ]
  12. class AlarmDecoderProtocol(basic.LineReceiver):
  13. '''This is a twisted protocol for AlarmDecoder.
  14. To use this class, instantiate the class. Then you must pass it to
  15. AlarmDecoder as it's device and pass it to the transport. Once both
  16. calls have been made, only a reference to the AlarmDecoder instance
  17. should be kept.
  18. There is a helper function adtwist that does this work with a SerialPort
  19. transport.
  20. '''
  21. # Protocol Stuff
  22. delimiter = '\r\n'
  23. def lineReceived(self, line):
  24. self.on_read(data=line)
  25. # AD Device Stuff
  26. on_open = event.Event("This event is called when the device has been opened.\n\n**Callback definition:** *def callback(device)*")
  27. on_close = event.Event("This event is called when the device has been closed.\n\n**Callback definition:** def callback(device)*")
  28. on_read = event.Event("This event is called when a line has been read from the device.\n\n**Callback definition:** def callback(device, data)*")
  29. on_write = event.Event("This event is called when data has been written to the device.\n\n**Callback definition:** def callback(device, data)*")
  30. def open(self, baudrate=None, no_reader_thread=None):
  31. # We don't have anything to do on open. We might want to
  32. # possibly do the transport connection here, or verify that
  33. # we have a transport.
  34. self.on_open()
  35. return self
  36. def write(self, data):
  37. self.transport.write(data)
  38. self.on_write(data=data)
  39. def close(self):
  40. self.on_close()
  41. def adtwist(serdev, *args, **kwargs):
  42. '''Create an AlarmDecoder instance using the twisted SerialPort transport.
  43. The arguments that are passed to this function are passed to SerialPort
  44. allowing the setting of SerialPort's parameters.
  45. open will have already been called.
  46. '''
  47. adp = AlarmDecoderProtocol()
  48. ad = alarmdecoder.AlarmDecoder(adp)
  49. twisted.internet.serialport.SerialPort(adp, serdev, reactor, *args, **kwargs)
  50. ad.open()
  51. return ad
  52. class TestADProtocol(unittest.TestCase):
  53. @staticmethod
  54. def getTimeout():
  55. return .2
  56. def setUp(self):
  57. self.adp = AlarmDecoderProtocol()
  58. self.ad = alarmdecoder.AlarmDecoder(self.adp)
  59. self.tr = proto_helpers.StringTransport()
  60. self.adp.makeConnection(self.tr)
  61. openmock = mock.MagicMock()
  62. self.adp.on_open += openmock
  63. self.ad.open()
  64. openmock.assert_called_once_with(self.adp)
  65. self.assertEqual(self.tr.value(), 'C\rV\r')
  66. self.tr.clear()
  67. self.adp.dataReceived('!CONFIG>ADDRESS=18&CONFIGBITS=ff00&LRR=N&EXP=NNNNN&REL=NNNN&MASK=ffffffff&DEDUPLICATE=N\r\n')
  68. self.adp.dataReceived('!VER:ffffffff,V2.2a.6,TX;RX;SM;VZ;RF;ZX;RE;AU;3X;CG;DD;MF;LR;KE;MK;CB\r\n')
  69. @mock.patch('alarmdecoder.AlarmDecoder.open')
  70. @mock.patch('twisted.internet.serialport.SerialPort')
  71. def test_adtwist(self, spmock, openmock):
  72. dev = 'somedev'
  73. origkwargs = { 'baudrate': 123 }
  74. ret = adtwist(dev, **origkwargs)
  75. self.assertIsInstance(ret, alarmdecoder.AlarmDecoder)
  76. args, kwargs = spmock.call_args
  77. self.assertIsInstance(args[0], AlarmDecoderProtocol)
  78. self.assertEqual(args[1], dev)
  79. self.assertEqual(kwargs, origkwargs)
  80. openmock.assert_called_once()
  81. def test_close(self):
  82. closemock = mock.MagicMock()
  83. self.adp.on_close += closemock
  84. self.ad.close()
  85. closemock.assert_called_once_with(self.adp)
  86. def test_adprot(self):
  87. alarmfun = mock.MagicMock()
  88. ad = self.ad
  89. adp = self.adp
  90. #print `self.tr.value()`
  91. self.assertEqual(ad.version_number, 'V2.2a.6')
  92. msgmock = mock.MagicMock()
  93. ad.on_message += msgmock
  94. data = '[0000000111000100----],006,[f7000007100600202a020000000000],"FIRE 06 "\r\n'
  95. msgdata = data[:-2]
  96. if False: # pragma: no cover
  97. # This'd be nice, but the Message object doesn't have a working equality operator
  98. from alarmdecoder.messages import Message
  99. dmsg = Message(msgdata)
  100. readmock = mock.MagicMock()
  101. readmockad = mock.MagicMock()
  102. adp.on_read += readmock
  103. ad.on_read += readmockad
  104. adp.dataReceived(data)
  105. readmock.assert_called_once_with(adp, data=msgdata)
  106. readmockad.assert_called_once_with(ad, data=msgdata)
  107. msgmock.assert_called_once()
  108. msg = msgmock.call_args[1]['message']
  109. self.assertTrue(msg.ac_power)
  110. self.assertEqual(msg.text, 'FIRE 06 ')
  111. msgmock.reset_mock()
  112. adp.dataReceived('[0000000110000000----],010,[f70000071010000028020000000000],"FAULT 10 "\r\n')
  113. msgmock.assert_called_once()
  114. msg = msgmock.call_args[1]['message']
  115. self.assertEqual(msg.text, 'FAULT 10 ')
  116. writemock = mock.MagicMock()
  117. adp.on_write += writemock
  118. ad.send('5')
  119. self.assertEqual(self.tr.value(), '5')
  120. writemock.assert_called_once_with(adp, data='5')