Exemple #1
0
def load_yaml(fname: str) -> JSON_TYPE:
    """Load a YAML file."""
    try:
        with open(fname, encoding="utf-8") as conf_file:
            # If configuration file is empty YAML returns None
            # We convert that to an empty dict
            return yaml.load(conf_file, Loader=SafeLineLoader) or OrderedDict()
    except yaml.YAMLError as exc:
        logger.error(str(exc))
        raise XKNXException(exc) from exc
    except UnicodeDecodeError as exc:
        logger.error("Unable to read file %s: %s", fname, exc)
        raise XKNXException(exc) from exc
Exemple #2
0
    def test_sync_exception(self):
        """Testing exception handling within sync()."""
        # pylint: disable=protected-access
        xknx = XKNX(loop=self.loop)
        device = Device(xknx, 'TestDevice')

        with patch('logging.Logger.error') as mock_error:
            with patch('xknx.devices.Device._sync_impl') as mock_sync_impl:
                fut = asyncio.Future()
                fut.set_result(None)
                mock_sync_impl.return_value = fut
                mock_sync_impl.side_effect = XKNXException()
                self.loop.run_until_complete(asyncio.Task(device.sync()))
                mock_sync_impl.assert_called_with(True)
                mock_error.assert_called_with('Error while syncing device: %s', XKNXException())
Exemple #3
0
    def send_telegram(self, telegram):
        """
        Send Telegram to routing tunelling device - retry mechanism.

        If a TUNNELLING_REQUEST frame is not confirmed within the TUNNELLING_REQUEST_TIME_- OUT
        time of one (1) second then the frame shall be repeated once with the same sequence counter
        value by the sending KNXnet/IP device.

        If the KNXnet/IP device does not receive a TUNNELLING_ACK frame within the
        TUNNELLING_- REQUEST_TIMEOUT (= 1 second) or the status of a received
        TUNNELLING_ACK frame signals any kind of error condition, the sending device
        shall repeat the TUNNELLING_REQUEST frame once and then terminate the
        connection by sending a DISCONNECT_REQUEST frame to the other device’s
        control endpoint.
        """
        success = yield from self._send_telegram_impl(telegram)
        if not success:
            self.xknx.logger.warning(
                "Sending of telegram failed. Retrying a second time.")
            success = yield from self._send_telegram_impl(telegram)
            if not success:
                self.xknx.logger.warning(
                    "Resending telegram failed. Reconnecting to tunnel.")
                yield from self.reconnect()
                success = yield from self._send_telegram_impl(telegram)
                if not success:
                    raise XKNXException("Could not send telegram to tunnel")
        self.increase_sequence_number()
Exemple #4
0
 def test_config_file_error(self):
     """Test error message when reading an errornous config file."""
     with patch("logging.Logger.error") as mock_err, patch(
             "xknx.config.ConfigV1.parse_group_light") as mock_parse:
         mock_parse.side_effect = XKNXException()
         XKNX(config="xknx.yaml")
         self.assertEqual(mock_err.call_count, 1)
Exemple #5
0
def validate_ip(address: str, address_name: str = "IP address") -> None:
    """Raise an exception if address cannot be parsed as IPv4 address."""
    try:
        ipaddress.IPv4Address(address)
    except ipaddress.AddressValueError as ex:
        raise XKNXException("%s is not a valid IPv4 address." %
                            address_name) from ex
Exemple #6
0
 def test_config_file_error(self):
     """Test error message when reading an errornous config file."""
     with patch('logging.Logger.error') as mock_err, \
             patch('xknx.core.Config.parse_group_light') as mock_parse:
         mock_parse.side_effect = XKNXException()
         XKNX(config='xknx.yaml', loop=self.loop)
         self.assertEqual(mock_err.call_count, 1)
