def __init__(self, xknx): """Initialize CEMIFrame object.""" super(CEMIFrame, self).__init__(xknx) self.code = CEMIMessageCode.L_DATA_IND self.flags = 0 self.cmd = APCICommand.GROUP_READ self.src_addr = GroupAddress(None) self.dst_addr = GroupAddress(None) self.mpdu_len = 0 self.payload = None
def __init__(self, xknx): """Initialize CEMIFrame object.""" super().__init__(xknx) self.code = CEMIMessageCode.L_DATA_IND self.flags = 0 self.cmd = APCICommand.GROUP_READ self.src_addr = GroupAddress(None) self.dst_addr = GroupAddress(None) self.mpdu_len = 0 self.payload = None
def test_valid_command(frame): """Test for valid frame parsing""" packet_len = frame.from_knx(get_data(0x29, 0, 0, 0, 0, 1, 0, [])) assert frame.code == CEMIMessageCode.L_DATA_IND assert frame.cmd == APCICommand.GROUP_READ assert frame.flags == 0 assert frame.mpdu_len == 1 assert frame.payload == DPTBinary(0) assert frame.src_addr == PhysicalAddress(0) assert frame.dst_addr == PhysicalAddress(0) assert packet_len == 11
def __init__(self): """Initialize DIBDeviceInformation class.""" super(DIBDeviceInformation, self).__init__() self.knx_medium = KNXMedium.TP1 self.programming_mode = False self.individual_address = PhysicalAddress(None) self.installation_number = 0 self.project_number = 0 self.serial_number = "" self.multicast_address = "224.0.23.12" self.mac_address = "" self.name = ""
def test_EndTOEnd_group_response(self): """Test parsing and streaming CEMIFrame KNX/IP packet, group response.""" # Incoming state raw = ((0x06, 0x10, 0x05, 0x30, 0x00, 0x11, 0x29, 0x00, 0xbc, 0xd0, 0x13, 0x01, 0x01, 0x88, 0x01, 0x00, 0x41)) xknx = XKNX(loop=self.loop) knxipframe = KNXIPFrame(xknx) knxipframe.from_knx(raw) telegram = knxipframe.body.telegram self.assertEqual( telegram, Telegram(GroupAddress("392"), TelegramType.GROUP_RESPONSE, payload=DPTBinary(1))) knxipframe2 = KNXIPFrame(xknx) knxipframe2.init(KNXIPServiceType.ROUTING_INDICATION) knxipframe2.body.src_addr = PhysicalAddress("1.3.1") knxipframe2.body.telegram = telegram knxipframe2.body.set_hops(5) knxipframe2.normalize() self.assertEqual(knxipframe2.header.to_knx(), list(raw[0:6])) self.assertEqual(knxipframe2.body.to_knx(), list(raw[6:])) self.assertEqual(knxipframe2.to_knx(), list(raw))
def test_telegram_set(self): """Test parsing and streaming CEMIFrame KNX/IP packet with DPTArray/DPTTime as payload.""" xknx = XKNX(loop=self.loop) knxipframe = KNXIPFrame(xknx) knxipframe.init(KNXIPServiceType.ROUTING_INDICATION) knxipframe.body.src_addr = PhysicalAddress("1.2.2") telegram = Telegram() telegram.group_address = GroupAddress(337) telegram.payload = DPTArray(DPTTime().to_knx({ 'hours': 13, 'minutes': 23, 'seconds': 42 })) knxipframe.body.telegram = telegram knxipframe.body.set_hops(5) knxipframe.normalize() raw = ((0x06, 0x10, 0x05, 0x30, 0x00, 0x14, 0x29, 0x00, 0xbc, 0xd0, 0x12, 0x02, 0x01, 0x51, 0x04, 0x00, 0x80, 13, 23, 42)) self.assertEqual(knxipframe.header.to_knx(), list(raw[0:6])) self.assertEqual(knxipframe.body.to_knx(), list(raw[6:])) self.assertEqual(knxipframe.to_knx(), list(raw))
def __init__(self, config=None, loop=None, own_address=PhysicalAddress(DEFAULT_ADDRESS), address_format=GroupAddressType.LONG, telegram_received_cb=None, device_updated_cb=None): """Initialize XKNX class.""" # pylint: disable=too-many-arguments self.devices = Devices() self.telegrams = asyncio.Queue() self.loop = loop or asyncio.get_event_loop() self.sigint_received = asyncio.Event() self.telegram_queue = TelegramQueue(self) self.state_updater = None self.knxip_interface = None self.started = False self.address_format = address_format self.own_address = own_address self.logger = logging.getLogger('xknx.log') self.knx_logger = logging.getLogger('xknx.knx') self.telegram_logger = logging.getLogger('xknx.telegram') if config is not None: Config(self).read(config) if telegram_received_cb is not None: self.telegram_queue.register_telegram_received_cb( telegram_received_cb) if device_updated_cb is not None: self.devices.register_device_updated_cb(device_updated_cb)
async def main(): """Connect to a tunnel, send 2 telegrams and disconnect.""" xknx = XKNX() gatewayscanner = GatewayScanner(xknx) gateways = await gatewayscanner.scan() if not gateways: print("No Gateways found") return gateway = gateways[0] src_address = PhysicalAddress("15.15.249") print("Connecting to {}:{} from {}".format(gateway.ip_addr, gateway.port, gateway.local_ip)) tunnel = Tunnel(xknx, src_address, local_ip=gateway.local_ip, gateway_ip=gateway.ip_addr, gateway_port=gateway.port) await tunnel.connect_udp() await tunnel.connect() await tunnel.send_telegram( Telegram(GroupAddress('1/0/15'), payload=DPTBinary(1))) await asyncio.sleep(2) await tunnel.send_telegram( Telegram(GroupAddress('1/0/15'), payload=DPTBinary(0))) await asyncio.sleep(2) await tunnel.connectionstate() await tunnel.disconnect()
def test_tunnelling(self): """Test tunnelling from KNX bus.""" # pylint: disable=too-many-locals xknx = XKNX(loop=self.loop) communication_channel_id = 23 udp_client = UDPClient(xknx, ("192.168.1.1", 0), ("192.168.1.2", 1234)) telegram = Telegram(GroupAddress('1/2/3'), payload=DPTArray((0x1, 0x2, 0x3))) sequence_counter = 42 src_address = PhysicalAddress('2.2.2') tunnelling = Tunnelling(xknx, udp_client, telegram, src_address, sequence_counter, communication_channel_id) tunnelling.timeout_in_seconds = 0 self.assertEqual(tunnelling.awaited_response_class, TunnellingAck) self.assertEqual(tunnelling.communication_channel_id, communication_channel_id) # Expected KNX/IP-Frame: exp_knxipframe = KNXIPFrame(xknx) exp_knxipframe.init(KNXIPServiceType.TUNNELLING_REQUEST) exp_knxipframe.body.cemi.telegram = telegram exp_knxipframe.body.cemi.src_addr = src_address exp_knxipframe.body.communication_channel_id = communication_channel_id exp_knxipframe.body.sequence_counter = sequence_counter exp_knxipframe.normalize() print(exp_knxipframe) with patch('xknx.io.UDPClient.send') as mock_udp_send, \ patch('xknx.io.UDPClient.getsockname') as mock_udp_getsockname: mock_udp_getsockname.return_value = ("192.168.1.3", 4321) self.loop.run_until_complete(asyncio.Task(tunnelling.start())) mock_udp_send.assert_called_with(exp_knxipframe) # Response KNX/IP-Frame with wrong type wrong_knxipframe = KNXIPFrame(xknx) wrong_knxipframe.init(KNXIPServiceType.CONNECTIONSTATE_REQUEST) with patch('logging.Logger.warning') as mock_warning: tunnelling.response_rec_callback(wrong_knxipframe, None) mock_warning.assert_called_with('Cant understand knxipframe') # Response KNX/IP-Frame with error: err_knxipframe = KNXIPFrame(xknx) err_knxipframe.init(KNXIPServiceType.TUNNELLING_ACK) err_knxipframe.body.status_code = ErrorCode.E_CONNECTION_ID with patch('logging.Logger.warning') as mock_warning: tunnelling.response_rec_callback(err_knxipframe, None) mock_warning.assert_called_with( "Error: KNX bus responded to request of type '%s' with error in '%s': %s", type(tunnelling).__name__, type(err_knxipframe.body).__name__, ErrorCode.E_CONNECTION_ID) # Correct Response KNX/IP-Frame: res_knxipframe = KNXIPFrame(xknx) res_knxipframe.init(KNXIPServiceType.TUNNELLING_ACK) with patch('logging.Logger.debug') as mock_debug: tunnelling.response_rec_callback(res_knxipframe, None) mock_debug.assert_called_with( 'Success: received correct answer from KNX bus: %s', ErrorCode.E_NO_ERROR) self.assertTrue(tunnelling.success)
def parse_general(self, doc): """Parse the general section of xknx.yaml.""" if "general" in doc: if "own_address" in doc["general"]: self.xknx.own_address = \ PhysicalAddress(doc["general"]["own_address"]) if "rate_limit" in doc["general"]: self.xknx.rate_limit = \ doc["general"]["rate_limit"]
def from_knx_data_link_layer(self, cemi): """Parse L_DATA_IND, CEMIMessageCode.L_Data_REQ, CEMIMessageCode.L_DATA_CON.""" if len(cemi) < 11: raise CouldNotParseKNXIP("CEMI too small") # AddIL (Additional Info Length), as specified within # KNX Chapter 3.6.3/4.1.4.3 "Additional information." # Additional information is not yet parsed. addil = cemi[1] self.flags = cemi[2 + addil] * 256 + cemi[3 + addil] self.src_addr = PhysicalAddress((cemi[4 + addil], cemi[5 + addil])) if self.flags & CEMIFlags.DESTINATION_GROUP_ADDRESS: self.dst_addr = GroupAddress((cemi[6 + addil], cemi[7 + addil]), levels=self.xknx.address_format) else: self.dst_addr = PhysicalAddress((cemi[6 + addil], cemi[7 + addil])) self.mpdu_len = cemi[8 + addil] # TPCI (transport layer control information) -> First 14 bit # APCI (application layer control information) -> Last 10 bit tpci_apci = cemi[9 + addil] * 256 + cemi[10 + addil] self.cmd = APCICommand(tpci_apci & 0xFFC0) apdu = cemi[10 + addil:] if len(apdu) != self.mpdu_len: raise CouldNotParseKNXIP( "APDU LEN should be {} but is {}".format( self.mpdu_len, len(apdu))) if len(apdu) == 1: apci = tpci_apci & DPTBinary.APCI_BITMASK self.payload = DPTBinary(apci) else: self.payload = DPTArray(cemi[11 + addil:]) return 10 + addil + len(apdu)
def from_knx(self, raw): """Parse/deserialize from KNX/IP raw data.""" if len(raw) < DIBDeviceInformation.LENGTH: raise CouldNotParseKNXIP("wrong connection header length") if raw[0] != DIBDeviceInformation.LENGTH: raise CouldNotParseKNXIP("wrong connection header length") if DIBTypeCode(raw[1]) != DIBTypeCode.DEVICE_INFO: raise CouldNotParseKNXIP("DIB is no device info") self.knx_medium = KNXMedium(raw[2]) # last bit of device_status. All other bits are unused self.programming_mode = bool(raw[3]) self.individual_address = \ PhysicalAddress((raw[4], raw[5])) installation_project_identifier = raw[6]*256+raw[7] self.project_number = installation_project_identifier >> 4 self.installation_number = installation_project_identifier & 15 self.serial_number = ":".join('%02x' % i for i in raw[8:14]) self.multicast_address = ".".join('%i' % i for i in raw[14:18]) self.mac_address = ":".join('%02x' % i for i in raw[18:24]) self.name = "".join(map(chr, raw[24:54])).rstrip('\0') return DIBDeviceInformation.LENGTH
def test_from_knx(self): """Test parsing and streaming CEMIFrame KNX/IP packet (payload=0xf0).""" raw = ((0x06, 0x10, 0x05, 0x30, 0x00, 0x12, 0x29, 0x00, 0xbc, 0xd0, 0x12, 0x02, 0x01, 0x51, 0x02, 0x00, 0x40, 0xf0)) xknx = XKNX(loop=self.loop) knxipframe = KNXIPFrame(xknx) knxipframe.from_knx(raw) self.assertTrue(isinstance(knxipframe.body, CEMIFrame)) self.assertEqual(knxipframe.body.src_addr, PhysicalAddress("1.2.2")) self.assertEqual(knxipframe.body.dst_addr, GroupAddress(337)) self.assertEqual(len(knxipframe.body.payload.value), 1) self.assertEqual(knxipframe.body.payload.value[0], 0xf0)
def test_maximum_apci(self): """Test parsing and streaming CEMIFrame KNX/IP packet, testing maximum APCI.""" telegram = Telegram() telegram.group_address = GroupAddress(337) telegram.payload = DPTBinary(DPTBinary.APCI_MAX_VALUE) xknx = XKNX(loop=self.loop) knxipframe = KNXIPFrame(xknx) knxipframe.init(KNXIPServiceType.ROUTING_INDICATION) knxipframe.body.src_addr = PhysicalAddress("1.3.1") knxipframe.body.telegram = telegram knxipframe.body.set_hops(5) knxipframe.normalize() raw = ((0x06, 0x10, 0x05, 0x30, 0x00, 0x11, 0x29, 0x00, 0xbc, 0xd0, 0x13, 0x01, 0x01, 0x51, 0x01, 0x00, 0xbf)) self.assertEqual(knxipframe.to_knx(), list(raw)) knxipframe2 = KNXIPFrame(xknx) knxipframe2.init(KNXIPServiceType.ROUTING_INDICATION) knxipframe2.from_knx(knxipframe.to_knx()) self.assertEqual(knxipframe2.body.telegram, telegram)
def test_EndTOEnd_group_write_1byte(self): """Test parsing and streaming CEMIFrame KNX/IP packet, dimm light in my kitchen.""" # Dimm Kitchen L1 to 0x65 raw = ((0x06, 0x10, 0x05, 0x30, 0x00, 0x12, 0x29, 0x00, 0xbc, 0xd0, 0xff, 0xf9, 0x01, 0x4b, 0x02, 0x00, 0x80, 0x65)) xknx = XKNX(loop=self.loop) knxipframe = KNXIPFrame(xknx) knxipframe.from_knx(raw) telegram = knxipframe.body.telegram self.assertEqual(telegram, Telegram(GroupAddress("331"), payload=DPTArray(0x65))) knxipframe2 = KNXIPFrame(xknx) knxipframe2.init(KNXIPServiceType.ROUTING_INDICATION) knxipframe2.body.src_addr = PhysicalAddress("15.15.249") knxipframe2.body.telegram = telegram knxipframe2.body.set_hops(5) knxipframe2.normalize() self.assertEqual(knxipframe2.header.to_knx(), list(raw[0:6])) self.assertEqual(knxipframe2.body.to_knx(), list(raw[6:])) self.assertEqual(knxipframe2.to_knx(), list(raw))
def test_EndTOEnd_group_read(self): """Test parsing and streaming CEMIFrame KNX/IP packet, group read.""" # State request raw = ((0x06, 0x10, 0x05, 0x30, 0x00, 0x11, 0x29, 0x00, 0xbc, 0xd0, 0xff, 0xf9, 0x01, 0xb8, 0x01, 0x00, 0x00)) xknx = XKNX(loop=self.loop) knxipframe = KNXIPFrame(xknx) knxipframe.from_knx(raw) telegram = knxipframe.body.telegram self.assertEqual( telegram, Telegram(GroupAddress("440"), TelegramType.GROUP_READ)) knxipframe2 = KNXIPFrame(xknx) knxipframe2.init(KNXIPServiceType.ROUTING_INDICATION) knxipframe2.body.src_addr = PhysicalAddress("15.15.249") knxipframe2.body.telegram = telegram knxipframe2.body.set_hops(5) knxipframe2.normalize() self.assertEqual(knxipframe2.header.to_knx(), list(raw[0:6])) self.assertEqual(knxipframe2.body.to_knx(), list(raw[6:])) self.assertEqual(knxipframe2.to_knx(), list(raw))
def test_device_info(self): """Test parsing of device info.""" raw = ((0x36, 0x01, 0x02, 0x00, 0x11, 0x00, 0x23, 0x42, 0x13, 0x37, 0x13, 0x37, 0x13, 0x37, 0xE0, 0x00, 0x17, 0x0c, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x47, 0x69, 0x72, 0x61, 0x20, 0x4b, 0x4e, 0x58, 0x2f, 0x49, 0x50, 0x2d, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00)) dib = DIB.determine_dib(raw) self.assertTrue(isinstance(dib, DIBDeviceInformation)) self.assertEqual(dib.from_knx(raw), DIBDeviceInformation.LENGTH) self.assertEqual(dib.knx_medium, KNXMedium.TP1) self.assertEqual(dib.programming_mode, False) self.assertEqual(dib.individual_address, PhysicalAddress('1.1.0')) self.assertEqual(dib.name, 'Gira KNX/IP-Router') self.assertEqual(dib.mac_address, '00:01:02:03:04:05') self.assertEqual(dib.multicast_address, '224.0.23.12') self.assertEqual(dib.serial_number, '13:37:13:37:13:37') self.assertEqual(dib.project_number, 564) self.assertEqual(dib.installation_number, 2) self.assertEqual(dib.to_knx(), list(raw))
def fake_router_search_response(xknx: XKNX) -> SearchResponse: """Return the SearchResponse of a KNX/IP Router.""" _frame_header = KNXIPHeader(xknx) _frame_header.service_type_ident = KNXIPServiceType.SEARCH_RESPONSE _frame_body = SearchResponse(xknx) _frame_body.control_endpoint = HPAI(ip_addr="192.168.42.10", port=3671) _device_information = DIBDeviceInformation() _device_information.name = "Gira KNX/IP-Router" _device_information.serial_number = "11:22:33:44:55:66" _device_information.individual_address = PhysicalAddress("1.1.0") _device_information.mac_address = "01:02:03:04:05:06" _svc_families = DIBSuppSVCFamilies() _svc_families.families.append( DIBSuppSVCFamilies.Family(name=DIBServiceFamily.CORE, version=1)) _svc_families.families.append( DIBSuppSVCFamilies.Family(name=DIBServiceFamily.DEVICE_MANAGEMENT, version=2)) _svc_families.families.append( DIBSuppSVCFamilies.Family(name=DIBServiceFamily.TUNNELING, version=1)) _svc_families.families.append( DIBSuppSVCFamilies.Family(name=DIBServiceFamily.ROUTING, version=1)) _svc_families.families.append( DIBSuppSVCFamilies.Family( name=DIBServiceFamily.REMOTE_CONFIGURATION_DIAGNOSIS, version=1)) _frame_body.dibs.append(_device_information) _frame_body.dibs.append(_svc_families) _frame_header.set_length(_frame_body) search_response = KNXIPFrame(xknx) search_response.init(KNXIPServiceType.SEARCH_RESPONSE) search_response.header = _frame_header search_response.body = _frame_body search_response.normalize() return search_response
def test_dib_device_informatio(self): """Test string representation of DIBDeviceInformation.""" dib = DIBDeviceInformation() dib.knx_medium = KNXMedium.TP1 dib.programming_mode = False dib.individual_address = PhysicalAddress('1.1.0') dib.name = 'Gira KNX/IP-Router' dib.mac_address = '00:01:02:03:04:05' dib.multicast_address = '224.0.23.12' dib.serial_number = '13:37:13:37:13:37' dib.project_number = 564 dib.installation_number = 2 self.assertEqual( str(dib), '<DIBDeviceInformation \n' '\tknx_medium="KNXMedium.TP1" \n' '\tprogramming_mode="False" \n' '\tindividual_address="PhysicalAddress("1.1.0")" \n' '\tinstallation_number="2" \n' '\tproject_number="564" \n' '\tserial_number="13:37:13:37:13:37" \n' '\tmulticast_address="224.0.23.12" \n' '\tmac_address="00:01:02:03:04:05" \n' '\tname="Gira KNX/IP-Router" />')
def test_EndTOEnd_group_write_2bytes(self): """Test parsing and streaming CEMIFrame KNX/IP packet, setting value of thermostat.""" # Incoming Temperature from thermostat raw = ((0x06, 0x10, 0x05, 0x30, 0x00, 0x13, 0x29, 0x00, 0xbc, 0xd0, 0x14, 0x02, 0x08, 0x01, 0x03, 0x00, 0x80, 0x07, 0xc1)) xknx = XKNX(loop=self.loop) knxipframe = KNXIPFrame(xknx) knxipframe.from_knx(raw) telegram = knxipframe.body.telegram self.assertEqual( telegram, Telegram(GroupAddress("2049"), payload=DPTArray(DPTTemperature().to_knx(19.85)))) knxipframe2 = KNXIPFrame(xknx) knxipframe2.init(KNXIPServiceType.ROUTING_INDICATION) knxipframe2.body.src_addr = PhysicalAddress("1.4.2") knxipframe2.body.telegram = telegram knxipframe2.body.set_hops(5) knxipframe2.normalize() self.assertEqual(knxipframe2.header.to_knx(), list(raw[0:6])) self.assertEqual(knxipframe2.body.to_knx(), list(raw[6:])) self.assertEqual(knxipframe2.to_knx(), list(raw))
def test_config_general(self): """Test reading general section from config file.""" self.assertEqual(TestConfig.xknx.own_address, PhysicalAddress('15.15.249')) self.assertEqual(TestConfig.xknx.rate_limit, 18)
class DIBDeviceInformation(DIB): """Class for serialization and deserialization of KNX DIB Device Information Block.""" # pylint: disable=too-many-instance-attributes LENGTH = 54 def __init__(self): """Initialize DIBDeviceInformation class.""" super(DIBDeviceInformation, self).__init__() self.knx_medium = KNXMedium.TP1 self.programming_mode = False self.individual_address = PhysicalAddress(None) self.installation_number = 0 self.project_number = 0 self.serial_number = "" self.multicast_address = "224.0.23.12" self.mac_address = "" self.name = "" def calculated_length(self): """Get length of KNX/IP object.""" return DIBDeviceInformation.LENGTH def from_knx(self, raw): """Parse/deserialize from KNX/IP raw data.""" if len(raw) < DIBDeviceInformation.LENGTH: raise CouldNotParseKNXIP("wrong connection header length") if raw[0] != DIBDeviceInformation.LENGTH: raise CouldNotParseKNXIP("wrong connection header length") if DIBTypeCode(raw[1]) != DIBTypeCode.DEVICE_INFO: raise CouldNotParseKNXIP("DIB is no device info") self.knx_medium = KNXMedium(raw[2]) # last bit of device_status. All other bits are unused self.programming_mode = bool(raw[3]) self.individual_address = \ PhysicalAddress((raw[4], raw[5])) installation_project_identifier = raw[6]*256+raw[7] self.project_number = installation_project_identifier >> 4 self.installation_number = installation_project_identifier & 15 self.serial_number = ":".join('%02x' % i for i in raw[8:14]) self.multicast_address = ".".join('%i' % i for i in raw[14:18]) self.mac_address = ":".join('%02x' % i for i in raw[18:24]) self.name = "".join(map(chr, raw[24:54])).rstrip('\0') return DIBDeviceInformation.LENGTH def to_knx(self): """Serialize to KNX/IP raw data.""" def hex_notation_to_knx(serial_number): """Serialize hex notation.""" for part in serial_number.split(":"): yield int(part, 16) def ip_to_knx(ip_addr): """Serialize ip.""" for part in ip_addr.split("."): yield int(part) def str_to_knx(string, length): """Serialize string.""" if len(string) > length-1: string = string[:length-1] for char in string: yield ord(char) for _ in range(0, 30-len(string)): yield 0x00 installation_project_identifier = \ (self.project_number * 16) + \ self.installation_number data = [] data.append(DIBDeviceInformation.LENGTH) data.append(DIBTypeCode.DEVICE_INFO.value) data.append(self.knx_medium.value) data.append(int(self.programming_mode)) data.extend(self.individual_address.to_knx()) data.append((installation_project_identifier >> 8) & 255) data.append(installation_project_identifier & 255) data.extend(hex_notation_to_knx(self.serial_number)) data.extend(ip_to_knx(self.multicast_address)) data.extend(hex_notation_to_knx(self.mac_address)) data.extend(str_to_knx(self.name, 30)) return data def __str__(self): """Return object as readable string.""" return '<DIBDeviceInformation ' \ '\n\tknx_medium="{0}" ' \ '\n\tprogramming_mode="{1}" ' \ '\n\tindividual_address="{2}" ' \ '\n\tinstallation_number="{3}" ' \ '\n\tproject_number="{4}" ' \ '\n\tserial_number="{5}" ' \ '\n\tmulticast_address="{6}" ' \ '\n\tmac_address="{7}" ' \ '\n\tname="{8}" />'.format( self.knx_medium, self.programming_mode, self.individual_address, self.installation_number, self.project_number, self.serial_number, self.multicast_address, self.mac_address, self.name)
class CEMIFrame(KNXIPBody): """Representation of a CEMI Frame.""" # pylint: disable=too-many-instance-attributes def __init__(self, xknx): """Initialize CEMIFrame object.""" super(CEMIFrame, self).__init__(xknx) self.code = CEMIMessageCode.L_DATA_IND self.flags = 0 self.cmd = APCICommand.GROUP_READ self.src_addr = GroupAddress(None) self.dst_addr = GroupAddress(None) self.mpdu_len = 0 self.payload = None @property def telegram(self): """Return telegram.""" telegram = Telegram() telegram.payload = self.payload telegram.group_address = self.dst_addr def resolve_telegram_type(cmd): """Return telegram type from APCI Command.""" if cmd == APCICommand.GROUP_WRITE: return TelegramType.GROUP_WRITE if cmd == APCICommand.GROUP_READ: return TelegramType.GROUP_READ if cmd == APCICommand.GROUP_RESPONSE: return TelegramType.GROUP_RESPONSE raise ConversionError("Telegram not implemented for {0}".format(self.cmd)) telegram.telegramtype = resolve_telegram_type(self.cmd) # TODO: Set telegram.direction [additional flag within KNXIP] return telegram @telegram.setter def telegram(self, telegram): """Set telegram.""" self.dst_addr = telegram.group_address self.payload = telegram.payload # TODO: Move to separate function, together with setting of # CEMIMessageCode self.flags = (CEMIFlags.FRAME_TYPE_STANDARD | CEMIFlags.DO_NOT_REPEAT | CEMIFlags.BROADCAST | CEMIFlags.PRIORITY_LOW | CEMIFlags.NO_ACK_REQUESTED | CEMIFlags.CONFIRM_NO_ERROR | CEMIFlags.DESTINATION_GROUP_ADDRESS | CEMIFlags.HOP_COUNT_1ST) # TODO: use telegram.direction def resolve_cmd(telegramtype): """Resolve APCICommand from TelegramType.""" if telegramtype == TelegramType.GROUP_READ: return APCICommand.GROUP_READ if telegramtype == TelegramType.GROUP_WRITE: return APCICommand.GROUP_WRITE if telegramtype == TelegramType.GROUP_RESPONSE: return APCICommand.GROUP_RESPONSE raise TypeError() self.cmd = resolve_cmd(telegram.telegramtype) def set_hops(self, hops): """Set hops.""" # Resetting hops self.flags &= 0xFFFF ^ 0x0070 # Setting new hops self.flags |= hops << 4 def calculated_length(self): """Get length of KNX/IP body.""" if self.payload is None: return 11 if isinstance(self.payload, DPTBinary): return 11 if isinstance(self.payload, DPTArray): return 11 + len(self.payload.value) raise TypeError() def from_knx(self, raw): """Parse/deserialize from KNX/IP raw data.""" self.code = CEMIMessageCode(raw[0]) if self.code == CEMIMessageCode.L_DATA_IND or \ self.code == CEMIMessageCode.L_Data_REQ or \ self.code == CEMIMessageCode.L_DATA_CON: return self.from_knx_data_link_layer(raw) raise CouldNotParseKNXIP("Could not understand CEMIMessageCode: {0} / {1}".format(self.code, raw[0])) def from_knx_data_link_layer(self, cemi): """Parse L_DATA_IND, CEMIMessageCode.L_Data_REQ, CEMIMessageCode.L_DATA_CON.""" if len(cemi) < 11: raise CouldNotParseKNXIP("CEMI too small") # AddIL (Additional Info Length), as specified within # KNX Chapter 3.6.3/4.1.4.3 "Additional information." # Additional information is not yet parsed. addil = cemi[1] self.flags = cemi[2 + addil] * 256 + cemi[3 + addil] self.src_addr = PhysicalAddress((cemi[4 + addil], cemi[5 + addil])) if self.flags & CEMIFlags.DESTINATION_GROUP_ADDRESS: self.dst_addr = GroupAddress((cemi[6 + addil], cemi[7 + addil]), levels=self.xknx.address_format) else: self.dst_addr = PhysicalAddress((cemi[6 + addil], cemi[7 + addil])) self.mpdu_len = cemi[8 + addil] # TPCI (transport layer control information) -> First 14 bit # APCI (application layer control information) -> Last 10 bit tpci_apci = cemi[9 + addil] * 256 + cemi[10 + addil] self.cmd = APCICommand(tpci_apci & 0xFFC0) apdu = cemi[10 + addil:] if len(apdu) != self.mpdu_len: raise CouldNotParseKNXIP( "APDU LEN should be {} but is {}".format( self.mpdu_len, len(apdu))) if len(apdu) == 1: apci = tpci_apci & DPTBinary.APCI_BITMASK self.payload = DPTBinary(apci) else: self.payload = DPTArray(cemi[11 + addil:]) return 10 + addil + len(apdu) def to_knx(self): """Serialize to KNX/IP raw data.""" if not isinstance(self.src_addr, (GroupAddress, PhysicalAddress)): raise ConversionError("src_add not set") if not isinstance(self.dst_addr, (GroupAddress, PhysicalAddress)): raise ConversionError("dst_add not set") data = [] data.append(self.code.value) data.append(0x00) data.append((self.flags >> 8) & 255) data.append(self.flags & 255) data.extend(self.src_addr.to_knx()) data.extend(self.dst_addr.to_knx()) def encode_cmd_and_payload(cmd, encoded_payload=0, appended_payload=None): """Encode cmd and payload.""" if appended_payload is None: appended_payload = [] data = [ 1 + len(appended_payload), (cmd.value >> 8) & 0xff, (cmd.value & 0xff) | (encoded_payload & DPTBinary.APCI_BITMASK)] data.extend(appended_payload) return data if self.payload is None: data.extend(encode_cmd_and_payload(self.cmd)) elif isinstance(self.payload, DPTBinary): data.extend(encode_cmd_and_payload( self.cmd, encoded_payload=self.payload.value)) elif isinstance(self.payload, DPTArray): data.extend(encode_cmd_and_payload( self.cmd, appended_payload=self.payload.value)) else: raise TypeError() return data def __str__(self): """Return object as readable string.""" return '<CEMIFrame SourceAddress="{0}" DestinationAddress="{1}" ' \ 'Flags="{2:16b}" Command="{3}" payload="{4}" />'.format( self.src_addr.__repr__(), self.dst_addr.__repr__(), self.flags, self.cmd, self.payload) def __eq__(self, other): """Equal operator.""" return self.__dict__ == other.__dict__
class CEMIFrame(KNXIPBody): """Representation of a CEMI Frame.""" # pylint: disable=too-many-instance-attributes def __init__(self, xknx): """Initialize CEMIFrame object.""" super().__init__(xknx) self.code = CEMIMessageCode.L_DATA_IND self.flags = 0 self.cmd = APCICommand.GROUP_READ self.src_addr = GroupAddress(None) self.dst_addr = GroupAddress(None) self.mpdu_len = 0 self.payload = None @property def telegram(self): """Return telegram.""" telegram = Telegram() telegram.payload = self.payload telegram.group_address = self.dst_addr def resolve_telegram_type(cmd): """Return telegram type from APCI Command.""" if cmd == APCICommand.GROUP_WRITE: return TelegramType.GROUP_WRITE if cmd == APCICommand.GROUP_READ: return TelegramType.GROUP_READ if cmd == APCICommand.GROUP_RESPONSE: return TelegramType.GROUP_RESPONSE raise ConversionError("Telegram not implemented for {0}".format(self.cmd)) telegram.telegramtype = resolve_telegram_type(self.cmd) # TODO: Set telegram.direction [additional flag within KNXIP] return telegram @telegram.setter def telegram(self, telegram): """Set telegram.""" self.dst_addr = telegram.group_address self.payload = telegram.payload # TODO: Move to separate function, together with setting of # CEMIMessageCode self.flags = (CEMIFlags.FRAME_TYPE_STANDARD | CEMIFlags.DO_NOT_REPEAT | CEMIFlags.BROADCAST | CEMIFlags.PRIORITY_LOW | CEMIFlags.NO_ACK_REQUESTED | CEMIFlags.CONFIRM_NO_ERROR | CEMIFlags.DESTINATION_GROUP_ADDRESS | CEMIFlags.HOP_COUNT_1ST) # TODO: use telegram.direction def resolve_cmd(telegramtype): """Resolve APCICommand from TelegramType.""" if telegramtype == TelegramType.GROUP_READ: return APCICommand.GROUP_READ if telegramtype == TelegramType.GROUP_WRITE: return APCICommand.GROUP_WRITE if telegramtype == TelegramType.GROUP_RESPONSE: return APCICommand.GROUP_RESPONSE raise TypeError() self.cmd = resolve_cmd(telegram.telegramtype) def set_hops(self, hops): """Set hops.""" # Resetting hops self.flags &= 0xFFFF ^ 0x0070 # Setting new hops self.flags |= hops << 4 def calculated_length(self): """Get length of KNX/IP body.""" if self.payload is None: return 11 if isinstance(self.payload, DPTBinary): return 11 if isinstance(self.payload, DPTArray): return 11 + len(self.payload.value) raise TypeError() def from_knx(self, raw): """Parse/deserialize from KNX/IP raw data.""" self.code = CEMIMessageCode(raw[0]) if self.code == CEMIMessageCode.L_DATA_IND or \ self.code == CEMIMessageCode.L_Data_REQ or \ self.code == CEMIMessageCode.L_DATA_CON: return self.from_knx_data_link_layer(raw) raise CouldNotParseKNXIP("Could not understand CEMIMessageCode: {0} / {1}".format(self.code, raw[0])) def from_knx_data_link_layer(self, cemi): """Parse L_DATA_IND, CEMIMessageCode.L_Data_REQ, CEMIMessageCode.L_DATA_CON.""" if len(cemi) < 11: raise CouldNotParseKNXIP("CEMI too small") # AddIL (Additional Info Length), as specified within # KNX Chapter 3.6.3/4.1.4.3 "Additional information." # Additional information is not yet parsed. addil = cemi[1] self.flags = cemi[2 + addil] * 256 + cemi[3 + addil] self.src_addr = PhysicalAddress((cemi[4 + addil], cemi[5 + addil])) if self.flags & CEMIFlags.DESTINATION_GROUP_ADDRESS: self.dst_addr = GroupAddress((cemi[6 + addil], cemi[7 + addil]), levels=self.xknx.address_format) else: self.dst_addr = PhysicalAddress((cemi[6 + addil], cemi[7 + addil])) self.mpdu_len = cemi[8 + addil] # TPCI (transport layer control information) -> First 14 bit # APCI (application layer control information) -> Last 10 bit tpci_apci = cemi[9 + addil] * 256 + cemi[10 + addil] self.cmd = APCICommand(tpci_apci & 0xFFC0) apdu = cemi[10 + addil:] if len(apdu) != self.mpdu_len: raise CouldNotParseKNXIP( "APDU LEN should be {} but is {}".format( self.mpdu_len, len(apdu))) if len(apdu) == 1: apci = tpci_apci & DPTBinary.APCI_BITMASK self.payload = DPTBinary(apci) else: self.payload = DPTArray(cemi[11 + addil:]) return 10 + addil + len(apdu) def to_knx(self): """Serialize to KNX/IP raw data.""" if not isinstance(self.src_addr, (GroupAddress, PhysicalAddress)): raise ConversionError("src_add not set") if not isinstance(self.dst_addr, (GroupAddress, PhysicalAddress)): raise ConversionError("dst_add not set") data = [] data.append(self.code.value) data.append(0x00) data.append((self.flags >> 8) & 255) data.append(self.flags & 255) data.extend(self.src_addr.to_knx()) data.extend(self.dst_addr.to_knx()) def encode_cmd_and_payload(cmd, encoded_payload=0, appended_payload=None): """Encode cmd and payload.""" if appended_payload is None: appended_payload = [] data = [ 1 + len(appended_payload), (cmd.value >> 8) & 0xff, (cmd.value & 0xff) | (encoded_payload & DPTBinary.APCI_BITMASK)] data.extend(appended_payload) return data if self.payload is None: data.extend(encode_cmd_and_payload(self.cmd)) elif isinstance(self.payload, DPTBinary): data.extend(encode_cmd_and_payload( self.cmd, encoded_payload=self.payload.value)) elif isinstance(self.payload, DPTArray): data.extend(encode_cmd_and_payload( self.cmd, appended_payload=self.payload.value)) else: raise TypeError() return data def __str__(self): """Return object as readable string.""" return '<CEMIFrame SourceAddress="{0}" DestinationAddress="{1}" ' \ 'Flags="{2:16b}" Command="{3}" payload="{4}" />'.format( self.src_addr.__repr__(), self.dst_addr.__repr__(), self.flags, self.cmd, self.payload) def __eq__(self, other): """Equal operator.""" return self.__dict__ == other.__dict__