nwp500-python 7.4.5__tar.gz → 7.4.6__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- nwp500_python-7.4.6/.bandit +3 -0
- nwp500_python-7.4.6/.github/RESOLVING_PR_COMMENTS.md +376 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/.github/copilot-instructions.md +65 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/.github/workflows/ci.yml +19 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/.gitignore +1 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/CHANGELOG.rst +27 -0
- {nwp500_python-7.4.5/src/nwp500_python.egg-info → nwp500_python-7.4.6}/PKG-INFO +4 -2
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/README.rst +1 -1
- nwp500_python-7.4.6/package-lock.json +6 -0
- nwp500_python-7.4.6/package.json +1 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/setup.cfg +1 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/src/nwp500/api_client.py +2 -1
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/src/nwp500/auth.py +5 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/src/nwp500/converters.py +2 -2
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/src/nwp500/factory.py +6 -1
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/src/nwp500/models.py +0 -1
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/src/nwp500/mqtt/client.py +13 -4
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/src/nwp500/mqtt/command_queue.py +1 -1
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/src/nwp500/mqtt/subscriptions.py +40 -9
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/src/nwp500/temperature.py +2 -2
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/src/nwp500/unit_system.py +2 -2
- {nwp500_python-7.4.5 → nwp500_python-7.4.6/src/nwp500_python.egg-info}/PKG-INFO +4 -2
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/src/nwp500_python.egg-info/SOURCES.txt +5 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/src/nwp500_python.egg-info/requires.txt +2 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/tests/test_model_converters.py +6 -8
- nwp500_python-7.4.6/tests/test_mqtt_hypothesis.py +180 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/tox.ini +8 -1
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/.agent/workflows/pre-completion-testing.md +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/.coveragerc +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/.github/workflows/release.yml +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/.pre-commit-config.yaml +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/.readthedocs.yml +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/AUTHORS.rst +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/CONTRIBUTING.rst +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/LICENSE.txt +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/Makefile +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/RELEASE.md +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/docs/Makefile +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/docs/_static/.gitignore +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/docs/api/nwp500.rst +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/docs/authors.rst +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/docs/changelog.rst +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/docs/conf.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/docs/configuration.rst +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/docs/development/contributing.rst +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/docs/development/history.rst +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/docs/enumerations.rst +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/docs/guides/advanced_features_explained.rst +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/docs/guides/authentication.rst +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/docs/guides/auto_recovery.rst +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/docs/guides/command_queue.rst +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/docs/guides/energy_monitoring.rst +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/docs/guides/event_system.rst +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/docs/guides/home_assistant_integration.rst +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/docs/guides/mqtt_diagnostics.rst +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/docs/guides/reservations.rst +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/docs/guides/scheduling_features.rst +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/docs/guides/time_of_use.rst +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/docs/guides/unit_conversion.rst +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/docs/index.rst +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/docs/installation.rst +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/docs/license.rst +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/docs/openapi.yaml +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/docs/protocol/data_conversions.rst +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/docs/protocol/device_features.rst +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/docs/protocol/device_status.rst +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/docs/protocol/error_codes.rst +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/docs/protocol/mqtt_protocol.rst +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/docs/protocol/quick_reference.rst +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/docs/protocol/rest_api.rst +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/docs/python_api/api_client.rst +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/docs/python_api/auth_client.rst +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/docs/python_api/cli.rst +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/docs/python_api/device_control.rst +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/docs/python_api/events.rst +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/docs/python_api/exceptions.rst +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/docs/python_api/models.rst +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/docs/python_api/mqtt_client.rst +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/docs/quickstart.rst +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/docs/requirements.txt +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/examples/.ruff.toml +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/examples/README.md +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/examples/advanced/air_filter_reset.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/examples/advanced/anti_legionella.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/examples/advanced/auto_recovery.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/examples/advanced/combined_callbacks.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/examples/advanced/demand_response.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/examples/advanced/device_capabilities.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/examples/advanced/device_status_debug.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/examples/advanced/energy_analytics.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/examples/advanced/error_code_demo.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/examples/advanced/mqtt_diagnostics.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/examples/advanced/power_control.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/examples/advanced/recirculation_control.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/examples/advanced/reconnection_demo.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/examples/advanced/reservation_schedule.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/examples/advanced/simple_auto_recovery.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/examples/advanced/token_restoration.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/examples/advanced/tou_openei.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/examples/advanced/tou_schedule.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/examples/advanced/water_reservation.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/examples/beginner/01_authentication.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/examples/beginner/02_list_devices.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/examples/beginner/03_get_status.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/examples/beginner/04_set_temperature.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/examples/intermediate/advanced_auth_patterns.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/examples/intermediate/command_queue.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/examples/intermediate/device_status_callback.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/examples/intermediate/error_handling.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/examples/intermediate/event_driven_control.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/examples/intermediate/improved_auth.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/examples/intermediate/legacy_auth_constructor.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/examples/intermediate/mqtt_realtime_monitoring.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/examples/intermediate/periodic_requests.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/examples/intermediate/set_mode.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/examples/intermediate/vacation_mode.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/examples/mask.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/examples/testing/periodic_device_info.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/examples/testing/simple_periodic_info.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/examples/testing/test_api_client.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/examples/testing/test_mqtt_connection.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/examples/testing/test_mqtt_messaging.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/examples/testing/test_periodic_minimal.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/pyproject.toml +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/scripts/README.md +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/scripts/bump_version.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/scripts/diagnose_mqtt_connection.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/scripts/extract_changelog.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/scripts/format.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/scripts/lint.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/scripts/setup-dev.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/scripts/validate_version.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/setup.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/src/nwp500/__init__.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/src/nwp500/cli/__init__.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/src/nwp500/cli/__main__.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/src/nwp500/cli/commands.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/src/nwp500/cli/handlers.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/src/nwp500/cli/monitoring.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/src/nwp500/cli/output_formatters.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/src/nwp500/cli/rich_output.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/src/nwp500/cli/token_storage.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/src/nwp500/command_decorators.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/src/nwp500/config.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/src/nwp500/device_capabilities.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/src/nwp500/device_info_cache.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/src/nwp500/encoding.py +4 -4
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/src/nwp500/enums.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/src/nwp500/events.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/src/nwp500/exceptions.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/src/nwp500/field_factory.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/src/nwp500/mqtt/__init__.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/src/nwp500/mqtt/connection.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/src/nwp500/mqtt/control.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/src/nwp500/mqtt/diagnostics.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/src/nwp500/mqtt/periodic.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/src/nwp500/mqtt/reconnection.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/src/nwp500/mqtt/utils.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/src/nwp500/mqtt_events.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/src/nwp500/py.typed +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/src/nwp500/topic_builder.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/src/nwp500/utils.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/src/nwp500_python.egg-info/dependency_links.txt +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/src/nwp500_python.egg-info/entry_points.txt +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/src/nwp500_python.egg-info/not-zip-safe +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/src/nwp500_python.egg-info/top_level.txt +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/tests/conftest.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/tests/test_api_helpers.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/tests/test_auth.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/tests/test_cli_basic.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/tests/test_cli_commands.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/tests/test_command_decorators.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/tests/test_command_queue.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/tests/test_device_capabilities.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/tests/test_device_info_cache.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/tests/test_events.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/tests/test_exceptions.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/tests/test_models.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/tests/test_mqtt_client_init.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/tests/test_temperature_converters.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/tests/test_unit_switching.py +0 -0
- {nwp500_python-7.4.5 → nwp500_python-7.4.6}/tests/test_utils.py +0 -0
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
# How to Resolve GitHub Review Comments
|
|
2
|
+
|
|
3
|
+
This guide documents how to mark review comment conversations as resolved after addressing reviewer feedback in pull requests.
|
|
4
|
+
|
|
5
|
+
## Quick Reference
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# 1. Get all review threads for PR #74
|
|
9
|
+
gh api graphql -f query='
|
|
10
|
+
query {
|
|
11
|
+
repository(owner: "eman", name: "nwp500-python") {
|
|
12
|
+
pullRequest(number: 74) {
|
|
13
|
+
reviewThreads(first: 10) {
|
|
14
|
+
nodes {
|
|
15
|
+
id
|
|
16
|
+
path
|
|
17
|
+
line
|
|
18
|
+
isResolved
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
' | jq '.data.repository.pullRequest.reviewThreads.nodes'
|
|
25
|
+
|
|
26
|
+
# 2. Resolve a specific thread
|
|
27
|
+
gh api graphql -f query='
|
|
28
|
+
mutation {
|
|
29
|
+
resolveReviewThread(input: {threadId: "PRRT_kwDOP_hNvM5ukIVT"}) {
|
|
30
|
+
thread { isResolved }
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
'
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Why Resolve Comments?
|
|
37
|
+
|
|
38
|
+
- **Clarity**: Shows reviewers their feedback has been acted upon
|
|
39
|
+
- **Workflow**: Prevents reviewers from re-reading already-addressed comments
|
|
40
|
+
- **Signal Readiness**: Indicates PR is ready for another review pass
|
|
41
|
+
- **Clean PR Interface**: Makes GitHub's PR conversation cleaner and easier to follow
|
|
42
|
+
|
|
43
|
+
## Workflow: Address and Resolve
|
|
44
|
+
|
|
45
|
+
### 1. Address the Comment
|
|
46
|
+
|
|
47
|
+
Make the code changes requested by the reviewer:
|
|
48
|
+
- Fix bugs
|
|
49
|
+
- Update documentation
|
|
50
|
+
- Refactor code
|
|
51
|
+
- Add tests
|
|
52
|
+
|
|
53
|
+
**Example:**
|
|
54
|
+
```bash
|
|
55
|
+
# Edit file based on comment
|
|
56
|
+
nano src/nwp500/converters.py
|
|
57
|
+
|
|
58
|
+
# Commit the change
|
|
59
|
+
git add src/nwp500/converters.py
|
|
60
|
+
git commit -m "Fix div_10 converter to handle string inputs"
|
|
61
|
+
|
|
62
|
+
# Push to remote
|
|
63
|
+
git push
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### 2. Verify Tests Pass
|
|
67
|
+
|
|
68
|
+
Run all validation before marking comments as resolved:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
# Run tests
|
|
72
|
+
pytest --ignore=tests/test_mqtt_hypothesis.py
|
|
73
|
+
|
|
74
|
+
# Run linting
|
|
75
|
+
make ci-lint
|
|
76
|
+
|
|
77
|
+
# Run type checking
|
|
78
|
+
python3 -m mypy src/nwp500 --config-file pyproject.toml
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
All must pass before proceeding.
|
|
82
|
+
|
|
83
|
+
### 3. Get Review Thread IDs
|
|
84
|
+
|
|
85
|
+
Query GraphQL to find threads that need resolving:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
gh api graphql -f query='
|
|
89
|
+
query {
|
|
90
|
+
repository(owner: "eman", name: "nwp500-python") {
|
|
91
|
+
pullRequest(number: 74) {
|
|
92
|
+
reviewThreads(first: 10) {
|
|
93
|
+
nodes {
|
|
94
|
+
id
|
|
95
|
+
path
|
|
96
|
+
line
|
|
97
|
+
isResolved
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
' | jq '.data.repository.pullRequest.reviewThreads.nodes'
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
**Output example:**
|
|
107
|
+
```json
|
|
108
|
+
[
|
|
109
|
+
{
|
|
110
|
+
"id": "PRRT_kwDOP_hNvM5ukIVT",
|
|
111
|
+
"path": "src/nwp500/converters.py",
|
|
112
|
+
"line": 125,
|
|
113
|
+
"isResolved": false
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
"id": "PRRT_kwDOP_hNvM5ukIVo",
|
|
117
|
+
"path": "tests/test_model_converters.py",
|
|
118
|
+
"line": 212,
|
|
119
|
+
"isResolved": false
|
|
120
|
+
}
|
|
121
|
+
]
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### 4. Identify Which Threads to Resolve
|
|
125
|
+
|
|
126
|
+
Cross-reference the output with:
|
|
127
|
+
1. Which file you modified
|
|
128
|
+
2. Which line the comment was on (approximately)
|
|
129
|
+
3. Whether `isResolved` is `false`
|
|
130
|
+
|
|
131
|
+
### 5. Resolve the Threads
|
|
132
|
+
|
|
133
|
+
For each thread you addressed:
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
gh api graphql -f query='
|
|
137
|
+
mutation {
|
|
138
|
+
resolveReviewThread(input: {threadId: "PRRT_kwDOP_hNvM5ukIVT"}) {
|
|
139
|
+
thread {
|
|
140
|
+
isResolved
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
'
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Success response:
|
|
148
|
+
```json
|
|
149
|
+
{
|
|
150
|
+
"data": {
|
|
151
|
+
"resolveReviewThread": {
|
|
152
|
+
"thread": {
|
|
153
|
+
"isResolved": true
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### 6. Verify All Are Resolved
|
|
161
|
+
|
|
162
|
+
Re-run the query from Step 3 to confirm all addressed threads now show `"isResolved": true`:
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
gh api graphql -f query='
|
|
166
|
+
query {
|
|
167
|
+
repository(owner: "eman", name: "nwp500-python") {
|
|
168
|
+
pullRequest(number: 74) {
|
|
169
|
+
reviewThreads(first: 10) {
|
|
170
|
+
nodes {
|
|
171
|
+
path
|
|
172
|
+
isResolved
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
' | jq '.data.repository.pullRequest.reviewThreads.nodes'
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
## Batch Resolving Multiple Threads
|
|
182
|
+
|
|
183
|
+
If you have many threads to resolve, use a loop:
|
|
184
|
+
|
|
185
|
+
```bash
|
|
186
|
+
#!/bin/bash
|
|
187
|
+
|
|
188
|
+
# Define thread IDs
|
|
189
|
+
THREAD_IDS=(
|
|
190
|
+
"PRRT_kwDOP_hNvM5ukIVT"
|
|
191
|
+
"PRRT_kwDOP_hNvM5ukIVo"
|
|
192
|
+
"PRRT_kwDOP_hNvM5ukIVx"
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
# Resolve each one
|
|
196
|
+
for thread_id in "${THREAD_IDS[@]}"; do
|
|
197
|
+
gh api graphql -f query="
|
|
198
|
+
mutation {
|
|
199
|
+
resolveReviewThread(input: {threadId: \"$thread_id\"}) {
|
|
200
|
+
thread { isResolved }
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
" && echo "✓ $thread_id resolved"
|
|
204
|
+
done
|
|
205
|
+
|
|
206
|
+
echo "All threads resolved!"
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
Or as a one-liner:
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
for id in PRRT_kwDOP_hNvM5ukIVT PRRT_kwDOP_hNvM5ukIVo PRRT_kwDOP_hNvM5ukIVx; do
|
|
213
|
+
gh api graphql -f query="mutation { resolveReviewThread(input: {threadId: \"$id\"}) { thread { isResolved } } }" && echo "✓ $id resolved"
|
|
214
|
+
done
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
## Shell Function (Optional)
|
|
218
|
+
|
|
219
|
+
Add this to your shell profile (`~/.bashrc`, `~/.zshrc`, etc.):
|
|
220
|
+
|
|
221
|
+
```bash
|
|
222
|
+
# Resolve a single GitHub PR review thread
|
|
223
|
+
resolve-pr-thread() {
|
|
224
|
+
local thread_id="$1"
|
|
225
|
+
if [[ -z "$thread_id" ]]; then
|
|
226
|
+
echo "Usage: resolve-pr-thread THREAD_ID"
|
|
227
|
+
echo "Example: resolve-pr-thread PRRT_kwDOP_hNvM5ukIVT"
|
|
228
|
+
return 1
|
|
229
|
+
fi
|
|
230
|
+
|
|
231
|
+
gh api graphql -f query="
|
|
232
|
+
mutation {
|
|
233
|
+
resolveReviewThread(input: {threadId: \"$thread_id\"}) {
|
|
234
|
+
thread { isResolved }
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
" | jq '.data.resolveReviewThread.thread.isResolved'
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
# Get all unresolved threads for a PR
|
|
241
|
+
pr-threads() {
|
|
242
|
+
local pr_num="${1:?PR number required}"
|
|
243
|
+
gh api graphql -f query="
|
|
244
|
+
query {
|
|
245
|
+
repository(owner: \"eman\", name: \"nwp500-python\") {
|
|
246
|
+
pullRequest(number: $pr_num) {
|
|
247
|
+
reviewThreads(first: 10) {
|
|
248
|
+
nodes {
|
|
249
|
+
id
|
|
250
|
+
path
|
|
251
|
+
line
|
|
252
|
+
isResolved
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
" | jq '.data.repository.pullRequest.reviewThreads.nodes'
|
|
259
|
+
}
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
Usage:
|
|
263
|
+
```bash
|
|
264
|
+
pr-threads 74 # List all threads for PR #74
|
|
265
|
+
resolve-pr-thread PRRT_kwDOP_hNvM5ukIVT # Resolve one thread
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
## Special Cases
|
|
269
|
+
|
|
270
|
+
### Unresolving a Thread
|
|
271
|
+
|
|
272
|
+
If a reviewer asks for changes after you marked it resolved, unresolve it:
|
|
273
|
+
|
|
274
|
+
```bash
|
|
275
|
+
gh api graphql -f query='
|
|
276
|
+
mutation {
|
|
277
|
+
unresolveReviewThread(input: {threadId: "PRRT_kwDOP_hNvM5ukIVT"}) {
|
|
278
|
+
thread {
|
|
279
|
+
isResolved
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
'
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
### Force-Pushed Commits
|
|
287
|
+
|
|
288
|
+
When you amend and force-push commits:
|
|
289
|
+
1. Old review threads remain resolvable
|
|
290
|
+
2. Thread IDs don't change
|
|
291
|
+
3. Line numbers may be different in new commits
|
|
292
|
+
4. Comments still point to old code but threads can be resolved
|
|
293
|
+
5. This is normal GitHub behavior - resolve all threads once your changes are complete
|
|
294
|
+
|
|
295
|
+
### Multiple Changes to Same File
|
|
296
|
+
|
|
297
|
+
If a reviewer left multiple comments on the same file and you addressed them all:
|
|
298
|
+
1. Make all changes to the file
|
|
299
|
+
2. Commit and push
|
|
300
|
+
3. Get all thread IDs for that file
|
|
301
|
+
4. Resolve each one individually
|
|
302
|
+
|
|
303
|
+
## Troubleshooting
|
|
304
|
+
|
|
305
|
+
### "Not Found" Error
|
|
306
|
+
|
|
307
|
+
**Problem:**
|
|
308
|
+
```
|
|
309
|
+
{
|
|
310
|
+
"message": "Not Found",
|
|
311
|
+
"documentation_url": "https://docs.github.com/rest",
|
|
312
|
+
"status": 404
|
|
313
|
+
}
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
**Solutions:**
|
|
317
|
+
- Verify PR number is correct
|
|
318
|
+
- Check you have access to the repository
|
|
319
|
+
- Verify `gh auth status` shows you're authenticated
|
|
320
|
+
- Try running `gh auth login` again
|
|
321
|
+
|
|
322
|
+
### No Threads Returned
|
|
323
|
+
|
|
324
|
+
**Problem:**
|
|
325
|
+
```
|
|
326
|
+
{
|
|
327
|
+
"data": {
|
|
328
|
+
"repository": {
|
|
329
|
+
"pullRequest": {
|
|
330
|
+
"reviewThreads": {
|
|
331
|
+
"nodes": []
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
**Solutions:**
|
|
340
|
+
- Verify PR number is correct
|
|
341
|
+
- The PR may have only general comments (not review comments)
|
|
342
|
+
- Try increasing `first: 10` to `first: 100` if there are many threads
|
|
343
|
+
- Verify reviewers left inline code comments, not just PR comments
|
|
344
|
+
|
|
345
|
+
### Thread Won't Resolve
|
|
346
|
+
|
|
347
|
+
**Problem:**
|
|
348
|
+
Mutation succeeds but `isResolved` still returns `false` on next check
|
|
349
|
+
|
|
350
|
+
**Solutions:**
|
|
351
|
+
- Wait a moment and query again (GitHub API may have delay)
|
|
352
|
+
- Verify you're using the exact same thread ID
|
|
353
|
+
- Check that you have write permissions on the repository
|
|
354
|
+
- Try running `gh auth refresh` to refresh your token
|
|
355
|
+
|
|
356
|
+
### Can't Find Thread ID for Comment I Fixed
|
|
357
|
+
|
|
358
|
+
**Problem:**
|
|
359
|
+
You fixed the code but can't find the matching thread ID
|
|
360
|
+
|
|
361
|
+
**Possible Causes:**
|
|
362
|
+
1. The comment was a general PR comment, not an inline code comment (can't be resolved)
|
|
363
|
+
2. The thread was already resolved by someone else
|
|
364
|
+
3. You're looking at the wrong PR number
|
|
365
|
+
4. The comment was deleted by the reviewer
|
|
366
|
+
|
|
367
|
+
**Solution:**
|
|
368
|
+
Go to the PR on GitHub.com and manually verify the comment still exists and is an inline review comment (not a general comment).
|
|
369
|
+
|
|
370
|
+
## References
|
|
371
|
+
|
|
372
|
+
- [GitHub GraphQL API: resolveReviewThread](https://docs.github.com/en/graphql/reference/mutations#resolvereviewthread)
|
|
373
|
+
- [GitHub GraphQL API: unresolveReviewThread](https://docs.github.com/en/graphql/reference/mutations#unresolvereviewthread)
|
|
374
|
+
- [GitHub GraphQL API: Review Threads](https://docs.github.com/en/graphql/reference/objects#reviewthread)
|
|
375
|
+
- [GitHub CLI: gh api](https://cli.github.com/manual/gh_api)
|
|
376
|
+
- [GitHub: Review conversations](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/reviewing-changes-in-pull-requests/commenting-on-a-pull-request#about-pull-request-reviews)
|
|
@@ -54,6 +54,71 @@ When working on pull requests, use the GitHub CLI to access review comments:
|
|
|
54
54
|
|
|
55
55
|
This ensures you can address all feedback from code reviewers systematically.
|
|
56
56
|
|
|
57
|
+
#### Marking Conversations as Resolved
|
|
58
|
+
|
|
59
|
+
After addressing a review comment, mark the conversation as resolved to signal reviewers that the feedback has been acted upon.
|
|
60
|
+
|
|
61
|
+
**Step 1: Get review thread IDs**
|
|
62
|
+
```bash
|
|
63
|
+
gh api graphql -f query='
|
|
64
|
+
query {
|
|
65
|
+
repository(owner: "eman", name: "nwp500-python") {
|
|
66
|
+
pullRequest(number: 74) {
|
|
67
|
+
reviewThreads(first: 10) {
|
|
68
|
+
nodes {
|
|
69
|
+
id
|
|
70
|
+
path
|
|
71
|
+
line
|
|
72
|
+
isResolved
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
'
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Replace `74` with the actual PR number. This returns thread IDs like `PRRT_kwDOP_hNvM5ukIVT`.
|
|
82
|
+
|
|
83
|
+
**Step 2: Resolve a thread**
|
|
84
|
+
```bash
|
|
85
|
+
gh api graphql -f query='
|
|
86
|
+
mutation {
|
|
87
|
+
resolveReviewThread(input: {threadId: "PRRT_kwDOP_hNvM5ukIVT"}) {
|
|
88
|
+
thread {
|
|
89
|
+
isResolved
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
'
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Replace the thread ID with the one from Step 1. Success response shows `"isResolved": true`.
|
|
97
|
+
|
|
98
|
+
**Step 3: Resolve multiple threads efficiently**
|
|
99
|
+
```bash
|
|
100
|
+
for id in PRRT_kwDOP_hNvM5ukIVT PRRT_kwDOP_hNvM5ukIVo PRRT_kwDOP_hNvM5ukIVx; do
|
|
101
|
+
gh api graphql -f query="
|
|
102
|
+
mutation {
|
|
103
|
+
resolveReviewThread(input: {threadId: \"$id\"}) {
|
|
104
|
+
thread { isResolved }
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
" && echo "✓ $id resolved"
|
|
108
|
+
done
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
**Step 4: Verify all are resolved**
|
|
112
|
+
Re-run Step 1 query and confirm all addressed threads show `"isResolved": true`.
|
|
113
|
+
|
|
114
|
+
**Important Notes:**
|
|
115
|
+
- Only inline code review comments can be resolved (not general PR comments)
|
|
116
|
+
- Conversations remain resolvable even after force-pushing commits
|
|
117
|
+
- When commits are amended and force-pushed, old thread IDs remain valid
|
|
118
|
+
- If you need to reopen a resolved thread, use `unresolveReviewThread` mutation instead
|
|
119
|
+
|
|
120
|
+
See detailed instructions at `.github/copilot-instructions.md` for more examples and troubleshooting.
|
|
121
|
+
|
|
57
122
|
### Before Committing Changes
|
|
58
123
|
Always run these checks before finalizing changes to ensure your code will pass CI:
|
|
59
124
|
1. **Linting**: `make ci-lint` - Ensures code style matches CI requirements
|
|
@@ -28,6 +28,25 @@ jobs:
|
|
|
28
28
|
- name: Run tox lint
|
|
29
29
|
run: tox -e lint
|
|
30
30
|
|
|
31
|
+
security:
|
|
32
|
+
name: Security Check
|
|
33
|
+
runs-on: ubuntu-latest
|
|
34
|
+
steps:
|
|
35
|
+
- uses: actions/checkout@v4
|
|
36
|
+
|
|
37
|
+
- name: Set up Python
|
|
38
|
+
uses: actions/setup-python@v5
|
|
39
|
+
with:
|
|
40
|
+
python-version: '3.13'
|
|
41
|
+
|
|
42
|
+
- name: Install tox
|
|
43
|
+
run: |
|
|
44
|
+
python -m pip install --upgrade pip
|
|
45
|
+
python -m pip install tox
|
|
46
|
+
|
|
47
|
+
- name: Run bandit
|
|
48
|
+
run: tox -e bandit
|
|
49
|
+
|
|
31
50
|
test:
|
|
32
51
|
name: Test on Python ${{ matrix.python-version }}
|
|
33
52
|
runs-on: ubuntu-latest
|
|
@@ -2,6 +2,33 @@
|
|
|
2
2
|
Changelog
|
|
3
3
|
=========
|
|
4
4
|
|
|
5
|
+
Version 7.4.6 (2026-02-13)
|
|
6
|
+
==========================
|
|
7
|
+
|
|
8
|
+
Fixed
|
|
9
|
+
-----
|
|
10
|
+
- **Converter Consistency**: ``div_10()`` and ``mul_10()`` now correctly apply division/multiplication to all input types after ``float()`` conversion, not just ``int``/``float`` types
|
|
11
|
+
- **Reservation Decoding**: Fixed ``decode_reservation_hex()`` to validate chunk length before checking for empty entries, preventing potential out-of-bounds access
|
|
12
|
+
- **Factory Cleanup**: ``create_navien_clients()`` now properly cleans up auth session if authentication fails during context manager entry
|
|
13
|
+
- **MQTT Reconnection**: MQTT client now resubscribes to all topics after successful reconnection
|
|
14
|
+
- **Subscription Leak**: Fixed resource leak where ``wait_for_device_feature()`` did not unsubscribe its callback after completion
|
|
15
|
+
- **Duplicate Handlers**: Subscription manager now prevents duplicate callback registration for the same topic
|
|
16
|
+
- **Command Queue**: ``MqttCommandQueue`` now raises on ``QueueFull`` instead of silently swallowing the error
|
|
17
|
+
- **Flow Rate Metadata**: Removed hardcoded ``"GPM"`` unit from ``recirc_dhw_flow_rate`` field; unit is now dynamic based on unit system
|
|
18
|
+
- **Temperature Rounding**: ``RawCelsius`` Fahrenheit conversion now uses a catch-all default for standard rounding instead of matching only ``STANDARD`` enum value
|
|
19
|
+
- **Unit System Default**: ``is_metric_preferred()`` now returns ``False`` (Fahrenheit) instead of ``None`` when no unit system override or context is set
|
|
20
|
+
|
|
21
|
+
Security
|
|
22
|
+
--------
|
|
23
|
+
- **Sensitive Data Logging**: Redacted MQTT topics in subscription manager logging to prevent leaking device IDs (resolves CodeQL alerts)
|
|
24
|
+
|
|
25
|
+
Added
|
|
26
|
+
-----
|
|
27
|
+
- **Auth Session Property**: Added ``NavienAuthClient.session`` property to access the active ``aiohttp`` session without using ``getattr``
|
|
28
|
+
- **Unsubscribe Feature**: Added ``unsubscribe_device_feature()`` method to MQTT client and subscription manager for targeted callback removal
|
|
29
|
+
- **Hypothesis Fuzzing**: Added property-based fuzzing tests for MQTT payload handling
|
|
30
|
+
- **Bandit Security Scanning**: Added bandit configuration for security analysis in CI
|
|
31
|
+
|
|
5
32
|
Version 7.4.5 (2026-02-04)
|
|
6
33
|
==========================
|
|
7
34
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nwp500-python
|
|
3
|
-
Version: 7.4.
|
|
3
|
+
Version: 7.4.6
|
|
4
4
|
Summary: A library for controlling Navien NWP500 Water Heaters via NaviLink
|
|
5
5
|
Home-page: https://github.com/eman/nwp500-python
|
|
6
6
|
Author: Emmanuel Levijarvi
|
|
@@ -29,6 +29,7 @@ Requires-Dist: setuptools; extra == "testing"
|
|
|
29
29
|
Requires-Dist: pytest; extra == "testing"
|
|
30
30
|
Requires-Dist: pytest-cov; extra == "testing"
|
|
31
31
|
Requires-Dist: pytest-asyncio; extra == "testing"
|
|
32
|
+
Requires-Dist: hypothesis; extra == "testing"
|
|
32
33
|
Provides-Extra: dev
|
|
33
34
|
Requires-Dist: ruff>=0.1.0; extra == "dev"
|
|
34
35
|
Requires-Dist: pyright>=1.1.0; extra == "dev"
|
|
@@ -36,6 +37,7 @@ Requires-Dist: setuptools; extra == "dev"
|
|
|
36
37
|
Requires-Dist: pytest; extra == "dev"
|
|
37
38
|
Requires-Dist: pytest-cov; extra == "dev"
|
|
38
39
|
Requires-Dist: pytest-asyncio; extra == "dev"
|
|
40
|
+
Requires-Dist: hypothesis; extra == "dev"
|
|
39
41
|
Dynamic: license-file
|
|
40
42
|
|
|
41
43
|
=============
|
|
@@ -110,7 +112,7 @@ Basic Usage
|
|
|
110
112
|
if device:
|
|
111
113
|
# Access status information
|
|
112
114
|
status = device.status
|
|
113
|
-
print(f"Water Temperature: {status.dhw_temperature}
|
|
115
|
+
print(f"Water Temperature: {status.dhw_temperature}")
|
|
114
116
|
print(f"Tank Charge: {status.dhw_charge_per}%")
|
|
115
117
|
print(f"Power Consumption: {status.current_inst_power}W")
|
|
116
118
|
|
|
@@ -70,7 +70,7 @@ Basic Usage
|
|
|
70
70
|
if device:
|
|
71
71
|
# Access status information
|
|
72
72
|
status = device.status
|
|
73
|
-
print(f"Water Temperature: {status.dhw_temperature}
|
|
73
|
+
print(f"Water Temperature: {status.dhw_temperature}")
|
|
74
74
|
print(f"Tank Charge: {status.dhw_charge_per}%")
|
|
75
75
|
print(f"Power Consumption: {status.current_inst_power}W")
|
|
76
76
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{}
|
|
@@ -78,7 +78,8 @@ class NavienAPIClient:
|
|
|
78
78
|
|
|
79
79
|
self.base_url = base_url.rstrip("/")
|
|
80
80
|
self._auth_client = auth_client
|
|
81
|
-
self._session = session or
|
|
81
|
+
self._session = session or auth_client.session
|
|
82
|
+
|
|
82
83
|
if self._session is None:
|
|
83
84
|
raise ValueError(
|
|
84
85
|
"auth_client must have an active session or a session "
|
|
@@ -633,6 +633,11 @@ class NavienAuthClient:
|
|
|
633
633
|
|
|
634
634
|
return tokens
|
|
635
635
|
|
|
636
|
+
@property
|
|
637
|
+
def session(self) -> aiohttp.ClientSession | None:
|
|
638
|
+
"""Get the active aiohttp session."""
|
|
639
|
+
return self._session
|
|
640
|
+
|
|
636
641
|
@property
|
|
637
642
|
def is_authenticated(self) -> bool:
|
|
638
643
|
"""Check if client is currently authenticated."""
|
|
@@ -122,7 +122,7 @@ def div_10(value: Any) -> float:
|
|
|
122
122
|
"""
|
|
123
123
|
if isinstance(value, (int, float)):
|
|
124
124
|
return float(value) / 10.0
|
|
125
|
-
return float(value)
|
|
125
|
+
return float(value) / 10.0
|
|
126
126
|
|
|
127
127
|
|
|
128
128
|
def mul_10(value: Any) -> float:
|
|
@@ -145,7 +145,7 @@ def mul_10(value: Any) -> float:
|
|
|
145
145
|
"""
|
|
146
146
|
if isinstance(value, (int, float)):
|
|
147
147
|
return float(value) * 10.0
|
|
148
|
-
return float(value)
|
|
148
|
+
return float(value) * 10.0
|
|
149
149
|
|
|
150
150
|
|
|
151
151
|
def enum_validator(enum_class: type[Any]) -> Callable[[Any], Any]:
|
|
@@ -73,7 +73,12 @@ async def create_navien_clients(
|
|
|
73
73
|
auth_client = NavienAuthClient(email, password)
|
|
74
74
|
|
|
75
75
|
# Authenticate and enter context manager
|
|
76
|
-
|
|
76
|
+
try:
|
|
77
|
+
await auth_client.__aenter__()
|
|
78
|
+
except BaseException:
|
|
79
|
+
# Ensure session is cleaned up if authentication fails
|
|
80
|
+
await auth_client.__aexit__(None, None, None)
|
|
81
|
+
raise
|
|
77
82
|
|
|
78
83
|
# Create API and MQTT clients that share the session
|
|
79
84
|
api_client = NavienAPIClient(auth_client=auth_client)
|
|
@@ -775,7 +775,6 @@ class DeviceStatus(NavienBaseModel):
|
|
|
775
775
|
recirc_dhw_flow_rate: FlowRate = Field(
|
|
776
776
|
description="Recirculation DHW flow rate (dynamic units: LPM/GPM)",
|
|
777
777
|
json_schema_extra={
|
|
778
|
-
"unit_of_measurement": "GPM",
|
|
779
778
|
"device_class": "flow_rate",
|
|
780
779
|
},
|
|
781
780
|
)
|
|
@@ -375,6 +375,7 @@ class NavienMqttClient(EventEmitter):
|
|
|
375
375
|
self._subscription_manager.update_connection(
|
|
376
376
|
self._connection
|
|
377
377
|
)
|
|
378
|
+
await self._subscription_manager.resubscribe_all()
|
|
378
379
|
|
|
379
380
|
_logger.info("Active reconnection successful")
|
|
380
381
|
else:
|
|
@@ -894,6 +895,16 @@ class NavienMqttClient(EventEmitter):
|
|
|
894
895
|
"subscribe_device_feature", device, callback
|
|
895
896
|
)
|
|
896
897
|
|
|
898
|
+
async def unsubscribe_device_feature(
|
|
899
|
+
self, device: Device, callback: Callable[[DeviceFeature], None]
|
|
900
|
+
) -> None:
|
|
901
|
+
"""Unsubscribe a specific device feature callback."""
|
|
902
|
+
if not self._connected or not self._subscription_manager:
|
|
903
|
+
return
|
|
904
|
+
await self._subscription_manager.unsubscribe_device_feature(
|
|
905
|
+
device, callback
|
|
906
|
+
)
|
|
907
|
+
|
|
897
908
|
async def subscribe_energy_usage(
|
|
898
909
|
self,
|
|
899
910
|
device: Device,
|
|
@@ -961,10 +972,8 @@ class NavienMqttClient(EventEmitter):
|
|
|
961
972
|
)
|
|
962
973
|
return False
|
|
963
974
|
finally:
|
|
964
|
-
#
|
|
965
|
-
|
|
966
|
-
# But the subscription manager handles multiple callbacks.
|
|
967
|
-
pass
|
|
975
|
+
# Unsubscribe using the specific callback to avoid leaking resources
|
|
976
|
+
await self.unsubscribe_device_feature(device, on_feature)
|
|
968
977
|
|
|
969
978
|
@property
|
|
970
979
|
def control(self) -> MqttDeviceController:
|