예제 #1
0
 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
예제 #2
0
파일: cemi_frame.py 프로젝트: phbaer/xknx
 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
예제 #3
0
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
예제 #4
0
 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 = ""
예제 #5
0
    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))
예제 #6
0
    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))
예제 #7
0
    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)
예제 #8
0
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()
예제 #9
0
    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)
예제 #10
0
 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"]
예제 #11
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)
예제 #12
0
파일: cemi_frame.py 프로젝트: phbaer/xknx
    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)
예제 #13
0
    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
예제 #14
0
    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)
예제 #15
0
    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)
예제 #16
0
    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))
예제 #17
0
    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))
예제 #18
0
    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))
예제 #19
0
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
예제 #20
0
 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" />')
예제 #21
0
    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))
예제 #22
0
 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)
예제 #23
0
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)
예제 #24
0
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__
예제 #25
0
파일: cemi_frame.py 프로젝트: phbaer/xknx
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__