-
-
Notifications
You must be signed in to change notification settings - Fork 25
Refactor connection handling #66
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
Conversation
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 introduces comprehensive test coverage and refactors the connection management architecture to be more robust and maintainable. The changes introduce automated reconnection logic, improved error handling, and better resource cleanup.
- Adds comprehensive test suite with unit and integration tests
- Refactors connection management to use a reconnection loop pattern
- Fixes boolean encoding bug in PDO value encoding
- Improves type annotations and error handling throughout
Reviewed Changes
Copilot reviewed 11 out of 13 changed files in this pull request and generated 15 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/test_util.py | Unit tests for PDO value encoding utilities |
| tests/test_integration.py | Integration tests for connection lifecycle and reconnection scenarios |
| tests/test_comfoconnect.py | Comprehensive unit tests for ComfoConnect class |
| tests/test_bridge.py | Unit tests for Bridge class and EventBus |
| tests/conftest.py | Shared test fixtures and helpers |
| pytest.ini | Pytest configuration for async test support |
| pyproject.toml | Added pytest-asyncio dependency |
| aiocomfoconnect/util.py | Fixed boolean encoding to use explicit byte conversion |
| aiocomfoconnect/comfoconnect.py | Refactored connection management with reconnection loop and improved type hints |
| aiocomfoconnect/bridge.py | Enhanced EventBus with better error handling and improved connection lifecycle management |
| .gitignore | Fixed to allow Python files and added VS Code exclusion |
| .github/copilot-instructions.md | Documentation for project tooling |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| self._sensor_callback_fn: Callable = sensor_callback | ||
| self._alarm_callback_fn: Callable = alarm_callback | ||
| self._sensor_callback_fn: Optional[Callable[[Sensor, Any], None]] = sensor_callback | ||
| self._alarm_callback_fn: Optional[Callable[[int, Dict[int, str]], None]] = alarm_callback |
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 type annotations for callbacks should use Optional consistently. However, line 56 appears correct but line 57 assumes alarm_callback always receives a Dict[int, str] for errors. Based on the test at test_comfoconnect.py:352, the errors parameter can be a dict, but the actual alarm handling code should be reviewed to ensure this type is accurate for all cases.
| self._alarm_callback_fn: Optional[Callable[[int, Dict[int, str]], None]] = alarm_callback | |
| self._alarm_callback_fn: Optional[Callable[[int, Optional[Dict[int, str]]], None]] = alarm_callback |
aiocomfoconnect/bridge.py
Outdated
|
|
||
| message = Message(cmd, msg, self._local_uuid, self.uuid) | ||
| if self._send_lock is None: | ||
| # TODO(stability): revisit this once message pipelining is supported safely. |
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 TODO comment lacks context about what specific stability concerns exist or what conditions would make message pipelining safe. Consider expanding the comment to explain the current limitation or risk.
| # TODO(stability): revisit this once message pipelining is supported safely. | |
| # TODO(stability): Currently, we use a lock to serialize outgoing messages because the underlying protocol and/or bridge device does not support concurrent (pipelined) requests safely. | |
| # Concurrent writes may result in interleaved or corrupted messages, race conditions, or unexpected bridge behavior. | |
| # Revisit this if the protocol is updated to support message pipelining, or if it can be demonstrated that concurrent requests are handled correctly by the bridge. |
|
|
||
| # Simulate alarm | ||
| mock_alarm = Mock() | ||
| mock_alarm.swProgramVersion = 3222278145 # Firmware > 1.4.0 |
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 magic number 3222278145 should be extracted as a named constant to improve readability and maintainability. Consider defining FIRMWARE_VERSION_1_4_0_PLUS = 3222278145 at the module level.
| comfoconnect._alarm_callback_fn = mock_callback | ||
|
|
||
| mock_alarm = Mock() | ||
| mock_alarm.swProgramVersion = 3222278145 # Firmware > 1.4.0 |
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.
Same magic number appears here. This duplication reinforces the need for a shared constant to avoid inconsistency if this value needs to change.
aiocomfoconnect/bridge.py
Outdated
|
|
||
| self._reference += 1 | ||
|
|
||
| assert fut is not None |
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.
Using assert for runtime validation in production code can be problematic since assertions can be disabled with python -O. Consider replacing this with an explicit check and raising a RuntimeError or similar exception if fut is None.
| assert fut is not None | |
| if fut is None: | |
| raise RuntimeError("Internal error: fut is None after send block") |
|
|
||
| await connected | ||
| await self._reconnect_task | ||
| except asyncio.CancelledError: |
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.
'except' clause does nothing but pass and there is no explanatory comment.
| except asyncio.CancelledError: | |
| except asyncio.CancelledError: | |
| # Task cancellation is expected during cleanup; ignore this exception. |
| self._reconnect_task.cancel() | ||
| try: | ||
| await self._reconnect_task | ||
| except asyncio.CancelledError: |
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.
'except' clause does nothing but pass and there is no explanatory comment.
| except asyncio.CancelledError: | |
| except asyncio.CancelledError: | |
| # Task was cancelled as part of cleanup; safe to ignore. |
| self._reconnect_task.cancel() | ||
| try: | ||
| await self._reconnect_task | ||
| except asyncio.CancelledError: |
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.
'except' clause does nothing but pass and there is no explanatory comment.
| except asyncio.CancelledError: | |
| except asyncio.CancelledError: | |
| # Task cancellation is expected during disconnect; ignore this exception. |
| with patch("asyncio.open_connection", side_effect=mock_open_connection): | ||
| try: | ||
| await comfoconnect.connect(LOCAL_UUID) | ||
| except AioComfoConnectTimeout: |
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.
'except' clause does nothing but pass and there is no explanatory comment.
| except AioComfoConnectTimeout: | |
| except AioComfoConnectTimeout: | |
| # Exception is expected during this test; ignore it. |
| assert not connect_task.done() | ||
|
|
||
| allow_start.set() | ||
| await connect_task |
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 statement has no effect.
This PR is the result of a session with Copilot where the connection handling is refactored.
In the process, tests were added as well.
I still need to review this properly.