-
-
Notifications
You must be signed in to change notification settings - Fork 24
fix(discovery): resolve Windows broadcast discovery failure with ProactorEventLoop #62
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
fix(discovery): resolve Windows broadcast discovery failure with ProactorEventLoop #62
Conversation
The discovery process failed due to the default Python broadcast address not working in all cases, causing UDP packets to not leave the interface and resulting in no units being found in the local network. This fix ensures the correct broadcast address is used.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR enhances bridge discovery functionality by replacing hardcoded broadcast addresses with programmatic detection using the netifaces library, and adds comprehensive error handling and documentation.
Key changes:
- Adds
netifacesdependency to dynamically determine network broadcast addresses - Updates type hints to use modern Python 3.10+ union syntax (
str | Noneinstead ofstr = None) - Adds try-except blocks for robust error handling during broadcast address detection and response parsing
- Adds comprehensive docstring to
discover_bridges()function
Reviewed Changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| pyproject.toml | Adds netifaces ^0.11.0 dependency for network interface detection |
| aiocomfoconnect/discovery.py | Implements dynamic broadcast address detection, adds error handling for discovery response parsing, modernizes type hints, and adds comprehensive function documentation |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| default_iface = gws['default'][netifaces.AF_INET][1] | ||
| addrs = netifaces.ifaddresses(default_iface) | ||
| broadcast_addr = addrs[netifaces.AF_INET][0].get('broadcast', '255.255.255.255') |
Copilot
AI
Oct 31, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This code assumes the presence of keys in nested dictionaries without checking their existence, which can cause KeyError exceptions not caught by the generic Exception handler. Specifically, gws['default'], gws['default'][netifaces.AF_INET], and addrs[netifaces.AF_INET] could raise KeyError if the network doesn't have a default IPv4 gateway or IPv4 addresses configured. While the broad exception handler will catch this, it would be more explicit and maintainable to use .get() methods or check for key existence.
| default_iface = gws['default'][netifaces.AF_INET][1] | |
| addrs = netifaces.ifaddresses(default_iface) | |
| broadcast_addr = addrs[netifaces.AF_INET][0].get('broadcast', '255.255.255.255') | |
| default = gws.get('default') | |
| default_inet = default.get(netifaces.AF_INET) if default else None | |
| default_iface = default_inet[1] if default_inet and len(default_inet) > 1 else None | |
| if default_iface: | |
| addrs = netifaces.ifaddresses(default_iface) | |
| inet_addrs = addrs.get(netifaces.AF_INET) | |
| if inet_addrs and len(inet_addrs) > 0: | |
| broadcast_addr = inet_addrs[0].get('broadcast', '255.255.255.255') | |
| else: | |
| broadcast_addr = '255.255.255.255' | |
| else: | |
| broadcast_addr = '255.255.255.255' |
| List[Bridge]: A list of discovered Bridge objects. | ||
| Raises: | ||
| Any exceptions raised by the underlying asyncio transport or protocol. |
Copilot
AI
Oct 31, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The 'Raises' section is vague and unhelpful. It should document specific exceptions that callers might need to handle. Based on the code, this function doesn't raise exceptions from the protocol (they're caught internally), but could raise exceptions from loop.create_datagram_endpoint(). Consider either removing this section or documenting specific exceptions like OSError for network issues.
| Any exceptions raised by the underlying asyncio transport or protocol. | |
| OSError: If a network error occurs while creating the datagram endpoint. |
| broadcast_addr = addrs[netifaces.AF_INET][0].get('broadcast', '255.255.255.255') | ||
| except Exception as e: | ||
| _LOGGER.warning("Could not determine broadcast address, using 255.255.255.255: %s", e) | ||
| broadcast_addr = '255.255.255.255' |
Copilot
AI
Oct 31, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The code indexes into addrs[netifaces.AF_INET][0] assuming the first address entry contains the broadcast address, but some network configurations may have multiple IPv4 addresses on an interface. While the fallback to '255.255.255.255' handles missing 'broadcast' keys, the indexing with [0] could still raise IndexError if the AF_INET list is empty. This is an edge case but worth addressing for robustness.
| broadcast_addr = addrs[netifaces.AF_INET][0].get('broadcast', '255.255.255.255') | |
| except Exception as e: | |
| _LOGGER.warning("Could not determine broadcast address, using 255.255.255.255: %s", e) | |
| broadcast_addr = '255.255.255.255' | |
| inet_addrs = addrs.get(netifaces.AF_INET, []) | |
| if inet_addrs and 'broadcast' in inet_addrs[0]: | |
| broadcast_addr = inet_addrs[0]['broadcast'] | |
| else: | |
| broadcast_addr = '255.255.255.255' |
|
I wonder if I can just use |
Problem
Discovery fails silently on Windows systems due to ProactorEventLoop not properly handling the
"<broadcast>"address, resulting in UDP packets not reaching the network interface. This particularly affects Windows systems with complex network configurations (multiple adapters, VirtualBox, WSL, etc.).Fixes #61
Root Cause
The hardcoded
"<broadcast>"string indiscovery.py:39works on Linux SelectorEventLoop but fails on Windows ProactorEventLoop, causing silent discovery failures where no bridges are found despite being present on the network.Solution
"<broadcast>"with programmatic broadcast address detection usingnetifaces255.255.255.255when interface detection failsTesting
Before (Windows)
After (Windows)
Cross-platform compatibility
Changes
netifacesdependency for network interface detectionBreaking Changes
None - maintains full backward compatibility.