def test_send(self): """ Test send() with AT command. """ device = Serial() xbee = ZigBee(device) xbee.send('at', command='MY') result = device.get_data_written() expected = b'~\x00\x04\x08\x01MYP' self.assertEqual(result, expected)
def test_send(self): """ Test send() with AT command. """ device = Serial() xbee = ZigBee(device, io_loop=self._patch_io) xbee.send('at', command='MY') result = device.get_data_written() expected = b'~\x00\x04\x08\x01MYP' self.assertEqual(result, expected)
def setUp(self): self.zigbee = ZigBee(None) super(TestParseZigBeeIOData, self).setUp()
class TestParseZigBeeIOData(unittest.TestCase): """ Test parsing ZigBee specific IO data """ def setUp(self): self.zigbee = ZigBee(None) super(TestParseZigBeeIOData, self).setUp() def test_parse_dio_adc(self): data = b'\x01\x08\x00\x0e\x08\x00\x00\x00\x02P\x02\x06' expected_results = [{ 'dio-11': True, 'adc-1': 0, 'adc-2': 592, 'adc-3': 518 }] results = self.zigbee._parse_samples(data) self.assertEqual(results, expected_results) def test_parse_samples_ticket_44(self): """ This example is from ticket 44 on Google Code: https://code.google.com/p/python-xbee/issues/detail?id=44 The author claims the given data is generated by an Xbee Pro 900HP module, but I could only find a definition for packet with a response type of 0x92 in the XBee ZB specification. """ data = ( b'\x01' + # Number of samples b'\x10\x00' + # Digital I/O mask (CD/DIO12 enabled) b'\x0E' + # ADC 1,2,3 enabled b'\x10\x00' + # DIO12 is high b'\x03\xA4' + # ADC1 = 932 b'\x01\x31' + # ADC2 = 305 b'\x03\x31') # ADC3 = 817 expected_results = [{ 'dio-12': True, 'adc-1': 932, 'adc-2': 305, 'adc-3': 817 }] results = self.zigbee._parse_samples(data) self.assertEqual(results, expected_results) def test_parse_dio_adc_supply_voltage_not_clamped(self): """ When bit 7 on the ADC mask is set, the supply voltage is included in the ADC I/O sample section. This sample may exceed 10 bits of precision, even though all other ADC channels are limited to a range of 0-1.2v with 10 bits of precision. I assume that a voltage divider and the firmware is used internally to compute the actual Vcc voltage. Therefore, the I/O sampling routine must not clamp this ADC channel to 10 bits of precision. """ data = b'\x01\x00\x00\x80\x0D\x18' expected_results = [{'adc-7': 0xD18}] results = self.zigbee._parse_samples(data) self.assertEqual(results, expected_results)
def setUp(self): self.zigbee = ZigBee(None) super(TestZigBee, self).setUp()
class TestZigBee(unittest.TestCase): """ Tests ZigBee-specific features """ def setUp(self): self.zigbee = ZigBee(None) super(TestZigBee, self).setUp() def test_send(self): """ Test send() with AT command. """ device = Serial() xbee = ZigBee(device) xbee.send('at', command='MY') result = device.get_data_written() expected = b'~\x00\x04\x08\x01MYP' self.assertEqual(result, expected) def test_null_terminated_field(self): """ Packets with null-terminated fields should be properly parsed """ expected_data = b'\x01\x02\x03\x04' terminator = b'\x00' node_identifier = b'\x95' + b'\x00' * 21 + expected_data + \ terminator + b'\x00' * 8 data = self.zigbee._split_response(node_identifier) self.assertEqual(data['node_id'], expected_data) def test_split_node_identification_identifier(self): data = b'\x95\x00\x13\xa2\x00\x40\x52\x2b\xaa\x7d\x84\x02\x7d\x84\x00\x13\xa2\x00\x40\x52\x2b\xaa\x20\x00\xff\xfe\x01\x01\xc1\x05\x10\x1e' # noqa info = self.zigbee._split_response(data) expected_info = { 'id': 'node_id_indicator', 'sender_addr_long': b'\x00\x13\xa2\x00\x40\x52\x2b\xaa', 'sender_addr': b'\x7d\x84', 'options': b'\x02', 'source_addr': b'\x7d\x84', 'source_addr_long': b'\x00\x13\xa2\x00\x40\x52\x2b\xaa', 'node_id': b' ', 'parent_source_addr': b'\xff\xfe', 'device_type': b'\x01', 'source_event': b'\x01', 'digi_profile_id': b'\xc1\x05', 'manufacturer_id': b'\x10\x1e', } self.assertEqual(info, expected_info) def test_split_node_identification_identifier2(self): data = b'\x95\x00\x13\xa2\x00\x40\x52\x2b\xaa\x7d\x84\x02\x7d\x84\x00\x13\xa2\x00\x40\x52\x2b\xaaCoordinator\x00\xff\xfe\x01\x01\xc1\x05\x10\x1e' # noqa info = self.zigbee._split_response(data) expected_info = { 'id': 'node_id_indicator', 'sender_addr_long': b'\x00\x13\xa2\x00\x40\x52\x2b\xaa', 'sender_addr': b'\x7d\x84', 'options': b'\x02', 'source_addr': b'\x7d\x84', 'source_addr_long': b'\x00\x13\xa2\x00\x40\x52\x2b\xaa', 'node_id': b'Coordinator', 'parent_source_addr': b'\xff\xfe', 'device_type': b'\x01', 'source_event': b'\x01', 'digi_profile_id': b'\xc1\x05', 'manufacturer_id': b'\x10\x1e', } self.assertEqual(info, expected_info) def test_is_remote_at_response_parameter_parsed_as_io_samples(self): """ A remote AT command of IS, to take a sample immediately and respond with the results, must be appropriately parsed for IO data. """ data = b'\x97A\x00\x13\xa2\x00@oG\xe4v\x1aIS\x00\x01\x1c\xc0\x06\x18\x00\x02\x8c\x03\x96' # noqa info = self.zigbee._split_response(data) expected_info = { 'id': 'remote_at_response', 'frame_id': b'A', 'source_addr_long': b'\x00\x13\xa2\x00@oG\xe4', 'source_addr': b'v\x1a', 'command': b'IS', 'status': b'\x00', 'parameter': [{ 'adc-1': 652, 'adc-2': 918, 'dio-10': False, 'dio-11': True, 'dio-12': True, 'dio-6': False, 'dio-7': False }] } self.assertEqual(info, expected_info) def test_lowercase_is_remote_at_response_parameter_parsed_as_io_samples( self): # noqa """ A remote AT command of lowercase is, to take a sample immediately and respond with the results, must be appropriately parsed for IO data. """ data = b'\x97A\x00\x13\xa2\x00@oG\xe4v\x1ais\x00\x01\x1c\xc0\x06\x18\x00\x02\x8c\x03\x96' # noqa info = self.zigbee._split_response(data) expected_info = { 'id': 'remote_at_response', 'frame_id': b'A', 'source_addr_long': b'\x00\x13\xa2\x00@oG\xe4', 'source_addr': b'v\x1a', 'command': b'is', 'status': b'\x00', 'parameter': [{ 'adc-1': 652, 'adc-2': 918, 'dio-10': False, 'dio-11': True, 'dio-12': True, 'dio-6': False, 'dio-7': False }] } self.assertEqual(info, expected_info) def test_parsing_may_encounter_field_which_does_not_exist(self): """ Some fields are optional and may not exist; parsing should not crash if/when they are not available. """ data = b'\x97A\x00\x13\xa2\x00@oG\xe4v\x1aIS\x01' info = self.zigbee._split_response(data) expected_info = { 'id': 'remote_at_response', 'frame_id': b'A', 'source_addr_long': b'\x00\x13\xa2\x00@oG\xe4', 'source_addr': b'v\x1a', 'command': b'IS', 'status': b'\x01', } self.assertEqual(info, expected_info) def test_nd_at_response_parameter_parsed(self): """ An at_response for an ND command must be parsed. """ data = b'\x88AND\x00v\x1a\x00\x13\xa2\x00@oG\xe4ENDPOINT-1\x00\xff\xfe\x01\x00\xc1\x05\x10\x1e' # noqa info = self.zigbee._split_response(data) expected_info = { 'id': 'at_response', 'frame_id': b'A', 'command': b'ND', 'status': b'\x00', 'parameter': { 'source_addr': b'\x76\x1a', 'source_addr_long': b'\x00\x13\xa2\x00\x40\x6f\x47\xe4', 'node_identifier': b'ENDPOINT-1', 'parent_address': b'\xff\xfe', 'device_type': b'\x01', 'status': b'\x00', 'profile_id': b'\xc1\x05', 'manufacturer': b'\x10\x1e', } } self.assertEqual(info, expected_info) def test_lowercase_nd_at_response_parameter_parsed(self): """ An at_response for a lowercase nd command must be parsed. """ data = b'\x88And\x00v\x1a\x00\x13\xa2\x00@oG\xe4ENDPOINT-1\x00\xff\xfe\x01\x00\xc1\x05\x10\x1e' # noqa info = self.zigbee._split_response(data) expected_info = { 'id': 'at_response', 'frame_id': b'A', 'command': b'nd', 'status': b'\x00', 'parameter': { 'source_addr': b'\x76\x1a', 'source_addr_long': b'\x00\x13\xa2\x00\x40\x6f\x47\xe4', 'node_identifier': b'ENDPOINT-1', 'parent_address': b'\xff\xfe', 'device_type': b'\x01', 'status': b'\x00', 'profile_id': b'\xc1\x05', 'manufacturer': b'\x10\x1e', } } self.assertEqual(info, expected_info) def test_split_route_information(self): data = b'\x8d\x12\x27\x9c\x93\x81\x7f\x00\x00\x00\x00\x13\xa2\x00\x40\x52\xaa\xaa\x00\x13\xa2\x00\x40\x52\xdd\xdd\x00\x13\xa2\x00\x40\x52\xbb\xbb\x00\x13\xa2\x00\x40\x52\xcc\xcc' # noqa info = self.zigbee._split_response(data) expected_info = { 'id': 'route_information', 'source_event': b'\x12', 'info_length': b'\x27', 'timestamp': b'\x9c\x93\x81\x7f', 'ack_timeout_count': b'\x00', 'tx_blocked_count': b'\x00', 'reserved': b'\x00', 'dest_addr': b'\x00\x13\xa2\x00\x40\x52\xaa\xaa', 'source_addr': b'\x00\x13\xa2\x00\x40\x52\xdd\xdd', 'responder_addr': b'\x00\x13\xa2\x00\x40\x52\xbb\xbb', 'receiver_addr': b'\x00\x13\xa2\x00\x40\x52\xcc\xcc', } self.assertEqual(info, expected_info)
def setUp(self): super(TestZigBee, self).setUp() self._patch_io = ioloop.IOLoop.current() self._patch_io.add_handler = Mock() serial_port = Serial() self.zigbee = ZigBee(serial_port, io_loop=self._patch_io)
class TestParseZigBeeIOData(unittest.TestCase): """ Test parsing ZigBee specific IO data """ def setUp(self): super(TestParseZigBeeIOData, self).setUp() patch_io = ioloop.IOLoop.current() patch_io.add_handler = Mock() serial_port = Serial() self.zigbee = ZigBee(serial_port, io_loop=patch_io) def test_parse_dio_adc(self): data = b'\x01\x08\x00\x0e\x08\x00\x00\x00\x02P\x02\x06' expected_results = [{'dio-11': True, 'adc-1': 0, 'adc-2': 592, 'adc-3': 518}] results = self.zigbee._parse_samples(data) self.assertEqual(results, expected_results) def test_parse_samples_ticket_44(self): """ This example is from ticket 44 on Google Code: https://code.google.com/p/python-xbee/issues/detail?id=44 The author claims the given data is generated by an Xbee Pro 900HP module, but I could only find a definition for packet with a response type of 0x92 in the XBee ZB specification. """ data = (b'\x01' + # Number of samples b'\x10\x00' + # Digital I/O mask (CD/DIO12 enabled) b'\x0E' + # ADC 1,2,3 enabled b'\x10\x00' + # DIO12 is high b'\x03\xA4' + # ADC1 = 932 b'\x01\x31' + # ADC2 = 305 b'\x03\x31') # ADC3 = 817 expected_results = [{'dio-12': True, 'adc-1': 932, 'adc-2': 305, 'adc-3': 817}] results = self.zigbee._parse_samples(data) self.assertEqual(results, expected_results) def test_parse_dio_adc_supply_voltage_not_clamped(self): """ When bit 7 on the ADC mask is set, the supply voltage is included in the ADC I/O sample section. This sample may exceed 10 bits of precision, even though all other ADC channels are limited to a range of 0-1.2v with 10 bits of precision. I assume that a voltage divider and the firmware is used internally to compute the actual Vcc voltage. Therefore, the I/O sampling routine must not clamp this ADC channel to 10 bits of precision. """ data = b'\x01\x00\x00\x80\x0D\x18' expected_results = [{'adc-7': 0xD18}] results = self.zigbee._parse_samples(data) self.assertEqual(results, expected_results)
class TestZigBee(unittest.TestCase): """ Tests ZigBee-specific features """ def setUp(self): super(TestZigBee, self).setUp() self._patch_io = ioloop.IOLoop.current() self._patch_io.add_handler = Mock() serial_port = Serial() self.zigbee = ZigBee(serial_port, io_loop=self._patch_io) def test_send(self): """ Test send() with AT command. """ device = Serial() xbee = ZigBee(device, io_loop=self._patch_io) xbee.send('at', command='MY') result = device.get_data_written() expected = b'~\x00\x04\x08\x01MYP' self.assertEqual(result, expected) def test_null_terminated_field(self): """ Packets with null-terminated fields should be properly parsed """ expected_data = b'\x01\x02\x03\x04' terminator = b'\x00' node_identifier = b'\x95' + b'\x00' * 21 + expected_data + \ terminator + b'\x00' * 8 data = self.zigbee._split_response(node_identifier) self.assertEqual(data['node_id'], expected_data) def test_split_node_identification_identifier(self): data = b'\x95\x00\x13\xa2\x00\x40\x52\x2b\xaa\x7d\x84\x02\x7d\x84\x00\x13\xa2\x00\x40\x52\x2b\xaa\x20\x00\xff\xfe\x01\x01\xc1\x05\x10\x1e' # noqa info = self.zigbee._split_response(data) expected_info = { 'id': 'node_id_indicator', 'sender_addr_long': b'\x00\x13\xa2\x00\x40\x52\x2b\xaa', 'sender_addr': b'\x7d\x84', 'options': b'\x02', 'source_addr': b'\x7d\x84', 'source_addr_long': b'\x00\x13\xa2\x00\x40\x52\x2b\xaa', 'node_id': b' ', 'parent_source_addr': b'\xff\xfe', 'device_type': b'\x01', 'source_event': b'\x01', 'digi_profile_id': b'\xc1\x05', 'manufacturer_id': b'\x10\x1e', } self.assertEqual(info, expected_info) def test_split_node_identification_identifier2(self): data = b'\x95\x00\x13\xa2\x00\x40\x52\x2b\xaa\x7d\x84\x02\x7d\x84\x00\x13\xa2\x00\x40\x52\x2b\xaaCoordinator\x00\xff\xfe\x01\x01\xc1\x05\x10\x1e' # noqa info = self.zigbee._split_response(data) expected_info = { 'id': 'node_id_indicator', 'sender_addr_long': b'\x00\x13\xa2\x00\x40\x52\x2b\xaa', 'sender_addr': b'\x7d\x84', 'options': b'\x02', 'source_addr': b'\x7d\x84', 'source_addr_long': b'\x00\x13\xa2\x00\x40\x52\x2b\xaa', 'node_id': b'Coordinator', 'parent_source_addr': b'\xff\xfe', 'device_type': b'\x01', 'source_event': b'\x01', 'digi_profile_id': b'\xc1\x05', 'manufacturer_id': b'\x10\x1e', } self.assertEqual(info, expected_info) def test_is_remote_at_response_parameter_parsed_as_io_samples(self): """ A remote AT command of IS, to take a sample immediately and respond with the results, must be appropriately parsed for IO data. """ data = b'\x97A\x00\x13\xa2\x00@oG\xe4v\x1aIS\x00\x01\x1c\xc0\x06\x18\x00\x02\x8c\x03\x96' # noqa info = self.zigbee._split_response(data) expected_info = { 'id': 'remote_at_response', 'frame_id': b'A', 'source_addr_long': b'\x00\x13\xa2\x00@oG\xe4', 'source_addr': b'v\x1a', 'command': b'IS', 'status': b'\x00', 'parameter': [{'adc-1': 652, 'adc-2': 918, 'dio-10': False, 'dio-11': True, 'dio-12': True, 'dio-6': False, 'dio-7': False }] } self.assertEqual(info, expected_info) def test_lowercase_is_remote_at_response_parameter_parsed_as_io_samples(self): # noqa """ A remote AT command of lowercase is, to take a sample immediately and respond with the results, must be appropriately parsed for IO data. """ data = b'\x97A\x00\x13\xa2\x00@oG\xe4v\x1ais\x00\x01\x1c\xc0\x06\x18\x00\x02\x8c\x03\x96' # noqa info = self.zigbee._split_response(data) expected_info = { 'id': 'remote_at_response', 'frame_id': b'A', 'source_addr_long': b'\x00\x13\xa2\x00@oG\xe4', 'source_addr': b'v\x1a', 'command': b'is', 'status': b'\x00', 'parameter': [{'adc-1': 652, 'adc-2': 918, 'dio-10': False, 'dio-11': True, 'dio-12': True, 'dio-6': False, 'dio-7': False }] } self.assertEqual(info, expected_info) def test_parsing_may_encounter_field_which_does_not_exist(self): """ Some fields are optional and may not exist; parsing should not crash if/when they are not available. """ data = b'\x97A\x00\x13\xa2\x00@oG\xe4v\x1aIS\x01' info = self.zigbee._split_response(data) expected_info = { 'id': 'remote_at_response', 'frame_id': b'A', 'source_addr_long': b'\x00\x13\xa2\x00@oG\xe4', 'source_addr': b'v\x1a', 'command': b'IS', 'status': b'\x01', } self.assertEqual(info, expected_info) def test_nd_at_response_parameter_parsed(self): """ An at_response for an ND command must be parsed. """ data = b'\x88AND\x00v\x1a\x00\x13\xa2\x00@oG\xe4ENDPOINT-1\x00\xff\xfe\x01\x00\xc1\x05\x10\x1e' # noqa info = self.zigbee._split_response(data) expected_info = { 'id': 'at_response', 'frame_id': b'A', 'command': b'ND', 'status': b'\x00', 'parameter': { 'source_addr': b'\x76\x1a', 'source_addr_long': b'\x00\x13\xa2\x00\x40\x6f\x47\xe4', 'node_identifier': b'ENDPOINT-1', 'parent_address': b'\xff\xfe', 'device_type': b'\x01', 'status': b'\x00', 'profile_id': b'\xc1\x05', 'manufacturer': b'\x10\x1e', } } self.assertEqual(info, expected_info) def test_lowercase_nd_at_response_parameter_parsed(self): """ An at_response for a lowercase nd command must be parsed. """ data = b'\x88And\x00v\x1a\x00\x13\xa2\x00@oG\xe4ENDPOINT-1\x00\xff\xfe\x01\x00\xc1\x05\x10\x1e' # noqa info = self.zigbee._split_response(data) expected_info = { 'id': 'at_response', 'frame_id': b'A', 'command': b'nd', 'status': b'\x00', 'parameter': { 'source_addr': b'\x76\x1a', 'source_addr_long': b'\x00\x13\xa2\x00\x40\x6f\x47\xe4', 'node_identifier': b'ENDPOINT-1', 'parent_address': b'\xff\xfe', 'device_type': b'\x01', 'status': b'\x00', 'profile_id': b'\xc1\x05', 'manufacturer': b'\x10\x1e', } } self.assertEqual(info, expected_info) def test_split_route_information(self): data = b'\x8d\x12\x27\x9c\x93\x81\x7f\x00\x00\x00\x00\x13\xa2\x00\x40\x52\xaa\xaa\x00\x13\xa2\x00\x40\x52\xdd\xdd\x00\x13\xa2\x00\x40\x52\xbb\xbb\x00\x13\xa2\x00\x40\x52\xcc\xcc' # noqa info = self.zigbee._split_response(data) expected_info = { 'id': 'route_information', 'source_event': b'\x12', 'info_length': b'\x27', 'timestamp': b'\x9c\x93\x81\x7f', 'ack_timeout_count': b'\x00', 'tx_blocked_count': b'\x00', 'reserved': b'\x00', 'dest_addr': b'\x00\x13\xa2\x00\x40\x52\xaa\xaa', 'source_addr': b'\x00\x13\xa2\x00\x40\x52\xdd\xdd', 'responder_addr': b'\x00\x13\xa2\x00\x40\x52\xbb\xbb', 'receiver_addr': b'\x00\x13\xa2\x00\x40\x52\xcc\xcc', } self.assertEqual(info, expected_info)