nwp500-python 9.13.0__tar.gz → 9.13.1__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-9.13.0 → nwp500_python-9.13.1}/.github/workflows/ci.yml +11 -11
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/.github/workflows/release.yml +7 -7
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/CHANGELOG.rst +116 -0
- {nwp500_python-9.13.0/src/nwp500_python.egg-info → nwp500_python-9.13.1}/PKG-INFO +2 -2
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/how-to/diagnose-mqtt.rst +42 -9
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/reference/configuration.rst +1 -1
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/reference/installation.rst +3 -3
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/reference/protocol/mqtt_protocol.rst +4 -3
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/pyproject.toml +1 -5
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/setup.cfg +9 -3
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/__init__.py +1 -1
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/ble/mqtt_adapter.py +13 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/cli/__main__.py +6 -2
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/cli/handlers.py +48 -7
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/converters.py +39 -5
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/device_capabilities.py +6 -4
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/models/feature.py +9 -3
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/models/status.py +27 -12
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/mqtt/_device_subscriptions.py +13 -13
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/mqtt/client.py +25 -22
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/mqtt/connection.py +137 -30
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/mqtt/control.py +17 -2
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/mqtt/periodic.py +28 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/mqtt/subscriptions.py +371 -150
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/mqtt/utils.py +153 -2
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/sim/api.py +35 -6
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/sim/engine.py +174 -28
- {nwp500_python-9.13.0 → nwp500_python-9.13.1/src/nwp500_python.egg-info}/PKG-INFO +2 -2
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500_python.egg-info/SOURCES.txt +6 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500_python.egg-info/requires.txt +1 -1
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500_python.egg-info/scm_file_list.json +6 -0
- nwp500_python-9.13.1/src/nwp500_python.egg-info/scm_version.json +8 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_cli_commands.py +10 -0
- nwp500_python-9.13.1/tests/test_cli_reply_cleanup.py +139 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_model_converters.py +19 -4
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_mqtt_clean_session_resume.py +2 -1
- nwp500_python-9.13.1/tests/test_mqtt_connection_builder.py +114 -0
- nwp500_python-9.13.1/tests/test_mqtt_connection_robustness.py +446 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_mqtt_control_fail.py +2 -1
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_mqtt_last_will.py +2 -6
- nwp500_python-9.13.1/tests/test_mqtt_parse_once.py +502 -0
- nwp500_python-9.13.1/tests/test_mqtt_payload_log.py +305 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_mqtt_reconnection_storm.py +5 -1
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_sim_import_contract.py +2 -3
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_sim_ready_api.py +22 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_sim_walk_cost.py +128 -2
- nwp500_python-9.13.1/tests/test_unknown_enum_values.py +143 -0
- nwp500_python-9.13.0/src/nwp500_python.egg-info/scm_version.json +0 -8
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/.coveragerc +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/.github/RESOLVING_PR_COMMENTS.md +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/.github/copilot-instructions.md +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/.gitignore +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/.pre-commit-config.yaml +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/.readthedocs.yml +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/AUTHORS.rst +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/CONTRIBUTING.rst +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/LICENSE.txt +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/Makefile +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/README.rst +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/RELEASE.md +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/Makefile +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/_static/.gitignore +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/_static/light-page.js +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/conf.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/explanation/architecture.rst +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/explanation/draws.rst +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/explanation/heating-elements.rst +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/explanation/index.rst +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/explanation/recovery-speed.rst +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/explanation/simulator-methodology.rst +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/explanation/simulator-tried.rst +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/explanation/simulator.rst +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/explanation/standby-loss.rst +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/explanation/tank-energy.rst +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/explanation/tou-recovery-cap.rst +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/explanation/unknown-values.rst +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/explanation/what-starts-a-recovery.rst +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/how-to/authenticate.rst +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/how-to/auto-recovery.rst +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/how-to/bluetooth.rst +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/how-to/calibrate-simulator.rst +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/how-to/home-assistant.rst +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/how-to/index.rst +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/how-to/maintenance.rst +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/how-to/manage-units.rst +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/how-to/monitor-status.rst +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/how-to/optimize-tou.rst +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/how-to/queue-commands.rst +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/how-to/schedule-operation.rst +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/how-to/simulate-heater.rst +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/how-to/track-energy.rst +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/index.rst +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/openapi.yaml +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/project/authors.rst +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/project/changelog.rst +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/project/contributing.rst +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/project/history.rst +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/project/license.rst +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/reference/enumerations.rst +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/reference/index.rst +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/reference/protocol/data_conversions.rst +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/reference/protocol/device_features.rst +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/reference/protocol/device_status.rst +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/reference/protocol/error_codes.rst +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/reference/protocol/quick_reference.rst +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/reference/protocol/rest_api.rst +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/reference/python_api/api_client.rst +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/reference/python_api/auth_client.rst +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/reference/python_api/cli.rst +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/reference/python_api/events.rst +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/reference/python_api/exceptions.rst +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/reference/python_api/models.rst +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/reference/python_api/mqtt_client.rst +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/reference/python_api/sim.rst +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/reference/simulator-inputs.rst +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/requirements.txt +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/tutorials/getting-started.rst +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/examples/.ruff.toml +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/examples/README.md +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/examples/advanced/air_filter_reset.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/examples/advanced/anti_legionella.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/examples/advanced/auto_recovery.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/examples/advanced/combined_callbacks.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/examples/advanced/demand_response.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/examples/advanced/device_capabilities.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/examples/advanced/device_status_debug.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/examples/advanced/energy_analytics.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/examples/advanced/error_code_demo.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/examples/advanced/firmware_payload_capture.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/examples/advanced/mqtt_diagnostics.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/examples/advanced/power_control.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/examples/advanced/recirculation_control.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/examples/advanced/reconnection_demo.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/examples/advanced/reservation_schedule.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/examples/advanced/simple_auto_recovery.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/examples/advanced/token_restoration.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/examples/advanced/tou_openei.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/examples/advanced/tou_schedule.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/examples/advanced/water_reservation.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/examples/beginner/01_authentication.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/examples/beginner/02_list_devices.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/examples/beginner/03_get_status.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/examples/beginner/04_set_temperature.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/examples/intermediate/advanced_auth_patterns.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/examples/intermediate/command_queue.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/examples/intermediate/device_status_callback.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/examples/intermediate/error_handling.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/examples/intermediate/event_driven_control.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/examples/intermediate/improved_auth.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/examples/intermediate/legacy_auth_constructor.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/examples/intermediate/mqtt_realtime_monitoring.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/examples/intermediate/periodic_requests.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/examples/intermediate/set_mode.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/examples/intermediate/vacation_mode.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/examples/mask.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/examples/simulator/lower-setpoint.json +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/examples/simulator/plan.json +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/examples/simulator/scenario.json +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/examples/testing/periodic_device_info.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/examples/testing/simple_periodic_info.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/examples/testing/test_api_client.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/examples/testing/test_mqtt_connection.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/examples/testing/test_mqtt_messaging.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/examples/testing/test_periodic_minimal.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/scripts/README.md +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/scripts/bump_version.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/scripts/diagnose_mqtt_connection.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/scripts/extract_changelog.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/scripts/format.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/scripts/lint.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/scripts/setup-dev.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/scripts/validate_version.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/_base.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/api_client.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/auth.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/ble/__init__.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/ble/__main__.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/ble/client.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/ble/protocol.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/cli/__init__.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/cli/ble_backend.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/cli/calibration.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/cli/monitoring.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/cli/output_formatters.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/cli/presentation.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/cli/recording.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/cli/rich_output.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/cli/simulate.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/cli/token_storage.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/command_decorators.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/config.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/device_info_cache.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/encoding.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/enums.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/events.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/exceptions.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/factory.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/field_factory.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/models/__init__.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/models/_converters.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/models/account.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/models/device.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/models/diagnostics.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/models/energy.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/models/firmware.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/models/maintenance.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/models/mqtt_models.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/models/schedule.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/models/tou.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/mqtt/__init__.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/mqtt/_control_commands.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/mqtt/command_queue.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/mqtt/diagnostics.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/mqtt/reconnection.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/mqtt/state_tracker.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/mqtt/types.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/mqtt_events.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/openei.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/py.typed +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/reservations.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/sim/__init__.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/sim/attribution.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/sim/calibration.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/sim/carry.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/sim/circulation.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/sim/codec.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/sim/cold_supply.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/sim/controller.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/sim/decay.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/sim/defaults.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/sim/delivery.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/sim/draw_ensemble.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/sim/draw_forecast.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/sim/extensions.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/sim/heat_pump_fit.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/sim/heat_pump_recovery.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/sim/heating_trajectory.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/sim/history.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/sim/history_frame.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/sim/inputs.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/sim/location_air.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/sim/margins.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/sim/observed.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/sim/params.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/sim/physics/__init__.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/sim/physics/draw.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/sim/physics/element.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/sim/physics/heat_pump.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/sim/physics/stratified.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/sim/physics/water.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/sim/profile.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/sim/ready.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/sim/record.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/sim/result.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/sim/schedules.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/sim/tank_fit.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/sim/tank_physics.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/sim/tou_triggers.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/sim/units.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/sim_adapters.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/sim_calibrate.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/sim_home_assistant.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/sim_live.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/sim_score.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/status_history.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/temperature.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/topic_builder.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/tou_schedule.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/unit_system.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/utils.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500_python.egg-info/dependency_links.txt +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500_python.egg-info/entry_points.txt +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500_python.egg-info/not-zip-safe +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500_python.egg-info/top_level.txt +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/conftest.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/fixtures/sim/circulation_run_2026-10-29.json +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/fixtures/sim/engine_golden.json +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/fixtures/sim/heater_params.json +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/fixtures/sim/heater_params_fitted.json +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/fixtures/sim/heater_params_two_zone.json +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/sim_cases.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_api_endpoints_201.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_api_helpers.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_app_queries.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_auth.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_auth_session_lifecycle.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_auth_session_refresh.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_ble_mqtt_adapter.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_ble_protocol.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_bug_fixes.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_canonical_schedule.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_cli_basic.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_cli_calibrate.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_cli_simulate.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_cli_tristate_presentation.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_command_decorators.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_command_queue.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_device_capabilities.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_device_info_cache.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_device_rest_models.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_events.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_exceptions.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_models.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_mqtt_client_init.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_mqtt_events.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_mqtt_hypothesis.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_mqtt_reconnection.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_mqtt_reliability.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_multi_device.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_navilink_201_enums.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_openei.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_protocol_correctness.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_public_api.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_reservation_validation.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_reservations.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_sim_adapters.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_sim_api.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_sim_calibrate.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_sim_calibration.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_sim_carry.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_sim_change_history.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_sim_circulation.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_sim_codec.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_sim_cold_supply.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_sim_controller.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_sim_correction_top.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_sim_defaults.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_sim_documents.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_sim_draw.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_sim_draw_duration.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_sim_draw_ensemble.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_sim_draw_forecast.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_sim_electric.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_sim_element.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_sim_element_fit.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_sim_envelope.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_sim_fast_draws.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_sim_forecast.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_sim_golden.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_sim_heat_pump.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_sim_heat_pump_fit.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_sim_history.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_sim_home_assistant.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_sim_hot_water.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_sim_live.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_sim_location_air.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_sim_margins.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_sim_meter_reads.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_sim_no_clock_no_state.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_sim_observed.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_sim_overlay.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_sim_profile.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_sim_readiness.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_sim_ready.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_sim_replay.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_sim_resume.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_sim_schedules.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_sim_score.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_sim_score_days.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_sim_stratified.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_sim_tank_fit.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_sim_tank_physics.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_sim_undiluted_share.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_sim_water.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_status_history.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_temperature_converters.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_threading_model.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_token_storage.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_tou_api.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_tou_schedule.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_unit_switching.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_unit_system_process_wide.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_utility_modules.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_utils.py +0 -0
- {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tox.ini +0 -0
|
@@ -13,10 +13,10 @@ jobs:
|
|
|
13
13
|
name: Lint and Format Check
|
|
14
14
|
runs-on: ubuntu-latest
|
|
15
15
|
steps:
|
|
16
|
-
- uses: actions/checkout@
|
|
16
|
+
- uses: actions/checkout@v7
|
|
17
17
|
|
|
18
18
|
- name: Set up Python
|
|
19
|
-
uses: actions/setup-python@
|
|
19
|
+
uses: actions/setup-python@v7
|
|
20
20
|
with:
|
|
21
21
|
python-version: '3.14'
|
|
22
22
|
|
|
@@ -32,10 +32,10 @@ jobs:
|
|
|
32
32
|
name: Security Check
|
|
33
33
|
runs-on: ubuntu-latest
|
|
34
34
|
steps:
|
|
35
|
-
- uses: actions/checkout@
|
|
35
|
+
- uses: actions/checkout@v7
|
|
36
36
|
|
|
37
37
|
- name: Set up Python
|
|
38
|
-
uses: actions/setup-python@
|
|
38
|
+
uses: actions/setup-python@v7
|
|
39
39
|
with:
|
|
40
40
|
python-version: '3.14'
|
|
41
41
|
|
|
@@ -55,10 +55,10 @@ jobs:
|
|
|
55
55
|
python-version: ['3.14']
|
|
56
56
|
|
|
57
57
|
steps:
|
|
58
|
-
- uses: actions/checkout@
|
|
58
|
+
- uses: actions/checkout@v7
|
|
59
59
|
|
|
60
60
|
- name: Set up Python ${{ matrix.python-version }}
|
|
61
|
-
uses: actions/setup-python@
|
|
61
|
+
uses: actions/setup-python@v7
|
|
62
62
|
with:
|
|
63
63
|
python-version: ${{ matrix.python-version }}
|
|
64
64
|
|
|
@@ -82,10 +82,10 @@ jobs:
|
|
|
82
82
|
runs-on: ubuntu-latest
|
|
83
83
|
|
|
84
84
|
steps:
|
|
85
|
-
- uses: actions/checkout@
|
|
85
|
+
- uses: actions/checkout@v7
|
|
86
86
|
|
|
87
87
|
- name: Set up Python
|
|
88
|
-
uses: actions/setup-python@
|
|
88
|
+
uses: actions/setup-python@v7
|
|
89
89
|
with:
|
|
90
90
|
python-version: '3.14'
|
|
91
91
|
|
|
@@ -110,12 +110,12 @@ jobs:
|
|
|
110
110
|
needs: [lint, test, test-without-cli-extras]
|
|
111
111
|
|
|
112
112
|
steps:
|
|
113
|
-
- uses: actions/checkout@
|
|
113
|
+
- uses: actions/checkout@v7
|
|
114
114
|
with:
|
|
115
115
|
fetch-depth: 0 # Required for setuptools_scm
|
|
116
116
|
|
|
117
117
|
- name: Set up Python
|
|
118
|
-
uses: actions/setup-python@
|
|
118
|
+
uses: actions/setup-python@v7
|
|
119
119
|
with:
|
|
120
120
|
python-version: '3.14'
|
|
121
121
|
|
|
@@ -131,7 +131,7 @@ jobs:
|
|
|
131
131
|
run: twine check dist/*
|
|
132
132
|
|
|
133
133
|
- name: Upload artifacts
|
|
134
|
-
uses: actions/upload-artifact@
|
|
134
|
+
uses: actions/upload-artifact@v7
|
|
135
135
|
with:
|
|
136
136
|
name: dist
|
|
137
137
|
path: dist/
|
|
@@ -13,10 +13,10 @@ jobs:
|
|
|
13
13
|
name: Pre-release Checks
|
|
14
14
|
runs-on: ubuntu-latest
|
|
15
15
|
steps:
|
|
16
|
-
- uses: actions/checkout@
|
|
16
|
+
- uses: actions/checkout@v7
|
|
17
17
|
|
|
18
18
|
- name: Set up Python
|
|
19
|
-
uses: actions/setup-python@
|
|
19
|
+
uses: actions/setup-python@v7
|
|
20
20
|
with:
|
|
21
21
|
python-version: '3.14'
|
|
22
22
|
|
|
@@ -46,12 +46,12 @@ jobs:
|
|
|
46
46
|
id-token: write # Required for trusted publishing
|
|
47
47
|
|
|
48
48
|
steps:
|
|
49
|
-
- uses: actions/checkout@
|
|
49
|
+
- uses: actions/checkout@v7
|
|
50
50
|
with:
|
|
51
51
|
fetch-depth: 0 # Required for setuptools_scm
|
|
52
52
|
|
|
53
53
|
- name: Set up Python
|
|
54
|
-
uses: actions/setup-python@
|
|
54
|
+
uses: actions/setup-python@v7
|
|
55
55
|
with:
|
|
56
56
|
python-version: '3.14'
|
|
57
57
|
|
|
@@ -75,10 +75,10 @@ jobs:
|
|
|
75
75
|
needs: build-and-publish
|
|
76
76
|
|
|
77
77
|
steps:
|
|
78
|
-
- uses: actions/checkout@
|
|
78
|
+
- uses: actions/checkout@v7
|
|
79
79
|
|
|
80
80
|
- name: Set up Python
|
|
81
|
-
uses: actions/setup-python@
|
|
81
|
+
uses: actions/setup-python@v7
|
|
82
82
|
with:
|
|
83
83
|
python-version: '3.14'
|
|
84
84
|
|
|
@@ -93,7 +93,7 @@ jobs:
|
|
|
93
93
|
echo "EOF" >> $GITHUB_OUTPUT
|
|
94
94
|
|
|
95
95
|
- name: Create Release
|
|
96
|
-
uses: softprops/action-gh-release@
|
|
96
|
+
uses: softprops/action-gh-release@v3
|
|
97
97
|
with:
|
|
98
98
|
body: ${{ steps.changelog.outputs.notes }}
|
|
99
99
|
draft: false
|
|
@@ -2,6 +2,122 @@
|
|
|
2
2
|
Changelog
|
|
3
3
|
=========
|
|
4
4
|
|
|
5
|
+
Unreleased
|
|
6
|
+
==========
|
|
7
|
+
|
|
8
|
+
Version 9.13.1 (2026-11-14)
|
|
9
|
+
===========================
|
|
10
|
+
|
|
11
|
+
This release makes the MQTT connection steadier, drops the
|
|
12
|
+
``awsiotsdk`` dependency for ``awscrt`` directly, keeps a message whose
|
|
13
|
+
enum code the library does not know, and makes a simulated recovery
|
|
14
|
+
with a TOU window ahead much faster.
|
|
15
|
+
|
|
16
|
+
Added
|
|
17
|
+
-----
|
|
18
|
+
|
|
19
|
+
- **Every MQTT payload sent and received can be logged.** Set the
|
|
20
|
+
``nwp500.mqtt.payloads`` logger to DEBUG for one line per message:
|
|
21
|
+
topic, packet id for a send, and the JSON with MAC addresses
|
|
22
|
+
redacted. See "What did the device get?" in the MQTT diagnostics
|
|
23
|
+
guide.
|
|
24
|
+
|
|
25
|
+
Changed
|
|
26
|
+
-------
|
|
27
|
+
|
|
28
|
+
- **Each command carries its own ``sessionID``**, the send time in
|
|
29
|
+
milliseconds, in place of one value for the life of the client.
|
|
30
|
+
Acknowledgements repeat it, so a reply can be matched to its
|
|
31
|
+
command. App-connection events and the Last-Will keep the client's
|
|
32
|
+
``session_id``.
|
|
33
|
+
- **The MQTT keep-alive default is 60 seconds**, down from 1200, so a
|
|
34
|
+
connection that dies without a disconnect is noticed in about a
|
|
35
|
+
minute and a half instead of about 20 minutes. Set
|
|
36
|
+
``MqttConnectionConfig.keep_alive_secs`` to keep the old value.
|
|
37
|
+
- **The MQTT connection is built with ``awscrt`` directly; the
|
|
38
|
+
``awsiotsdk`` dependency is gone.** It was used only to build the
|
|
39
|
+
signed websocket connection and pinned ``awscrt`` to one release.
|
|
40
|
+
The library now requires ``awscrt>=0.36.1,<0.39``. The CONNECT
|
|
41
|
+
packet no longer carries the SDK's name and version in its username.
|
|
42
|
+
|
|
43
|
+
Fixed
|
|
44
|
+
-----
|
|
45
|
+
|
|
46
|
+
- **A simulated recovery with a TOU window ahead is no longer slowed
|
|
47
|
+
by each draw.** A draw during a recovery judged the TOU cap again on
|
|
48
|
+
a 16-hour walk ahead, so a forecast's small draw every few minutes
|
|
49
|
+
walked the tank 40 to 50 times the simulated span. The cap is now
|
|
50
|
+
judged where it can act: as a window opens, at the cap's level, or
|
|
51
|
+
at the level inside a window. Results are unchanged. A 28-hour day
|
|
52
|
+
of 10-minute draws with a peak ahead went from 3 to 11 seconds to
|
|
53
|
+
about half a second, close to the same day without TOU (#409).
|
|
54
|
+
- **The TOU cap's look-ahead stops a running element at its off
|
|
55
|
+
point.** It walked the tank with what ran at its start, so an
|
|
56
|
+
element running beside the compressor heated for the whole 16 hours;
|
|
57
|
+
the cap was judged on an overheated tank, and a long enough walk
|
|
58
|
+
failed with non-finite layers.
|
|
59
|
+
- **A code the library does not know no longer drops the whole
|
|
60
|
+
message.** An unlisted ``errorCode``, ``operationMode``,
|
|
61
|
+
``currentHeatUse`` or any other enum field in a status or feature
|
|
62
|
+
message failed validation, and the message was discarded, so status
|
|
63
|
+
updates stopped when the heater reported a new fault. An unknown code
|
|
64
|
+
now becomes a pseudo-member named ``UNKNOWN_<code>``: it compares
|
|
65
|
+
equal to the code, ``.name`` and ``.value`` work as for any member,
|
|
66
|
+
and it survives pickling and copying. The device list's
|
|
67
|
+
``connected`` and the feature's ``volumeCode`` do the same, through
|
|
68
|
+
``converters.enum_validator``.
|
|
69
|
+
- **An unknown ``dhwTemperatureSettingUse`` code does not permit
|
|
70
|
+
temperature writes.** Only the three known enabled control types
|
|
71
|
+
pass the ``dhw_temperature_setting_use`` capability check.
|
|
72
|
+
- **A status or feature message is parsed and announced once.** With
|
|
73
|
+
two callbacks subscribed for one device, each message was validated
|
|
74
|
+
twice and ``status_received`` (or ``feature_received``) was emitted
|
|
75
|
+
twice. Callbacks for the same device now share one parsed object,
|
|
76
|
+
and the event and state tracking run once per message.
|
|
77
|
+
- **The CLI unsubscribes the callbacks it registers to wait for a
|
|
78
|
+
reply**, so ``monitor`` and other long-running commands no longer
|
|
79
|
+
get each later message again for every earlier request. Over
|
|
80
|
+
Bluetooth the adapter gains the matching
|
|
81
|
+
``unsubscribe_device_status`` and ``unsubscribe_device_feature``.
|
|
82
|
+
- **An unacknowledged unsubscribe no longer leaves the topic marked
|
|
83
|
+
subscribed.** When the last callback for a topic was removed and the
|
|
84
|
+
broker's acknowledgement never came, the next subscribe to that topic
|
|
85
|
+
skipped SUBSCRIBE and got no messages if the broker had dropped it.
|
|
86
|
+
SUBSCRIBE and UNSUBSCRIBE for a topic now run one at a time and the
|
|
87
|
+
topic is unmarked before UNSUBSCRIBE is sent, so the next subscribe
|
|
88
|
+
sends SUBSCRIBE, a subscribe made meanwhile keeps its callback, and a
|
|
89
|
+
late SUBACK cannot mark a topic that was unsubscribed.
|
|
90
|
+
- **Unsubscribing while disconnected removes the callback.** The
|
|
91
|
+
``unsubscribe_*`` methods returned without doing anything while the
|
|
92
|
+
connection was down, so the callback came back with the next
|
|
93
|
+
resubscribe. The callback is now removed locally and nothing is sent
|
|
94
|
+
to the broker.
|
|
95
|
+
- **A failed or cancelled resubscribe no longer loses subscriptions.**
|
|
96
|
+
After a reconnect without a session, a ``TimeoutError`` or a rejected
|
|
97
|
+
SUBACK stopped the resubscribe, and every topic not yet restored was
|
|
98
|
+
dropped for good. Topics that fail are tried twice more before the
|
|
99
|
+
queued commands are sent, and any still not restored stay registered
|
|
100
|
+
for the next reconnect. An unsubscribe made while they wait, or while
|
|
101
|
+
a SUBACK is pending, is kept. A rejected SUBACK in ``subscribe()``
|
|
102
|
+
removes the handler it added, but not a callback that was already
|
|
103
|
+
registered. ``resubscribe_all()`` returns whether every subscription
|
|
104
|
+
was restored.
|
|
105
|
+
- **Reconnects sign with the current AWS credentials.** The connection
|
|
106
|
+
read the credentials once, so the SDK's own reconnect kept signing
|
|
107
|
+
with expired ones after a token refresh. It now reads the current
|
|
108
|
+
ones on each attempt.
|
|
109
|
+
- **A late interruption or resumption from a replaced or closed
|
|
110
|
+
connection is ignored**, instead of changing the connected state.
|
|
111
|
+
- **Topic logs in the MQTT connection redact the MAC address**, and a
|
|
112
|
+
failed subscribe logs the error type rather than its text, which
|
|
113
|
+
carries the topic.
|
|
114
|
+
- **A periodic request task that stops on an unexpected error logs
|
|
115
|
+
it**, instead of ending silently while still listed as running.
|
|
116
|
+
- **``time_to_ready(..., inlet_measured=True)`` no longer says
|
|
117
|
+
``inlet_temp_modelled`` when the scenario has draws.** The mode walks
|
|
118
|
+
now get the flag, so the heat pump's walk's reasons no longer bring
|
|
119
|
+
the reason back into the answer.
|
|
120
|
+
|
|
5
121
|
Version 9.13.0 (2026-11-06)
|
|
6
122
|
===========================
|
|
7
123
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nwp500-python
|
|
3
|
-
Version: 9.13.
|
|
3
|
+
Version: 9.13.1
|
|
4
4
|
Summary: A library for controlling Navien NWP500 Water Heaters via NaviLink
|
|
5
5
|
Author-email: Emmanuel Levijarvi <emansl@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -17,7 +17,7 @@ Requires-Python: >=3.14
|
|
|
17
17
|
Description-Content-Type: text/x-rst; charset=UTF-8
|
|
18
18
|
License-File: LICENSE.txt
|
|
19
19
|
Requires-Dist: aiohttp>=3.13.5
|
|
20
|
-
Requires-Dist:
|
|
20
|
+
Requires-Dist: awscrt<0.39,>=0.36.1
|
|
21
21
|
Requires-Dist: pydantic>=2.0.0
|
|
22
22
|
Provides-Extra: cli
|
|
23
23
|
Requires-Dist: click>=8.3.0; extra == "cli"
|
|
@@ -33,7 +33,7 @@ Quick Start
|
|
|
33
33
|
diagnostics = MqttDiagnosticsCollector(enable_verbose_logging=True)
|
|
34
34
|
|
|
35
35
|
# Connect with hardened config
|
|
36
|
-
config = MqttConnectionConfig(keep_alive_secs=60)
|
|
36
|
+
config = MqttConnectionConfig(keep_alive_secs=60)
|
|
37
37
|
mqtt_client = NavienMqttClient(auth_client, config=config)
|
|
38
38
|
|
|
39
39
|
# Hook events
|
|
@@ -164,8 +164,8 @@ Start with these hardened settings:
|
|
|
164
164
|
from nwp500.mqtt_utils import MqttConnectionConfig
|
|
165
165
|
|
|
166
166
|
config = MqttConnectionConfig(
|
|
167
|
-
# 1.
|
|
168
|
-
keep_alive_secs=60,
|
|
167
|
+
# 1. Keep-alive below NAT idle timeouts (the default)
|
|
168
|
+
keep_alive_secs=60,
|
|
169
169
|
|
|
170
170
|
# 2. Faster reconnection for transient failures
|
|
171
171
|
initial_reconnect_delay=0.5, # 500ms (was 1s)
|
|
@@ -187,10 +187,10 @@ Start with these hardened settings:
|
|
|
187
187
|
Why These Settings Matter
|
|
188
188
|
--------------------------
|
|
189
189
|
|
|
190
|
-
**Keep-Alive (
|
|
191
|
-
Most NAT devices
|
|
192
|
-
|
|
193
|
-
|
|
190
|
+
**Keep-Alive (60s, the default)**
|
|
191
|
+
Most NAT devices time out idle connections after 300-600 seconds. A 60s
|
|
192
|
+
keep-alive sends a ping well before that, and a dead connection is noticed
|
|
193
|
+
within about a minute and a half.
|
|
194
194
|
|
|
195
195
|
**Faster Reconnection (1s → 0.5s)**
|
|
196
196
|
Transient network glitches often recover in <1 second. Faster initial retry
|
|
@@ -489,8 +489,9 @@ and emits as the ``control_failed`` event for every device subscription
|
|
|
489
489
|
``failCode`` is 2, "DHW control interval exceeded": the previous DHW control (mode, temperature, power) was sent
|
|
490
490
|
too recently, so wait and resend. The message names the client whose
|
|
491
491
|
command failed, so ``event.own_command`` tells your rejections from
|
|
492
|
-
another client's.
|
|
493
|
-
|
|
492
|
+
another client's. The acknowledgement is not sent for every command:
|
|
493
|
+
some commands apply with no reply, so read the device status to see
|
|
494
|
+
whether a command took effect.
|
|
494
495
|
|
|
495
496
|
.. code-block:: python
|
|
496
497
|
|
|
@@ -502,6 +503,38 @@ most likely never delivered: check the connection drop log above.
|
|
|
502
503
|
|
|
503
504
|
mqtt.on(MqttClientEvents.CONTROL_FAILED, on_control_failed)
|
|
504
505
|
|
|
506
|
+
What did the device get?
|
|
507
|
+
------------------------
|
|
508
|
+
|
|
509
|
+
Set the ``nwp500.mqtt.payloads`` logger to DEBUG to log every payload
|
|
510
|
+
the client sends and receives, one line each:
|
|
511
|
+
|
|
512
|
+
.. code-block:: text
|
|
513
|
+
|
|
514
|
+
send #1910 cmd/52/navilink-REDACTED/ctrl {"clientID":"navien-client-1996271a","sessionID":"1763116258162",...}
|
|
515
|
+
recv cmd/52/navilink-REDACTED/navien-client-1996271a/res {"clientID":"navilink-REDACTED","sessionID":"1763116258162",...}
|
|
516
|
+
|
|
517
|
+
A sent line carries the MQTT packet id after ``#``. MAC addresses are
|
|
518
|
+
replaced with ``REDACTED``; ``clientID`` and ``sessionID`` are kept.
|
|
519
|
+
Every command carries its own ``sessionID``, and a reply carries the
|
|
520
|
+
``sessionID`` of the command it answers, so search the log for a
|
|
521
|
+
command's ``sessionID`` to find its reply, or see that none came.
|
|
522
|
+
``clientID`` names the client a message came from.
|
|
523
|
+
|
|
524
|
+
.. code-block:: python
|
|
525
|
+
|
|
526
|
+
import logging
|
|
527
|
+
|
|
528
|
+
logging.getLogger("nwp500.mqtt.payloads").setLevel(logging.DEBUG)
|
|
529
|
+
|
|
530
|
+
In Home Assistant:
|
|
531
|
+
|
|
532
|
+
.. code-block:: yaml
|
|
533
|
+
|
|
534
|
+
logger:
|
|
535
|
+
logs:
|
|
536
|
+
nwp500.mqtt.payloads: debug
|
|
537
|
+
|
|
505
538
|
Integration Examples
|
|
506
539
|
====================
|
|
507
540
|
|
|
@@ -55,7 +55,7 @@ Core Dependencies
|
|
|
55
55
|
The library requires:
|
|
56
56
|
|
|
57
57
|
* ``aiohttp>=3.13.5``: Async HTTP client for REST API
|
|
58
|
-
* ``
|
|
58
|
+
* ``awscrt>=0.36.1,<0.39``: AWS Common Runtime, for the MQTT connection
|
|
59
59
|
* ``pydantic>=2.0.0``: Data validation and models
|
|
60
60
|
|
|
61
61
|
Optional Dependencies
|
|
@@ -134,11 +134,11 @@ If you get SSL certificate errors:
|
|
|
134
134
|
AWS IoT Connection Issues
|
|
135
135
|
--------------------------
|
|
136
136
|
|
|
137
|
-
The MQTT client requires the AWS
|
|
137
|
+
The MQTT client requires the AWS Common Runtime:
|
|
138
138
|
|
|
139
139
|
.. code-block:: bash
|
|
140
140
|
|
|
141
|
-
pip install
|
|
141
|
+
pip install "awscrt>=0.36.1,<0.39"
|
|
142
142
|
|
|
143
143
|
Upgrading
|
|
144
144
|
=========
|
|
@@ -147,7 +147,7 @@ All MQTT messages are JSON with this structure:
|
|
|
147
147
|
|
|
148
148
|
{
|
|
149
149
|
"clientID": "navien-client-1996271a",
|
|
150
|
-
"sessionID": "
|
|
150
|
+
"sessionID": "1763116258162",
|
|
151
151
|
"requestTopic": "cmd/52/navilink-04786332fca0/ctrl",
|
|
152
152
|
"responseTopic": "cmd/52/navilink-04786332fca0/navien-client-1996271a/res",
|
|
153
153
|
"protocolVersion": 2,
|
|
@@ -165,7 +165,8 @@ All MQTT messages are JSON with this structure:
|
|
|
165
165
|
**Fields:**
|
|
166
166
|
|
|
167
167
|
* ``clientID``: MQTT client identifier
|
|
168
|
-
* ``sessionID``:
|
|
168
|
+
* ``sessionID``: The send time in milliseconds, a new value per command.
|
|
169
|
+
The acknowledgement repeats it.
|
|
169
170
|
* ``requestTopic``: Topic the request is published to
|
|
170
171
|
* ``responseTopic``: Topic the reply should be published to
|
|
171
172
|
* ``protocolVersion``: Protocol version (always 2)
|
|
@@ -931,7 +932,7 @@ Control commands and ``st`` are acknowledged on
|
|
|
931
932
|
|
|
932
933
|
{
|
|
933
934
|
"clientID": "navilink-04786332fca0",
|
|
934
|
-
"sessionID": "
|
|
935
|
+
"sessionID": "1763116258162",
|
|
935
936
|
"requestTopic": "...",
|
|
936
937
|
"responseTopic": "...",
|
|
937
938
|
"response": {
|
|
@@ -21,7 +21,7 @@ classifiers = [
|
|
|
21
21
|
dynamic = ["version"]
|
|
22
22
|
dependencies = [
|
|
23
23
|
"aiohttp>=3.13.5",
|
|
24
|
-
"
|
|
24
|
+
"awscrt>=0.36.1,<0.39",
|
|
25
25
|
"pydantic>=2.0.0",
|
|
26
26
|
]
|
|
27
27
|
|
|
@@ -231,10 +231,6 @@ cache_dir = ".mypy_cache"
|
|
|
231
231
|
module = "awscrt.*"
|
|
232
232
|
ignore_missing_imports = true
|
|
233
233
|
|
|
234
|
-
[[tool.mypy.overrides]]
|
|
235
|
-
module = "awsiot.*"
|
|
236
|
-
ignore_missing_imports = true
|
|
237
|
-
|
|
238
234
|
[[tool.mypy.overrides]]
|
|
239
235
|
module = "aiohttp.*"
|
|
240
236
|
ignore_missing_imports = true
|
|
@@ -3,9 +3,16 @@ addopts =
|
|
|
3
3
|
--cov nwp500 --cov-report term-missing
|
|
4
4
|
--verbose
|
|
5
5
|
norecursedirs =
|
|
6
|
-
|
|
6
|
+
.*
|
|
7
|
+
*.egg
|
|
8
|
+
_darcs
|
|
7
9
|
build
|
|
8
|
-
|
|
10
|
+
CVS
|
|
11
|
+
dist
|
|
12
|
+
node_modules
|
|
13
|
+
venv
|
|
14
|
+
{arch}
|
|
15
|
+
asyncio_default_fixture_loop_scope = function
|
|
9
16
|
testpaths = tests
|
|
10
17
|
|
|
11
18
|
[devpi:upload]
|
|
@@ -44,7 +51,6 @@ forbidden_modules =
|
|
|
44
51
|
nwp500.sim_home_assistant
|
|
45
52
|
aiohttp
|
|
46
53
|
awscrt
|
|
47
|
-
awsiot
|
|
48
54
|
logging
|
|
49
55
|
|
|
50
56
|
[importlinter:contract:calibrate-no-cli]
|
|
@@ -25,7 +25,7 @@ finally:
|
|
|
25
25
|
|
|
26
26
|
# The exports load on first use (PEP 562), so a part of the library
|
|
27
27
|
# imports without the others' dependencies: ``nwp500.sim`` needs neither
|
|
28
|
-
# aiohttp nor
|
|
28
|
+
# aiohttp nor awscrt, which the clients load.
|
|
29
29
|
if TYPE_CHECKING:
|
|
30
30
|
from nwp500.api_client import (
|
|
31
31
|
NavienAPIClient,
|
|
@@ -11,6 +11,7 @@ adapter maps.
|
|
|
11
11
|
|
|
12
12
|
from __future__ import annotations
|
|
13
13
|
|
|
14
|
+
import contextlib
|
|
14
15
|
from collections.abc import Callable
|
|
15
16
|
from dataclasses import dataclass
|
|
16
17
|
from typing import Any
|
|
@@ -155,6 +156,12 @@ class BleMqttAdapter:
|
|
|
155
156
|
self._feature_callbacks.append(callback)
|
|
156
157
|
return 0
|
|
157
158
|
|
|
159
|
+
async def unsubscribe_device_feature(
|
|
160
|
+
self, device: Any, callback: FeatureCallback
|
|
161
|
+
) -> None:
|
|
162
|
+
with contextlib.suppress(ValueError):
|
|
163
|
+
self._feature_callbacks.remove(callback)
|
|
164
|
+
|
|
158
165
|
async def request_device_info(self, device: Any) -> int:
|
|
159
166
|
"""Read the blocks and deliver a :class:`BleDeviceFeature`.
|
|
160
167
|
|
|
@@ -180,6 +187,12 @@ class BleMqttAdapter:
|
|
|
180
187
|
self._status_callbacks.append(callback)
|
|
181
188
|
return 0
|
|
182
189
|
|
|
190
|
+
async def unsubscribe_device_status(
|
|
191
|
+
self, device: Any, callback: StatusCallback
|
|
192
|
+
) -> None:
|
|
193
|
+
with contextlib.suppress(ValueError):
|
|
194
|
+
self._status_callbacks.remove(callback)
|
|
195
|
+
|
|
183
196
|
async def request_device_status(self, device: Any) -> int:
|
|
184
197
|
"""Deliver the next :class:`~nwp500.ble.RealTimeStatus` block.
|
|
185
198
|
|
|
@@ -59,9 +59,9 @@ async def _detect_unit_system(
|
|
|
59
59
|
if not future.done():
|
|
60
60
|
future.set_result(status)
|
|
61
61
|
|
|
62
|
-
await mqtt.subscribe_device_status(device, _on_status)
|
|
63
|
-
await mqtt.request_device_status(device)
|
|
64
62
|
try:
|
|
63
|
+
await mqtt.subscribe_device_status(device, _on_status)
|
|
64
|
+
await mqtt.request_device_status(device)
|
|
65
65
|
status = await asyncio.wait_for(future, timeout=5.0)
|
|
66
66
|
if status.temperature_type == TemperatureType.CELSIUS:
|
|
67
67
|
_logger.info("Auto-detected metric unit system from device")
|
|
@@ -73,6 +73,10 @@ async def _detect_unit_system(
|
|
|
73
73
|
"Timed out detecting unit system, defaulting to us_customary"
|
|
74
74
|
)
|
|
75
75
|
return "us_customary"
|
|
76
|
+
finally:
|
|
77
|
+
await handlers.unsubscribe_quietly(
|
|
78
|
+
mqtt.unsubscribe_device_status, device, _on_status
|
|
79
|
+
)
|
|
76
80
|
|
|
77
81
|
|
|
78
82
|
def _parse_filter_hours(
|
|
@@ -59,6 +59,9 @@ async def _wait_for_response(
|
|
|
59
59
|
subscribe_func: Callable[
|
|
60
60
|
[Device, Callable[[Any], None]], Coroutine[Any, Any, Any]
|
|
61
61
|
],
|
|
62
|
+
unsubscribe_func: Callable[
|
|
63
|
+
[Device, Callable[[Any], None]], Coroutine[Any, Any, Any]
|
|
64
|
+
],
|
|
62
65
|
device: Device,
|
|
63
66
|
action_func: Callable[[], Coroutine[Any, Any, Any]],
|
|
64
67
|
timeout: float = 10.0,
|
|
@@ -71,15 +74,40 @@ async def _wait_for_response(
|
|
|
71
74
|
if not future.done():
|
|
72
75
|
future.set_result(res)
|
|
73
76
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
+
try:
|
|
78
|
+
# Inside the try: a subscribe cancelled while its acknowledgement
|
|
79
|
+
# is pending has already registered the callback.
|
|
80
|
+
await subscribe_func(device, callback)
|
|
81
|
+
_logger.info(f"Requesting {action_name}...")
|
|
82
|
+
await action_func()
|
|
83
|
+
try:
|
|
84
|
+
return await asyncio.wait_for(future, timeout=timeout)
|
|
85
|
+
except TimeoutError:
|
|
86
|
+
_logger.error(f"Timed out waiting for {action_name} response.")
|
|
87
|
+
raise
|
|
88
|
+
finally:
|
|
89
|
+
await unsubscribe_quietly(unsubscribe_func, device, callback)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
async def unsubscribe_quietly(
|
|
93
|
+
unsubscribe_func: Callable[
|
|
94
|
+
[Device, Callable[[Any], None]], Coroutine[Any, Any, Any]
|
|
95
|
+
],
|
|
96
|
+
device: Device,
|
|
97
|
+
callback: Callable[[Any], None],
|
|
98
|
+
) -> None:
|
|
99
|
+
"""Remove a reply callback without replacing the caller's outcome.
|
|
77
100
|
|
|
101
|
+
The callback is removed locally before the broker is asked; a failed
|
|
102
|
+
or unacknowledged broker unsubscribe is logged, so it can neither
|
|
103
|
+
hide a reply already received nor replace the request's own error.
|
|
104
|
+
"""
|
|
78
105
|
try:
|
|
79
|
-
|
|
80
|
-
except
|
|
81
|
-
_logger.
|
|
82
|
-
|
|
106
|
+
await unsubscribe_func(device, callback)
|
|
107
|
+
except Exception as e:
|
|
108
|
+
_logger.warning(
|
|
109
|
+
"Failed to unsubscribe a reply callback: %s", type(e).__name__
|
|
110
|
+
)
|
|
83
111
|
|
|
84
112
|
|
|
85
113
|
async def _handle_command_with_status_feedback(
|
|
@@ -94,6 +122,7 @@ async def _handle_command_with_status_feedback(
|
|
|
94
122
|
try:
|
|
95
123
|
status: Any = await _wait_for_response(
|
|
96
124
|
mqtt.subscribe_device_status,
|
|
125
|
+
mqtt.unsubscribe_device_status,
|
|
97
126
|
device,
|
|
98
127
|
action_func,
|
|
99
128
|
action_name=action_name,
|
|
@@ -124,6 +153,7 @@ async def get_controller_serial_number(
|
|
|
124
153
|
try:
|
|
125
154
|
feature: Any = await _wait_for_response(
|
|
126
155
|
mqtt.subscribe_device_feature,
|
|
156
|
+
mqtt.unsubscribe_device_feature,
|
|
127
157
|
device,
|
|
128
158
|
lambda: mqtt.request_device_info(device),
|
|
129
159
|
timeout=timeout,
|
|
@@ -155,6 +185,9 @@ async def _handle_info_request(
|
|
|
155
185
|
subscribe_method: Callable[
|
|
156
186
|
[Device, Callable[[Any], None]], Coroutine[Any, Any, Any]
|
|
157
187
|
],
|
|
188
|
+
unsubscribe_method: Callable[
|
|
189
|
+
[Device, Callable[[Any], None]], Coroutine[Any, Any, Any]
|
|
190
|
+
],
|
|
158
191
|
request_method: Callable[[Device], Coroutine[Any, Any, Any]],
|
|
159
192
|
data_key: str,
|
|
160
193
|
action_name: str,
|
|
@@ -166,6 +199,7 @@ async def _handle_info_request(
|
|
|
166
199
|
if not raw:
|
|
167
200
|
res = await _wait_for_response(
|
|
168
201
|
subscribe_method,
|
|
202
|
+
unsubscribe_method,
|
|
169
203
|
device,
|
|
170
204
|
lambda: request_method(device),
|
|
171
205
|
action_name=action_name,
|
|
@@ -199,6 +233,7 @@ async def handle_status_request(
|
|
|
199
233
|
mqtt,
|
|
200
234
|
device,
|
|
201
235
|
mqtt.subscribe_device_status,
|
|
236
|
+
mqtt.unsubscribe_device_status,
|
|
202
237
|
mqtt.request_device_status,
|
|
203
238
|
"status",
|
|
204
239
|
"device status",
|
|
@@ -215,6 +250,7 @@ async def handle_device_info_request(
|
|
|
215
250
|
mqtt,
|
|
216
251
|
device,
|
|
217
252
|
mqtt.subscribe_device_feature,
|
|
253
|
+
mqtt.unsubscribe_device_feature,
|
|
218
254
|
mqtt.request_device_info,
|
|
219
255
|
"feature",
|
|
220
256
|
"device information",
|
|
@@ -888,6 +924,7 @@ async def handle_get_energy_request(
|
|
|
888
924
|
try:
|
|
889
925
|
res: Any = await _wait_for_response(
|
|
890
926
|
mqtt.subscribe_energy_usage,
|
|
927
|
+
mqtt.unsubscribe_energy_usage,
|
|
891
928
|
device,
|
|
892
929
|
lambda: mqtt.request_energy_usage(device, year, months),
|
|
893
930
|
action_name="energy usage",
|
|
@@ -913,6 +950,7 @@ async def handle_get_energy_monthly_request(
|
|
|
913
950
|
try:
|
|
914
951
|
res: Any = await _wait_for_response(
|
|
915
952
|
mqtt.subscribe_energy_usage_monthly,
|
|
953
|
+
mqtt.unsubscribe_energy_usage_monthly,
|
|
916
954
|
device,
|
|
917
955
|
lambda: mqtt.request_energy_usage_monthly(device, years),
|
|
918
956
|
action_name="monthly energy usage",
|
|
@@ -937,6 +975,7 @@ async def handle_diagnostics_request(
|
|
|
937
975
|
try:
|
|
938
976
|
diagnostics: Any = await _wait_for_response(
|
|
939
977
|
mqtt.subscribe_diagnostics,
|
|
978
|
+
mqtt.unsubscribe_diagnostics,
|
|
940
979
|
device,
|
|
941
980
|
lambda: mqtt.request_diagnostics(device),
|
|
942
981
|
action_name="diagnostics",
|
|
@@ -962,6 +1001,7 @@ async def handle_firmware_download_info_request(
|
|
|
962
1001
|
try:
|
|
963
1002
|
info: Any = await _wait_for_response(
|
|
964
1003
|
mqtt.subscribe_firmware_download_info,
|
|
1004
|
+
mqtt.unsubscribe_firmware_download_info,
|
|
965
1005
|
device,
|
|
966
1006
|
lambda: mqtt.request_firmware_download_info(device),
|
|
967
1007
|
action_name="firmware download info",
|
|
@@ -986,6 +1026,7 @@ async def handle_get_recirculation_schedule_request(
|
|
|
986
1026
|
try:
|
|
987
1027
|
schedule: Any = await _wait_for_response(
|
|
988
1028
|
mqtt.subscribe_recirculation_schedule_response,
|
|
1029
|
+
mqtt.unsubscribe_recirculation_schedule_response,
|
|
989
1030
|
device,
|
|
990
1031
|
lambda: mqtt.request_recirculation_schedule(device),
|
|
991
1032
|
action_name="recirculation schedule",
|