Exemple #7
0
    async def start_automatic(self, scan_filter: GatewayScanFilter):
        """Start GatewayScanner and connect to the found device."""
        gatewayscanner = GatewayScanner(self.xknx, scan_filter=scan_filter)
        gateways = await gatewayscanner.scan()

        if not gateways:
            raise XKNXException("No Gateways found")

        gateway = gateways[0]

        # on Linux gateway.local_ip can be any interface listening to the
        # multicast group (even 127.0.0.1) so we set the interface with find_local_ip
        local_interface_ip = self.find_local_ip(gateway_ip=gateway.ip_addr)

        if gateway.supports_tunnelling and scan_filter.routing is not True:
            await self.start_tunnelling(
                local_interface_ip,
                self.connection_config.local_port,
                gateway.ip_addr,
                gateway.port,
                self.connection_config.auto_reconnect,
                self.connection_config.auto_reconnect_wait,
                bind_ip=self.connection_config.bind_ip,
                bind_port=self.connection_config.bind_port,
            )
        elif gateway.supports_routing:
            await self.start_routing(local_interface_ip)
Exemple #8
0
    def send(self, knxipframe):
        """Send KNXIPFrame to socket."""
        self.xknx.knx_logger.debug("Sending: %s", knxipframe)
        if self.transport is None:
            raise XKNXException("Transport not connected")

        if self.multicast:
            self.transport.sendto(bytes(knxipframe.to_knx()), self.remote_addr)
        else:
            self.transport.sendto(bytes(knxipframe.to_knx()))
Exemple #9
0
 async def disconnect(self, ignore_error=False):
     """Disconnect from tunnel device."""
     disconnect = Disconnect(
         self.xknx,
         self.udp_client,
         communication_channel_id=self.communication_channel)
     await disconnect.start()
     if not disconnect.success and not ignore_error:
         raise XKNXException("Could not disconnect channel")
     else:
         self.xknx.logger.debug("Tunnel disconnected (communication_channel: %s)", self.communication_channel)
Exemple #10
0
    async def _start_routing(self, local_ip: str | None = None) -> None:
        """Start KNX/IP Routing."""
        local_ip = local_ip or await util.get_default_local_ip()
        if local_ip is None:
            raise XKNXException("No network interface found.")
        util.validate_ip(local_ip, address_name="Local IP address")

        logger.debug("Starting Routing from %s as %s", local_ip,
                     self.xknx.own_address)
        self._interface = Routing(self.xknx, self.telegram_received, local_ip)
        await self._interface.connect()
Exemple #11
0
def _env_var_yaml(loader: SafeLineLoader, node: yaml.nodes.Node) -> str:
    """Load environment variables and embed it into the configuration YAML."""
    args = node.value.split()

    # Check for a default value
    if len(args) > 1:
        return os.getenv(args[0], " ".join(args[1:]))
    if args[0] in os.environ:
        return os.environ[args[0]]
    logger.error("Environment variable %s not defined", node.value)
    raise XKNXException(node.value)
Exemple #12
0
 def connect(self):
     """Connect/build tunnel."""
     connect = Connect(self.xknx, self.udp_client)
     yield from connect.start()
     if not connect.success:
         raise XKNXException("Could not establish connection")
     self.xknx.logger.debug(
         "Tunnel established communication_channel=%s, id=%s",
         connect.communication_channel, connect.identifier)
     self.communication_channel = connect.communication_channel
     self.sequence_number = 0
     yield from self.start_heartbeat()
Exemple #13
0
def _include_yaml(loader: SafeLineLoader, node: yaml.nodes.Node) -> JSON_TYPE:
    """Load another YAML file and embeds it using the !include tag.

    Example:
        device_tracker: !include device_tracker.yaml

    """
    fname = os.path.join(os.path.dirname(loader.name), node.value)
    try:
        return _add_reference(load_yaml(fname), loader, node)
    except FileNotFoundError as exc:
        raise XKNXException(f"{node.start_mark}: Unable to read file {fname}.") from exc
