Skip to content

Commit 2709dfb

Browse files
authored
Fix GatewayScanner on Mac and Windows (#801)
* Fix automatic connection handling on Mac and Windows as the local_addr would always be 127.0.0.1 * Changelog
1 parent 5e4efe6 commit 2709dfb

File tree

3 files changed

+18
-33
lines changed

3 files changed

+18
-33
lines changed

changelog.md

+4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
## Unreleased changes
44

5+
### Internals
6+
7+
- Fix GatewayScanner on MacOS and Windows and only return one instance of a gateway
8+
59
### Devices
610

711
- Light: Only send to global switch or brightness address if individual colors are also configured

test/io_tests/gateway_scanner_test.py

+6-16
Original file line numberDiff line numberDiff line change
@@ -156,27 +156,17 @@ def test_search_response_reception(self):
156156
test_search_response,
157157
udp_client_mock,
158158
interface="en1",
159-
netmask="255.255.255.0",
160159
)
161-
assert str(gateway_scanner.found_gateways[0]) == str(self.gateway_desc_both)
162160

163-
def test_search_response_wrong_network(self):
164-
"""Test function of gateway scanner when network iface doesnt match search response."""
165-
xknx = XKNX()
166-
gateway_scanner = GatewayScanner(xknx)
167-
test_search_response = fake_router_search_response(xknx)
168-
udp_client_mock = create_autospec(UDPClient)
169-
udp_client_mock.local_addr = ("192.168.100.50", 0)
170-
udp_client_mock.getsockname.return_value = ("192.168.100.50", 0)
161+
assert str(gateway_scanner.found_gateways[0]) == str(self.gateway_desc_both)
162+
assert len(gateway_scanner.found_gateways) == 1
171163

172-
assert gateway_scanner.found_gateways == []
173164
gateway_scanner._response_rec_callback(
174165
test_search_response,
175166
udp_client_mock,
176-
interface="en1",
177-
netmask="255.255.255.0",
167+
interface="eth1",
178168
)
179-
assert gateway_scanner.found_gateways == []
169+
assert len(gateway_scanner.found_gateways) == 1
180170

181171
@patch("xknx.io.gateway_scanner.netifaces", autospec=True)
182172
async def test_scan_timeout(self, netifaces_mock):
@@ -214,8 +204,8 @@ async def async_none():
214204

215205
assert _search_interface_mock.call_count == 2
216206
expected_calls = [
217-
((gateway_scanner, "lo0", "127.0.0.1", "255.0.0.0"),),
218-
((gateway_scanner, "en1", "10.1.1.2", "255.255.255.0"),),
207+
((gateway_scanner, "lo0", "127.0.0.1"),),
208+
((gateway_scanner, "en1", "10.1.1.2"),),
219209
]
220210
assert _search_interface_mock.call_args_list == expected_calls
221211
assert test_scan == []

xknx/io/gateway_scanner.py

+8-17
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99

1010
import asyncio
1111
from functools import partial
12-
from ipaddress import IPv4Address, IPv4Network
1312
import logging
1413
from typing import TYPE_CHECKING
1514

@@ -132,6 +131,7 @@ async def scan(self) -> list[GatewayDescriptor]:
132131
pass
133132
finally:
134133
await self._stop()
134+
135135
return self.found_gateways
136136

137137
async def _stop(self) -> None:
@@ -145,15 +145,12 @@ async def _send_search_requests(self) -> None:
145145
try:
146146
af_inet = netifaces.ifaddresses(interface)[netifaces.AF_INET]
147147
ip_addr = af_inet[0]["addr"]
148-
netmask = af_inet[0]["netmask"]
149-
await self._search_interface(interface, ip_addr, netmask)
148+
await self._search_interface(interface, ip_addr)
150149
except KeyError:
151150
logger.info("Could not connect to an KNX/IP device on %s", interface)
152151
continue
153152

154-
async def _search_interface(
155-
self, interface: str, ip_addr: str, netmask: str
156-
) -> None:
153+
async def _search_interface(self, interface: str, ip_addr: str) -> None:
157154
"""Send a search request on a specific interface."""
158155
logger.debug("Searching on %s / %s", interface, ip_addr)
159156

@@ -165,7 +162,7 @@ async def _search_interface(
165162
)
166163

167164
udp_client.register_callback(
168-
partial(self._response_rec_callback, interface=interface, netmask=netmask),
165+
partial(self._response_rec_callback, interface=interface),
169166
[KNXIPServiceType.SEARCH_RESPONSE],
170167
)
171168
await udp_client.connect()
@@ -183,21 +180,12 @@ def _response_rec_callback(
183180
knx_ip_frame: KNXIPFrame,
184181
udp_client: UDPClient,
185182
interface: str = "",
186-
netmask: str = "",
187183
) -> None:
188184
"""Verify and handle knxipframe. Callback from internal udpclient."""
189185
if not isinstance(knx_ip_frame.body, SearchResponse):
190186
logger.warning("Could not understand knxipframe")
191187
return
192188

193-
address: IPv4Address = IPv4Address(knx_ip_frame.body.control_endpoint.ip_addr)
194-
network: IPv4Network = IPv4Network(
195-
f"{udp_client.local_addr[0]}/{netmask}", False
196-
)
197-
198-
if address not in network:
199-
return
200-
201189
gateway = GatewayDescriptor(
202190
name=knx_ip_frame.body.device_name,
203191
ip_addr=knx_ip_frame.body.control_endpoint.ip_addr,
@@ -229,7 +217,10 @@ def _response_rec_callback(
229217
self._add_found_gateway(gateway)
230218

231219
def _add_found_gateway(self, gateway: GatewayDescriptor) -> None:
232-
if self.scan_filter.match(gateway):
220+
if self.scan_filter.match(gateway) and not any(
221+
_gateway.individual_address == gateway.individual_address
222+
for _gateway in self.found_gateways
223+
):
233224
self.found_gateways.append(gateway)
234225
if 0 < self._count_upper_bound <= len(self.found_gateways):
235226
self._response_received_event.set()

0 commit comments

Comments
 (0)