class TestZigBee(unittest.TestCase):
    """
    Tests ZigBee-specific features
    """

    def setUp(self):
        self.zigbee = ZigBee(None)

    def test_null_terminated_field(self):
        """
        Packets with null-terminated fields
        should be properly parsed
        """
        expected_data = '\x01\x02\x03\x04'
        terminator = '\x00'
        node_identifier = '\x95' + '\x00' * 21 + expected_data + terminator + '\x00' * 8

        data = self.zigbee._split_response(node_identifier)

        self.assertEqual(data['node_id'], expected_data)

    def test_split_node_identification_identifier(self):
            data = '\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'
            info = self.zigbee._split_response(data)
            expected_info = {
                'id': 'node_id_indicator',
                'sender_addr_long': '\x00\x13\xa2\x00\x40\x52\x2b\xaa',
                'sender_addr': '\x7d\x84',
                'options': '\x02',
                'source_addr': '\x7d\x84',
                'source_addr_long': '\x00\x13\xa2\x00\x40\x52\x2b\xaa',
                'node_id': ' ',
                'parent_source_addr': '\xff\xfe',
                'device_type': '\x01',
                'source_event': '\x01',
                'digi_profile_id': '\xc1\x05',
                'manufacturer_id': '\x10\x1e',
            }
            
            self.assertEqual(info, expected_info)
            
    def test_split_node_identification_identifier2(self):
            data = '\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'
            info = self.zigbee._split_response(data)
            expected_info = {
                'id': 'node_id_indicator',
                'sender_addr_long': '\x00\x13\xa2\x00\x40\x52\x2b\xaa',
                'sender_addr': '\x7d\x84',
                'options': '\x02',
                'source_addr': '\x7d\x84',
                'source_addr_long': '\x00\x13\xa2\x00\x40\x52\x2b\xaa',
                'node_id': 'Coordinator',
                'parent_source_addr': '\xff\xfe',
                'device_type': '\x01',
                'source_event': '\x01',
                'digi_profile_id': '\xc1\x05',
                'manufacturer_id': '\x10\x1e',
            }
            
            self.assertEqual(info, expected_info)
Example #2
0
class TestZigBee(unittest.TestCase):
    """
    Tests ZigBee-specific features
    """
    def setUp(self):
        self.zigbee = ZigBee(None)

    def test_null_terminated_field(self):
        """
        Packets with null-terminated fields
        should be properly parsed
        """
        expected_data = '\x01\x02\x03\x04'
        terminator = '\x00'
        node_identifier = '\x95' + '\x00' * 21 + expected_data + terminator + '\x00' * 8

        data = self.zigbee._split_response(node_identifier)

        self.assertEqual(data['node_id'], expected_data)

    def test_split_node_identification_identifier(self):
        data = '\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'
        info = self.zigbee._split_response(data)
        expected_info = {
            'id': 'node_id_indicator',
            'sender_addr_long': '\x00\x13\xa2\x00\x40\x52\x2b\xaa',
            'sender_addr': '\x7d\x84',
            'options': '\x02',
            'source_addr': '\x7d\x84',
            'source_addr_long': '\x00\x13\xa2\x00\x40\x52\x2b\xaa',
            'node_id': ' ',
            'parent_source_addr': '\xff\xfe',
            'device_type': '\x01',
            'source_event': '\x01',
            'digi_profile_id': '\xc1\x05',
            'manufacturer_id': '\x10\x1e',
        }

        self.assertEqual(info, expected_info)

    def test_split_node_identification_identifier2(self):
        data = '\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'
        info = self.zigbee._split_response(data)
        expected_info = {
            'id': 'node_id_indicator',
            'sender_addr_long': '\x00\x13\xa2\x00\x40\x52\x2b\xaa',
            'sender_addr': '\x7d\x84',
            'options': '\x02',
            'source_addr': '\x7d\x84',
            'source_addr_long': '\x00\x13\xa2\x00\x40\x52\x2b\xaa',
            'node_id': 'Coordinator',
            'parent_source_addr': '\xff\xfe',
            'device_type': '\x01',
            'source_event': '\x01',
            'digi_profile_id': '\xc1\x05',
            'manufacturer_id': '\x10\x1e',
        }

        self.assertEqual(info, expected_info)
Example #3
0
class TestZigBee(unittest.TestCase):
    """
    Tests ZigBee-specific features
    """
    def setUp(self):
        self.zigbee = ZigBee(None)

    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'
        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'
        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'
        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': [{
                'dio-10': False,
                'adc-2': 918,
                'dio-6': False,
                'dio-11': True,
                'adc-1': 652
            }]
        }

        self.assertEqual(info, expected_info)

    def test_lowercase_is_remote_at_response_parameter_parsed_as_io_samples(
            self):
        """
        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'
        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': [{
                'dio-10': False,
                'adc-2': 918,
                'dio-6': False,
                'dio-11': True,
                'adc-1': 652
            }]
        }

        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'
        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'
        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)
Example #4
0
class TestZigBee(unittest.TestCase):
    """
    Tests ZigBee-specific features
    """

    def setUp(self):
        self.zigbee = ZigBee(None)

    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"
        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"
        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"
        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": [{"dio-10": False, "adc-2": 918, "dio-6": False, "dio-11": True, "adc-1": 652}],
        }

        self.assertEqual(info, expected_info)

    def test_lowercase_is_remote_at_response_parameter_parsed_as_io_samples(self):
        """
        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"
        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": [{"dio-10": False, "adc-2": 918, "dio-6": False, "dio-11": True, "adc-1": 652}],
        }

        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"
        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"
        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)
Example #5
0
class TestZigBee(unittest.TestCase):
    """
    Tests ZigBee-specific features
    """

    def setUp(self):
        self.zigbee = ZigBee(None)

    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'
            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'
            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'
        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):
        """
        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'
        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'
        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'
        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)