Repository: XKNX/xknx
Pull request #748 (2021-08-03 21:50:19)

Ignore non-gateway KNX/IP devices in GatewayScanner
<!--
  You are awesome! Thanks for contributing to our project!
  Please, DO NOT DELETE ANY TEXT from this template!.
-->
## Description
<!--
Please include a summary of the change and which issue is fixed.
Please also include relevant motivation and context.
List any dependencies that are required for this change.
-->
Ignore non-gateway KNX/IP devices in GatewayScanner.
`GatewayScanFilter()` matches Rotuers, Tunnel servers and both. When `tunnelling` or `routing` attributes are set they are treated as AND. `None` ignores. Non-gateway KNX/IP devices are never matched by GatewayScanner.

Fixes # (issue)

## Type of change
<!--
Please tick the applicable options.
NOTE: Ticking multiple options most likely indicates
that your change is to big and it is suggested to split it into several smaller PRs.
-->

- [x] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] This change requires a documentation update

## Checklist:

- [x] I have performed a self-review of my own code
- [ ] The documentation has been adjusted accordingly
- [x] The changes generate no new warnings
- [x] Tests have been added that prove the fix is effective or that the feature works
- [x] The changes are documented in the changelog
- [ ] The Homeassistant plugin has been adjusted in case of new config options