Exemple #14
0
    def start_automatic(self):
        """Start GatewayScanner and connect to the found device."""
        gatewayscanner = GatewayScanner(self.xknx)
        yield from gatewayscanner.start()
        gatewayscanner.stop()

        if not gatewayscanner.found:
            raise XKNXException("No Gateways found")

        if gatewayscanner.supports_tunneling:
            yield from self.start_tunnelling(gatewayscanner.found_local_ip,
                                             gatewayscanner.found_ip_addr,
                                             gatewayscanner.found_port)
        elif gatewayscanner.supports_routing:
            yield from self.start_routing(gatewayscanner.found_local_ip)
Exemple #15
0
    async def start_automatic(self, scan_filter: GatewayScanFilter):
        """Start GatewayScanner and connect to the found device."""
        gatewayscanner = GatewayScanner(self.xknx, scan_filter=scan_filter)
        gateways = await gatewayscanner.scan()

        if not gateways:
            raise XKNXException("No Gateways found")

        gateway = gateways[0]
        if gateway.supports_tunnelling:
            await self.start_tunnelling(gateway.local_ip,
                                        gateway.ip_addr,
                                        gateway.port)
        elif gateway.supports_routing:
            bind_to_multicast_addr = get_os_name() != "Darwin"  # = Mac OS
            await self.start_routing(gateway.local_ip, bind_to_multicast_addr)
Exemple #16
0
 async def disconnect(self, ignore_error=False):
     """Disconnect from tunnel device."""
     # only send disconnect request if we ever were connected
     if self.communication_channel is None:
         # close udp client to prevent open file descriptors
         await self.udp_client.stop()
         return
     disconnect = Disconnect(
         self.xknx,
         self.udp_client,
         communication_channel_id=self.communication_channel)
     await disconnect.start()
     if not disconnect.success and not ignore_error:
         raise XKNXException("Could not disconnect channel")
     self.xknx.logger.debug("Tunnel disconnected (communication_channel: %s)", self.communication_channel)
     # close udp client to prevent open file descriptors
     await self.udp_client.stop()
Exemple #17
0
 async def _disconnect_request(self, ignore_error: bool = False) -> None:
     """Disconnect from tunnel device. Delete communication_channel."""
     if self.communication_channel is not None:
         disconnect = Disconnect(
             self.xknx,
             self.udp_client,
             communication_channel_id=self.communication_channel,
             route_back=self.route_back,
         )
         await disconnect.start()
         if not disconnect.success and not ignore_error:
             self.communication_channel = None
             raise XKNXException("Could not disconnect channel")
         logger.debug(
             "Tunnel disconnected (communication_channel: %s)",
             self.communication_channel,
         )
     self.communication_channel = None
Exemple #18
0
 async def connect(self):
     """Connect/build tunnel."""
     connect = Connect(self.xknx, self.udp_client)
     await connect.start()
     if not connect.success:
         if self.auto_reconnect:
             msg = "Cannot connect to KNX. Retry in {} seconds.".format(
                 self.auto_reconnect_wait)
             self.xknx.logger.warning(msg)
             task = self.xknx.loop.create_task(self.schedule_reconnect())
             self._reconnect_task = task
             return
         raise XKNXException("Could not establish connection")
     self.xknx.logger.debug(
         "Tunnel established communication_channel=%s, id=%s",
         connect.communication_channel, connect.identifier)
     self._reconnect_task = None
     self.communication_channel = connect.communication_channel
     self.sequence_number = 0
     await self.start_heartbeat()
Exemple #19
0
    async def _scan(
            self,
            queue: asyncio.Queue[GatewayDescriptor | None] | None = None
    ) -> None:
        """Scan for gateways."""
        local_ip = self.local_ip or await util.get_default_local_ip(
            remote_ip=self.xknx.multicast_group)
        if local_ip is None:
            if queue is not None:
                queue.put_nowait(None)
            raise XKNXException("No usable network interface found.")
        interface_name = util.get_local_interface_name(local_ip=local_ip)
        logger.debug("Searching on %s / %s", interface_name, local_ip)

        udp_transport = UDPTransport(
            local_addr=(local_ip, 0),
            remote_addr=(self.xknx.multicast_group, self.xknx.multicast_port),
        )
        udp_transport.register_callback(
            partial(self._response_rec_callback,
                    interface=interface_name,
                    queue=queue),
            [
                KNXIPServiceType.SEARCH_RESPONSE,
                KNXIPServiceType.SEARCH_RESPONSE_EXTENDED,
            ],
        )
        try:
            await self._send_search_requests(udp_transport=udp_transport)
            await asyncio.wait_for(
                self._response_received_event.wait(),
                timeout=self.timeout_in_seconds,
            )
        except asyncio.TimeoutError:
            pass
        except asyncio.CancelledError:
            pass
        finally:
            udp_transport.stop()
            if queue is not None:
                queue.put_nowait(None)
Exemple #20
0
    async def _start_tunnelling_udp(
        self,
        gateway_ip: str,
        gateway_port: int,
    ) -> None:
        """Start KNX/IP UDP tunnel."""
        util.validate_ip(gateway_ip, address_name="Gateway IP address")
        local_ip = self.connection_config.local_ip or util.find_local_ip(
            gateway_ip=gateway_ip)
        local_port = self.connection_config.local_port
        route_back = self.connection_config.route_back
        if local_ip is None:
            local_ip = await util.get_default_local_ip(gateway_ip)
            if local_ip is None:
                raise XKNXException("No network interface found.")
            route_back = True
            logger.debug(
                "Falling back to default interface and enabling route back.")
        util.validate_ip(local_ip, address_name="Local IP address")

        logger.debug(
            "Starting tunnel from %s:%s to %s:%s",
            local_ip,
            local_port,
            gateway_ip,
            gateway_port,
        )
        self._interface = UDPTunnel(
            self.xknx,
            gateway_ip=gateway_ip,
            gateway_port=gateway_port,
            local_ip=local_ip,
            local_port=local_port,
            route_back=route_back,
            telegram_received_callback=self.telegram_received,
            auto_reconnect=self.connection_config.auto_reconnect,
            auto_reconnect_wait=self.connection_config.auto_reconnect_wait,
        )
        await self._interface.connect()
Exemple #21
0
 def parse_connection(self, doc):
     """Parse the connection section of xknx.yaml."""
     if "connection" in doc \
             and hasattr(doc["connection"], '__iter__'):
         for conn, prefs in doc["connection"].items():
             try:
                 if conn == "tunneling":
                     if prefs is None or \
                             "gateway_ip" not in prefs:
                         raise XKNXException(
                             "`gateway_ip` is required for tunneling connection."
                         )
                     conn_type = ConnectionType.TUNNELING
                 elif conn == "routing":
                     conn_type = ConnectionType.ROUTING
                 else:
                     conn_type = ConnectionType.AUTOMATIC
                 self._parse_connection_prefs(conn_type, prefs)
             except XKNXException as ex:
                 self.xknx.logger.error(
                     "Error while reading config file: Could not parse %s: %s",
                     conn, ex)
                 raise ex
Exemple #22
0
        (
            CouldNotParseKNXIP("desc1"),
            CouldNotParseKNXIP("desc1"),
            CouldNotParseKNXIP("desc2"),
        ),
        (
            CouldNotParseTelegram("desc", arg1=1, arg2=2),
            CouldNotParseTelegram("desc", arg1=1, arg2=2),
            CouldNotParseTelegram("desc", arg1=2, arg2=1),
        ),
        (
            DeviceIllegalValue("value1", "desc"),
            DeviceIllegalValue("value1", "desc"),
            DeviceIllegalValue("value1", "desc2"),
        ),
        (
            XKNXException("desc1"),
            XKNXException("desc1"),
            XKNXException("desc2"),
        ),
    ],
)
def test_exceptions(base, equal, diff):
    """Test hashability and repr of exceptions."""
    assert hash(base) == hash(equal)
    assert hash(base) != hash(diff)
    assert base == equal
    assert base != diff
    assert repr(base) == repr(equal)
    assert repr(base) != repr(diff)