codecov[bot]: # [Codecov](https://codecov.io/gh/XKNX/xknx/pull/748?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=XKNX) Report
> Merging [#748](https://codecov.io/gh/XKNX/xknx/pull/748?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=XKNX) (621415d) into [main](https://codecov.io/gh/XKNX/xknx/commit/c0ff9b239f721db76b98d75d49599ae9f2bf1b9b?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=XKNX) (c0ff9b2) will **not change** coverage.
> The diff coverage is `100.00%`.

> :exclamation: Current head 621415d differs from pull request most recent head 8c2dec4. Consider uploading reports for the commit 8c2dec4 to get more accurate results
[![Impacted file tree graph](https://codecov.io/gh/XKNX/xknx/pull/748/graphs/tree.svg?width=650&height=150&src=pr&token=irWbIygS84&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=XKNX)](https://codecov.io/gh/XKNX/xknx/pull/748?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=XKNX)

```diff
@@           Coverage Diff           @@
##             main     #748   +/-   ##
=======================================
  Coverage   94.67%   94.67%           
=======================================
  Files         106      106           
  Lines        6665     6665           
=======================================
  Hits         6310     6310           
  Misses        355      355           
```


| [Impacted Files](https://codecov.io/gh/XKNX/xknx/pull/748?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=XKNX) | Coverage Δ | |
|---|---|---|
| [xknx/devices/binary\_sensor.py](https://codecov.io/gh/XKNX/xknx/pull/748/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=XKNX#diff-eGtueC9kZXZpY2VzL2JpbmFyeV9zZW5zb3IucHk=) | `96.90% <100.00%> (-0.07%)` | :arrow_down: |
| [xknx/io/gateway\_scanner.py](https://codecov.io/gh/XKNX/xknx/pull/748/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=XKNX#diff-eGtueC9pby9nYXRld2F5X3NjYW5uZXIucHk=) | `83.33% <100.00%> (+0.35%)` | :arrow_up: |


Patch:
diff --git a/changelog.md b/changelog.md
index 326fc387..6011bc92 100644
--- a/changelog.md
+++ b/changelog.md
@@ -1,5 +1,11 @@
 # Changelog
 
+## Unreleased changes
+
+### Internals
+
+- GatewayScanFilter: Ignore non-gateway KNX/IP devices
+
 ## 0.18.9 HS-color 2021-07-26
 
 ### Devices
diff --git a/xknx/io/gateway_scanner.py b/xknx/io/gateway_scanner.py
index eb9bafcd..b6d83329 100644
--- a/xknx/io/gateway_scanner.py
+++ b/xknx/io/gateway_scanner.py
@@ -67,7 +67,11 @@ class GatewayDescriptor:
 
 
 class GatewayScanFilter:
-    """Filter to limit gateway scan attempts."""
+    """Filter to limit gateway scan attempts.
+
+    If `tunnelling` and `routing` are set it is treated as AND.
+    KNX/IP devices that don't support `tunnelling` or `routing` aren't matched.
+    """
 
     def __init__(
         self,
@@ -81,7 +85,7 @@ class GatewayScanFilter:
         self.routing = routing
 
     def match(self, gateway: GatewayDescriptor) -> bool:
-        """Check whether the given GatewayDescriptor matches the filter."""
+        """Check whether the device is a gateway and given GatewayDescriptor matches the filter."""
         if self.name is not None and self.name != gateway.name:
             return False
         if (
@@ -91,7 +95,7 @@ class GatewayScanFilter:
             return False
         if self.routing is not None and self.routing != gateway.supports_routing:
             return False
-        return True
+        return gateway.supports_tunnelling or gateway.supports_routing
 
 
 class GatewayScanner:


Test patch:
diff --git a/test/io_tests/gateway_scanner_test.py b/test/io_tests/gateway_scanner_test.py
index 1ac82e03..901e614e 100644
--- a/test/io_tests/gateway_scanner_test.py
+++ b/test/io_tests/gateway_scanner_test.py
@@ -48,6 +48,15 @@ class TestGatewayScanner:
         supports_tunnelling=True,
         supports_routing=True,
     )
+    gateway_desc_neither = GatewayDescriptor(
+        name="AC/S 1.1.1 Application Control",
+        ip_addr="10.1.1.15",
+        port=3671,
+        local_interface="en1",
+        local_ip="192.168.42.50",
+        supports_tunnelling=False,
+        supports_routing=False,
+    )
 
     fake_interfaces = ["lo0", "en0", "en1"]
     fake_ifaddresses = {
@@ -89,26 +98,48 @@ class TestGatewayScanner:
 
     def test_gateway_scan_filter_match(self):
         """Test match function of gateway filter."""
+        filter_default = GatewayScanFilter()
         filter_tunnel = GatewayScanFilter(tunnelling=True)
         filter_router = GatewayScanFilter(routing=True)
         filter_name = GatewayScanFilter(name="KNX-Router")
         filter_no_tunnel = GatewayScanFilter(tunnelling=False)
+        filter_no_router = GatewayScanFilter(routing=False)
+        filter_tunnel_and_router = GatewayScanFilter(tunnelling=True, routing=True)
+
+        assert filter_default.match(self.gateway_desc_interface)
+        assert filter_default.match(self.gateway_desc_router)
+        assert filter_default.match(self.gateway_desc_both)
+        assert not filter_default.match(self.gateway_desc_neither)
 
         assert filter_tunnel.match(self.gateway_desc_interface)
         assert not filter_tunnel.match(self.gateway_desc_router)
         assert filter_tunnel.match(self.gateway_desc_both)
+        assert not filter_tunnel.match(self.gateway_desc_neither)
 
         assert not filter_router.match(self.gateway_desc_interface)
         assert filter_router.match(self.gateway_desc_router)
         assert filter_router.match(self.gateway_desc_both)
+        assert not filter_router.match(self.gateway_desc_neither)
 
         assert not filter_name.match(self.gateway_desc_interface)
         assert filter_name.match(self.gateway_desc_router)
         assert not filter_name.match(self.gateway_desc_both)
+        assert not filter_name.match(self.gateway_desc_neither)
 
         assert not filter_no_tunnel.match(self.gateway_desc_interface)
         assert filter_no_tunnel.match(self.gateway_desc_router)
         assert not filter_no_tunnel.match(self.gateway_desc_both)
+        assert not filter_no_tunnel.match(self.gateway_desc_neither)
+
+        assert filter_no_router.match(self.gateway_desc_interface)
+        assert not filter_no_router.match(self.gateway_desc_router)
+        assert not filter_no_router.match(self.gateway_desc_both)
+        assert not filter_no_router.match(self.gateway_desc_neither)
+
+        assert not filter_tunnel_and_router.match(self.gateway_desc_interface)
+        assert not filter_tunnel_and_router.match(self.gateway_desc_router)
+        assert filter_tunnel_and_router.match(self.gateway_desc_both)
+        assert not filter_tunnel_and_router.match(self.gateway_desc_neither)
 
     def test_search_response_reception(self):
         """Test function of gateway scanner."""