nwp500-python 8.1.3__tar.gz → 9.0.0__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-8.1.3 → nwp500_python-9.0.0}/.gitignore +3 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/CHANGELOG.rst +302 -0
- {nwp500_python-8.1.3/src/nwp500_python.egg-info → nwp500_python-9.0.0}/PKG-INFO +1 -1
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/docs/how-to/optimize-tou.rst +7 -6
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/docs/how-to/schedule-operation.rst +3 -3
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/docs/reference/python_api/auth_client.rst +0 -4
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/docs/reference/python_api/cli.rst +4 -9
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/docs/reference/python_api/exceptions.rst +0 -57
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/docs/reference/python_api/mqtt_client.rst +1 -1
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/examples/advanced/mqtt_diagnostics.py +4 -5
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/examples/advanced/tou_openei.py +2 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/examples/mask.py +0 -2
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/pyproject.toml +19 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/src/nwp500/__init__.py +1 -49
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/src/nwp500/_base.py +11 -2
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/src/nwp500/api_client.py +19 -7
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/src/nwp500/auth.py +124 -36
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/src/nwp500/cli/__init__.py +0 -2
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/src/nwp500/cli/__main__.py +7 -5
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/src/nwp500/cli/handlers.py +2 -4
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/src/nwp500/cli/monitoring.py +0 -2
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/src/nwp500/cli/output_formatters.py +4 -19
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/src/nwp500/cli/rich_output.py +2 -3
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/src/nwp500/cli/token_storage.py +7 -4
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/src/nwp500/command_decorators.py +1 -3
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/src/nwp500/config.py +0 -2
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/src/nwp500/converters.py +0 -37
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/src/nwp500/device_capabilities.py +2 -4
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/src/nwp500/device_info_cache.py +0 -2
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/src/nwp500/encoding.py +39 -13
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/src/nwp500/enums.py +0 -2
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/src/nwp500/events.py +22 -22
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/src/nwp500/exceptions.py +0 -59
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/src/nwp500/factory.py +0 -2
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/src/nwp500/field_factory.py +57 -67
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/src/nwp500/models/__init__.py +0 -2
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/src/nwp500/models/_converters.py +0 -2
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/src/nwp500/models/device.py +0 -2
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/src/nwp500/models/energy.py +0 -2
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/src/nwp500/models/feature.py +0 -2
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/src/nwp500/models/mqtt_models.py +0 -2
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/src/nwp500/models/schedule.py +0 -2
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/src/nwp500/models/status.py +6 -4
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/src/nwp500/models/tou.py +0 -2
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/src/nwp500/mqtt/__init__.py +0 -2
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/src/nwp500/mqtt/client.py +77 -46
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/src/nwp500/mqtt/command_queue.py +47 -58
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/src/nwp500/mqtt/connection.py +68 -73
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/src/nwp500/mqtt/control.py +16 -6
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/src/nwp500/mqtt/diagnostics.py +0 -2
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/src/nwp500/mqtt/periodic.py +20 -9
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/src/nwp500/mqtt/reconnection.py +72 -15
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/src/nwp500/mqtt/state_tracker.py +4 -4
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/src/nwp500/mqtt/subscriptions.py +63 -39
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/src/nwp500/mqtt/utils.py +35 -4
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/src/nwp500/mqtt_events.py +12 -14
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/src/nwp500/openei.py +15 -3
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/src/nwp500/reservations.py +0 -2
- nwp500_python-9.0.0/src/nwp500/temperature.py +254 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/src/nwp500/topic_builder.py +0 -2
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/src/nwp500/unit_system.py +24 -23
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/src/nwp500/utils.py +0 -2
- {nwp500_python-8.1.3 → nwp500_python-9.0.0/src/nwp500_python.egg-info}/PKG-INFO +1 -1
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/src/nwp500_python.egg-info/SOURCES.txt +9 -1
- nwp500_python-9.0.0/src/nwp500_python.egg-info/scm_file_list.json +203 -0
- nwp500_python-9.0.0/src/nwp500_python.egg-info/scm_version.json +8 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/tests/test_auth.py +2 -11
- nwp500_python-9.0.0/tests/test_auth_session_lifecycle.py +275 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/tests/test_bug_fixes.py +2 -3
- nwp500_python-9.0.0/tests/test_cli_basic.py +111 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/tests/test_cli_commands.py +15 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/tests/test_events.py +2 -1
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/tests/test_exceptions.py +0 -39
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/tests/test_mqtt_clean_session_resume.py +0 -2
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/tests/test_mqtt_client_init.py +49 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/tests/test_mqtt_reconnection.py +0 -2
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/tests/test_mqtt_reconnection_storm.py +1 -3
- nwp500_python-9.0.0/tests/test_mqtt_reliability.py +630 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/tests/test_multi_device.py +2 -2
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/tests/test_openei.py +23 -0
- nwp500_python-9.0.0/tests/test_protocol_correctness.py +455 -0
- nwp500_python-9.0.0/tests/test_public_api.py +61 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/tests/test_reservations.py +0 -2
- nwp500_python-9.0.0/tests/test_threading_model.py +305 -0
- nwp500_python-9.0.0/tests/test_token_storage.py +61 -0
- nwp500_python-9.0.0/tests/test_utility_modules.py +149 -0
- nwp500_python-8.1.3/src/nwp500/cli/commands.py +0 -91
- nwp500_python-8.1.3/src/nwp500/temperature.py +0 -447
- nwp500_python-8.1.3/tests/test_cli_basic.py +0 -27
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/.coveragerc +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/.github/RESOLVING_PR_COMMENTS.md +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/.github/copilot-instructions.md +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/.github/workflows/ci.yml +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/.github/workflows/release.yml +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/.pre-commit-config.yaml +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/.readthedocs.yml +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/AUTHORS.rst +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/CONTRIBUTING.rst +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/LICENSE.txt +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/Makefile +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/README.rst +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/RELEASE.md +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/docs/Makefile +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/docs/_static/.gitignore +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/docs/conf.py +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/docs/explanation/advanced-features.rst +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/docs/explanation/architecture.rst +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/docs/explanation/index.rst +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/docs/how-to/authenticate.rst +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/docs/how-to/auto-recovery.rst +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/docs/how-to/diagnose-mqtt.rst +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/docs/how-to/home-assistant.rst +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/docs/how-to/index.rst +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/docs/how-to/maintenance.rst +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/docs/how-to/manage-units.rst +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/docs/how-to/monitor-status.rst +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/docs/how-to/queue-commands.rst +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/docs/how-to/track-energy.rst +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/docs/index.rst +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/docs/openapi.yaml +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/docs/project/authors.rst +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/docs/project/changelog.rst +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/docs/project/contributing.rst +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/docs/project/history.rst +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/docs/project/license.rst +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/docs/reference/configuration.rst +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/docs/reference/enumerations.rst +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/docs/reference/index.rst +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/docs/reference/installation.rst +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/docs/reference/protocol/data_conversions.rst +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/docs/reference/protocol/device_features.rst +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/docs/reference/protocol/device_status.rst +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/docs/reference/protocol/error_codes.rst +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/docs/reference/protocol/mqtt_protocol.rst +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/docs/reference/protocol/quick_reference.rst +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/docs/reference/protocol/rest_api.rst +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/docs/reference/python_api/api_client.rst +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/docs/reference/python_api/events.rst +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/docs/reference/python_api/models.rst +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/docs/requirements.txt +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/docs/tutorials/getting-started.rst +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/examples/.ruff.toml +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/examples/README.md +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/examples/advanced/air_filter_reset.py +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/examples/advanced/anti_legionella.py +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/examples/advanced/auto_recovery.py +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/examples/advanced/combined_callbacks.py +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/examples/advanced/demand_response.py +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/examples/advanced/device_capabilities.py +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/examples/advanced/device_status_debug.py +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/examples/advanced/energy_analytics.py +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/examples/advanced/error_code_demo.py +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/examples/advanced/firmware_payload_capture.py +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/examples/advanced/power_control.py +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/examples/advanced/recirculation_control.py +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/examples/advanced/reconnection_demo.py +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/examples/advanced/reservation_schedule.py +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/examples/advanced/simple_auto_recovery.py +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/examples/advanced/token_restoration.py +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/examples/advanced/tou_schedule.py +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/examples/advanced/water_reservation.py +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/examples/beginner/01_authentication.py +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/examples/beginner/02_list_devices.py +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/examples/beginner/03_get_status.py +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/examples/beginner/04_set_temperature.py +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/examples/intermediate/advanced_auth_patterns.py +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/examples/intermediate/command_queue.py +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/examples/intermediate/device_status_callback.py +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/examples/intermediate/error_handling.py +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/examples/intermediate/event_driven_control.py +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/examples/intermediate/improved_auth.py +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/examples/intermediate/legacy_auth_constructor.py +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/examples/intermediate/mqtt_realtime_monitoring.py +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/examples/intermediate/periodic_requests.py +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/examples/intermediate/set_mode.py +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/examples/intermediate/vacation_mode.py +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/examples/testing/periodic_device_info.py +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/examples/testing/simple_periodic_info.py +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/examples/testing/test_api_client.py +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/examples/testing/test_mqtt_connection.py +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/examples/testing/test_mqtt_messaging.py +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/examples/testing/test_periodic_minimal.py +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/scripts/README.md +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/scripts/bump_version.py +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/scripts/diagnose_mqtt_connection.py +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/scripts/extract_changelog.py +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/scripts/format.py +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/scripts/lint.py +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/scripts/setup-dev.py +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/scripts/validate_version.py +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/setup.cfg +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/setup.py +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/src/nwp500/py.typed +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/src/nwp500_python.egg-info/dependency_links.txt +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/src/nwp500_python.egg-info/entry_points.txt +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/src/nwp500_python.egg-info/not-zip-safe +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/src/nwp500_python.egg-info/requires.txt +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/src/nwp500_python.egg-info/top_level.txt +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/tests/conftest.py +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/tests/test_api_helpers.py +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/tests/test_command_decorators.py +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/tests/test_command_queue.py +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/tests/test_device_capabilities.py +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/tests/test_device_info_cache.py +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/tests/test_model_converters.py +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/tests/test_models.py +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/tests/test_mqtt_hypothesis.py +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/tests/test_temperature_converters.py +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/tests/test_tou_api.py +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/tests/test_unit_switching.py +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/tests/test_utils.py +0 -0
- {nwp500_python-8.1.3 → nwp500_python-9.0.0}/tox.ini +0 -0
|
@@ -5,6 +5,308 @@ Changelog
|
|
|
5
5
|
Unreleased
|
|
6
6
|
==========
|
|
7
7
|
|
|
8
|
+
Version 9.0.0 (2026-07-05)
|
|
9
|
+
==========================
|
|
10
|
+
|
|
11
|
+
**BREAKING CHANGES**: Public API surface trimmed and dead code removed.
|
|
12
|
+
These changes require a major version bump.
|
|
13
|
+
|
|
14
|
+
Modernization (Python 3.14)
|
|
15
|
+
---------------------------
|
|
16
|
+
- **Removed** ``from __future__ import annotations`` **project-wide**
|
|
17
|
+
(57 files): redundant on a Python >=3.14-only package where lazy
|
|
18
|
+
annotation evaluation (PEP 649) is the default.
|
|
19
|
+
- **Adopted additional ruff rule sets**: ``RUF`` (Ruff-specific),
|
|
20
|
+
``DTZ`` (naive datetime), ``PTH`` (pathlib), ``ASYNC``, and ``PERF``,
|
|
21
|
+
with documented ignores for intentional patterns (grouped ``__all__``
|
|
22
|
+
lists, unicode degree signs, public ``timeout`` parameters). Fixes
|
|
23
|
+
applied for all resulting findings, including a timezone-naive
|
|
24
|
+
timestamp in CSV exports (now timezone-aware local time),
|
|
25
|
+
``Path.open()`` usage, ``itertools.pairwise()`` in the CLI range
|
|
26
|
+
collapser, a ``ClassVar`` annotation on the capability map, and
|
|
27
|
+
dangling ``asyncio.create_task`` references in tests.
|
|
28
|
+
- **PeriodicRequestType is now a StrEnum** (was a plain ``Enum`` with
|
|
29
|
+
string values), matching the enum style used elsewhere.
|
|
30
|
+
- **Event payload dataclasses use** ``slots=True``: the frozen event
|
|
31
|
+
dataclasses in ``mqtt_events.py`` and ``events.EventListener`` are
|
|
32
|
+
created per state change; slots reduce their memory footprint.
|
|
33
|
+
- **match statement** for the periodic request-type dispatch.
|
|
34
|
+
|
|
35
|
+
Testing
|
|
36
|
+
-------
|
|
37
|
+
- New unit tests for previously untested modules:
|
|
38
|
+
``topic_builder.py`` (full topic schema), ``field_factory.py``
|
|
39
|
+
(metadata defaults, overrides, merge semantics), and
|
|
40
|
+
``models/_converters.py`` (unit-preference conversions and
|
|
41
|
+
round-trips).
|
|
42
|
+
|
|
43
|
+
Removed
|
|
44
|
+
-------
|
|
45
|
+
- **Deprecated ``.control`` property**: removed
|
|
46
|
+
``NavienMqttClient.control`` (deprecated shim slated for v9.0.0; the
|
|
47
|
+
project policy is to remove rather than deprecate). Use the delegated
|
|
48
|
+
methods on the client directly:
|
|
49
|
+
|
|
50
|
+
.. code-block:: python
|
|
51
|
+
|
|
52
|
+
# OLD (removed)
|
|
53
|
+
await client.control.set_power(device, True)
|
|
54
|
+
|
|
55
|
+
# NEW
|
|
56
|
+
await client.set_power(device, True)
|
|
57
|
+
|
|
58
|
+
- **Unused exception classes**: removed ``TokenExpiredError``,
|
|
59
|
+
``MqttSubscriptionError``, ``DeviceNotFoundError``,
|
|
60
|
+
``DeviceOfflineError``, and ``DeviceOperationError``. None of them was
|
|
61
|
+
ever raised by the library, so no working error handling can break;
|
|
62
|
+
catch the parent classes (``AuthenticationError``, ``MqttError``,
|
|
63
|
+
``DeviceError``) instead.
|
|
64
|
+
- **Internal plumbing removed from the top-level namespace**: the
|
|
65
|
+
package no longer re-exports ``requires_capability``,
|
|
66
|
+
``MqttDeviceInfoCache``, ``MqttDeviceCapabilityChecker``,
|
|
67
|
+
``log_performance``, or the bit-encoding helpers
|
|
68
|
+
(``encode_week_bitfield``, ``decode_week_bitfield``,
|
|
69
|
+
``encode_season_bitfield``, ``decode_season_bitfield``,
|
|
70
|
+
``encode_price``, ``decode_price``, ``build_reservation_entry``,
|
|
71
|
+
``build_tou_period``). Import them from their owning modules:
|
|
72
|
+
|
|
73
|
+
.. code-block:: python
|
|
74
|
+
|
|
75
|
+
# OLD (removed)
|
|
76
|
+
from nwp500 import build_reservation_entry, encode_price
|
|
77
|
+
|
|
78
|
+
# NEW
|
|
79
|
+
from nwp500.encoding import build_reservation_entry, encode_price
|
|
80
|
+
|
|
81
|
+
- **Dead code**: removed the never-imported ``nwp500.cli.commands``
|
|
82
|
+
module (its metadata had drifted from the real click commands), the
|
|
83
|
+
unused ``converters.str_enum_validator``, the unused module-level
|
|
84
|
+
``temperature.half_celsius_to_fahrenheit`` /
|
|
85
|
+
``deci_celsius_to_fahrenheit`` wrappers, the no-op
|
|
86
|
+
``NavienMqttClient._on_message_received`` placeholder, and unused
|
|
87
|
+
width calculations in the CLI formatters.
|
|
88
|
+
|
|
89
|
+
Changed
|
|
90
|
+
-------
|
|
91
|
+
- **Temperature classes deduplicated**: ``HalfCelsius``, ``DeciCelsius``,
|
|
92
|
+
``RawCelsius``, and ``DeciCelsiusDelta`` were four near-identical
|
|
93
|
+
copies differing only in a scale constant. All conversions are now
|
|
94
|
+
implemented once on the ``Temperature`` base class with a per-class
|
|
95
|
+
``_scale``; only special rounding (``RawCelsius``) and delta semantics
|
|
96
|
+
(``DeciCelsiusDelta``) are overridden. Behavior is unchanged
|
|
97
|
+
(~200 lines removed).
|
|
98
|
+
- **field_factory deduplicated**: the four field factories shared a
|
|
99
|
+
copy-pasted metadata-merge block, now extracted into a single private
|
|
100
|
+
helper. Behavior is unchanged.
|
|
101
|
+
- **Device boolean encoding centralized**: ``mqtt/control.py`` now uses
|
|
102
|
+
``converters.device_bool_from_python()`` instead of inline
|
|
103
|
+
``2 if enabled else 1`` literals.
|
|
104
|
+
- **Version resolution decoupled**: ``auth.py`` resolves the package
|
|
105
|
+
version from distribution metadata instead of ``from . import
|
|
106
|
+
__version__``, removing an order-dependent near-circular import.
|
|
107
|
+
|
|
108
|
+
Bug Fixes
|
|
109
|
+
---------
|
|
110
|
+
- **Fix malformed weekly reservation and recirculation schedule
|
|
111
|
+
payloads**: ``update_weekly_reservation()`` and
|
|
112
|
+
``configure_recirculation_schedule()`` dumped the schedule models
|
|
113
|
+
as-is, double-nesting the request (``request.reservation.reservation``
|
|
114
|
+
/ ``request.schedule.schedule``) and leaking pydantic computed display
|
|
115
|
+
fields — including a unit-converted ``temperature`` alongside the raw
|
|
116
|
+
half-Celsius ``param`` — into device commands. Both now send the flat,
|
|
117
|
+
raw protocol shape used by ``update_reservations()``. A new
|
|
118
|
+
``NavienBaseModel.to_protocol_dict()`` dumps only declared protocol
|
|
119
|
+
fields.
|
|
120
|
+
- **Preserve command order when a queued flush fails**: a command that
|
|
121
|
+
failed mid-flush was re-queued at the tail, behind commands queued
|
|
122
|
+
after it, inverting order-sensitive sequences (e.g. ``set_temp``
|
|
123
|
+
replayed before ``power_on``). The queue is now a deque and failed
|
|
124
|
+
commands are re-inserted at the front.
|
|
125
|
+
- **Expire stale queued commands**: queued commands stored a timestamp
|
|
126
|
+
that was never checked, so a multi-hour outage replayed hours-old
|
|
127
|
+
control commands (e.g. ``set_power``) to the appliance on reconnect.
|
|
128
|
+
Commands older than ``MqttConnectionConfig.max_queued_command_age``
|
|
129
|
+
(default 300 s, ``None`` to disable) are now discarded at send time.
|
|
130
|
+
- **Fix Fahrenheit conversion for sub-zero temperatures** (ASYMMETRIC
|
|
131
|
+
formula): the rounding used Python's floored ``%``, which is always
|
|
132
|
+
non-negative, while the firmware/app uses a truncated remainder. Raw
|
|
133
|
+
``-11`` (-5.5 °C) decoded to 22 °F instead of the app's 23 °F. Now
|
|
134
|
+
uses ``math.fmod`` semantics.
|
|
135
|
+
- **Fix freeze protection default limits**: the defaults (43/65) were
|
|
136
|
+
Fahrenheit display values stored in raw half-Celsius fields, decoding
|
|
137
|
+
to 70.7 °F / 90.5 °F when the device omitted them. Corrected to raw
|
|
138
|
+
12/20 (43 °F / 50 °F), matching the documented fixed limits.
|
|
139
|
+
- **Emit error_detected when the error code changes**: a transition
|
|
140
|
+
between two non-zero error codes (e.g. E799 → E407) emitted no event,
|
|
141
|
+
so consumers kept displaying the stale error.
|
|
142
|
+
- **Fix TOU price encoding**: ``encode_price()`` used banker's rounding,
|
|
143
|
+
under-encoding exact half values at even boundaries (0.125 at
|
|
144
|
+
``decimal_point=2`` encoded to 12 instead of 13) — now uses
|
|
145
|
+
``Decimal`` half-up rounding. ``build_tou_period()`` also treated
|
|
146
|
+
``bool`` prices as pre-encoded integers (``True`` sent as price 1).
|
|
147
|
+
- **Fix protocol documentation errors**: ``decode_reservation_hex()``
|
|
148
|
+
documented the enable flag inverted (1=enabled instead of 2=enabled);
|
|
149
|
+
the ``build_reservation_entry()`` example showed ``week: 158`` for
|
|
150
|
+
Mon/Wed/Fri instead of the correct 84; temperature doctest examples
|
|
151
|
+
showed ``int`` raw values where ``float`` is returned.
|
|
152
|
+
- **Run MQTT message dispatch on the event loop**: JSON parsing, pydantic
|
|
153
|
+
model validation, and user callbacks all executed directly on the AWS
|
|
154
|
+
CRT network thread. A slow or blocking callback (e.g. the CLI monitor's
|
|
155
|
+
CSV writes) stalled all MQTT message processing, user callbacks ran on
|
|
156
|
+
an undocumented SDK thread where asyncio operations are unsafe, and the
|
|
157
|
+
handler registry could be mutated on the event loop while the CRT
|
|
158
|
+
thread iterated it (``RuntimeError: dictionary changed size during
|
|
159
|
+
iteration``, dropping the message — most likely during the
|
|
160
|
+
reconnect/resubscribe window). The awscrt callback now only marshals
|
|
161
|
+
the raw payload onto the event loop; parsing and dispatch run there,
|
|
162
|
+
iterating snapshots of the handler registries.
|
|
163
|
+
- **Stop one raising handler from aborting message delivery**: handler
|
|
164
|
+
dispatch caught only ``(TypeError, AttributeError, KeyError)``; a user
|
|
165
|
+
callback raising anything else (e.g. ``ValueError``) escaped into the
|
|
166
|
+
awscrt callback machinery and skipped the remaining handlers for that
|
|
167
|
+
message. Individual handler failures are now logged and isolated.
|
|
168
|
+
- **Make the unit system preference process-wide**:
|
|
169
|
+
``set_unit_system()`` stored the preference in a ``ContextVar`` set in
|
|
170
|
+
the caller's task. Tasks scheduled from AWS CRT callback threads never
|
|
171
|
+
inherit that context, so the preference silently reverted to
|
|
172
|
+
auto-detect for all MQTT-delivered data — ``nwp-cli --unit-system
|
|
173
|
+
metric monitor`` logged temperatures in the device's native unit while
|
|
174
|
+
labeling them °C. The preference is now a process-wide setting visible
|
|
175
|
+
from every task and thread.
|
|
176
|
+
- **Fix once-listeners firing more than once**: ``EventEmitter.emit()``
|
|
177
|
+
removed one-time listeners only after invoking them, so a callback
|
|
178
|
+
that raised stayed registered forever, and two overlapping emits could
|
|
179
|
+
both fire the same once-listener. Once-listeners are now removed
|
|
180
|
+
before invocation.
|
|
181
|
+
- **Fix wait_for() leaking its listener on cancellation**:
|
|
182
|
+
``EventEmitter.wait_for()`` removed its listener only on timeout; a
|
|
183
|
+
cancelled waiter left the listener registered until the event next
|
|
184
|
+
fired, setting a result on a dead future. Cleanup now happens in a
|
|
185
|
+
``finally`` block.
|
|
186
|
+
- **Fix wait_for() docstring examples**: examples showed
|
|
187
|
+
``args, _ = await emitter.wait_for(...)``, but ``wait_for`` returns
|
|
188
|
+
just the args tuple — following the documented pattern raised
|
|
189
|
+
``ValueError`` or silently mis-assigned.
|
|
190
|
+
- **Serialize concurrent token refresh**: ``ensure_valid_token()`` and
|
|
191
|
+
``refresh_token()`` had no lock, so concurrent callers (API 401 retry,
|
|
192
|
+
MQTT reconnect, periodic requests) at token expiry fired parallel
|
|
193
|
+
refresh requests; with token rotation the losers were left holding
|
|
194
|
+
invalidated tokens. Refreshes are now serialized behind an
|
|
195
|
+
``asyncio.Lock`` with a post-acquire re-check: callers that lose the
|
|
196
|
+
race receive the already-refreshed tokens, callers passing a stale
|
|
197
|
+
(pre-rotation) refresh token get the fresh tokens instead of a
|
|
198
|
+
guaranteed failure, and an explicit refresh with the current token
|
|
199
|
+
still forces a refresh (deep-reconnect behavior preserved).
|
|
200
|
+
- **Preserve refresh_token/id_token across refreshes**: the refresh
|
|
201
|
+
response merge preserved AWS credential fields but not
|
|
202
|
+
``refresh_token``/``id_token``. A refresh response omitting them wiped
|
|
203
|
+
the stored refresh token, so the next refresh posted an empty string
|
|
204
|
+
and failed unconditionally.
|
|
205
|
+
- **Fix aiohttp session leak when authentication fails in**
|
|
206
|
+
``__aenter__``: Python never calls ``__aexit__`` when ``__aenter__``
|
|
207
|
+
raises, so bad credentials or a network error leaked the owned
|
|
208
|
+
``ClientSession`` (one per retry attempt). The session is now closed
|
|
209
|
+
before the exception propagates.
|
|
210
|
+
- **Make** ``NavienAuthClient.__aenter__`` **idempotent**:
|
|
211
|
+
``create_navien_clients()`` pre-enters the context and its docstring
|
|
212
|
+
instructs users to enter again with ``async with auth:``; the second
|
|
213
|
+
entry created a fresh session and orphaned the first — which the API
|
|
214
|
+
client was still pinned to. Re-entering now reuses the existing
|
|
215
|
+
session.
|
|
216
|
+
- **Fall back to full sign-in when stored-token refresh fails**:
|
|
217
|
+
restoring expired stored tokens raised ``TokenRefreshError`` even
|
|
218
|
+
though credentials for a full ``sign_in()`` were available.
|
|
219
|
+
- **Stop pinning the auth session in the API client**:
|
|
220
|
+
``NavienAPIClient`` captured ``auth_client.session`` at construction
|
|
221
|
+
and kept using it after the auth client recreated its session
|
|
222
|
+
(``RuntimeError: Session is closed``). The session is now resolved per
|
|
223
|
+
request; an explicitly provided session still takes precedence.
|
|
224
|
+
- **Add HTTP timeouts to unguarded sessions**: the standalone
|
|
225
|
+
``refresh_access_token()`` helper and ``OpenEIClient`` created
|
|
226
|
+
``ClientSession``s without a ``ClientTimeout``; requests could hang
|
|
227
|
+
indefinitely. Both now use a 30-second total timeout.
|
|
228
|
+
- **Fix reconnection loop dying on authentication errors**: the backoff
|
|
229
|
+
loop caught only ``AwsCrtError`` and ``RuntimeError``, so
|
|
230
|
+
``TokenRefreshError``, ``AuthenticationError``, and
|
|
231
|
+
``MqttCredentialsError`` raised during quick/deep reconnection escaped
|
|
232
|
+
and silently killed the reconnect task. A routine outage coinciding
|
|
233
|
+
with token expiry left the client permanently offline despite unlimited
|
|
234
|
+
retries. All library errors (``Nwp500Error``) and operation timeouts
|
|
235
|
+
are now treated as failed attempts and retried; only
|
|
236
|
+
``InvalidCredentialsError`` is fatal and stops the loop with a
|
|
237
|
+
``reconnection_failed`` event.
|
|
238
|
+
- **Fix disconnect() being a no-op while the connection is interrupted**:
|
|
239
|
+
calling ``disconnect()`` during an interruption returned early without
|
|
240
|
+
disabling automatic reconnection or stopping periodic tasks, so the
|
|
241
|
+
backoff loop would resurrect the connection after the application shut
|
|
242
|
+
the client down. ``disconnect()`` now always disables reconnection and
|
|
243
|
+
stops periodic tasks, and tears down the SDK connection even when not
|
|
244
|
+
connected.
|
|
245
|
+
- **Fix queued commands being lost after active/deep reconnection**: the
|
|
246
|
+
command queue was only flushed from the SDK's ``on_connection_resumed``
|
|
247
|
+
callback, which never fires for the new connection built by
|
|
248
|
+
active/deep reconnection. Commands queued while offline were silently
|
|
249
|
+
dropped. Both reconnect paths now flush the queue after subscriptions
|
|
250
|
+
are restored.
|
|
251
|
+
- **Fix periodic request tasks dying on MQTT errors**: the periodic loop
|
|
252
|
+
caught only ``AwsCrtError`` and ``RuntimeError``;
|
|
253
|
+
``MqttNotConnectedError``/``MqttPublishError`` raised by a publish
|
|
254
|
+
racing a disconnection permanently killed the polling task while it
|
|
255
|
+
still appeared active. The loop now survives all library errors.
|
|
256
|
+
- **Fix silent failures in thread-scheduled coroutines**: futures
|
|
257
|
+
returned by ``run_coroutine_threadsafe`` were discarded, so exceptions
|
|
258
|
+
from scheduled work (e.g. a failed resubscribe after a clean-session
|
|
259
|
+
resume, leaving the client connected but deaf) vanished. A done
|
|
260
|
+
callback now logs them.
|
|
261
|
+
- **Fix CancelledError being swallowed in reconnection and periodic
|
|
262
|
+
loops**: both loops caught ``asyncio.CancelledError`` and ``break``-ed,
|
|
263
|
+
so cancelled tasks ended "successfully" (and the reconnection loop
|
|
264
|
+
could emit ``reconnection_failed`` during a manual disconnect).
|
|
265
|
+
Cancellation now propagates correctly.
|
|
266
|
+
- **Fix AttributeError in configure_reservation_water_program**: The
|
|
267
|
+
``NavienMqttClient`` proxy referenced ``self._control``, which is never
|
|
268
|
+
assigned (the attribute is ``_device_controller``), so every call raised
|
|
269
|
+
``AttributeError``. Now delegates correctly; a regression test guards all
|
|
270
|
+
proxies against references to the undefined attribute.
|
|
271
|
+
- **Fix broken CLI mode choices**: ``nwp-cli mode vacation`` always failed
|
|
272
|
+
because vacation mode (5) requires a day count that was never supplied, and
|
|
273
|
+
``mode standby`` sent the invalid writable mode value ``0``. Both choices
|
|
274
|
+
were removed from the ``mode`` command; use the dedicated ``vacation DAYS``
|
|
275
|
+
and ``power off`` commands instead.
|
|
276
|
+
- **Fix CLI exit codes**: click ignores command return values in standalone
|
|
277
|
+
mode, so the CLI always exited ``0`` even when a command failed. Failures
|
|
278
|
+
now propagate through ``ctx.exit()`` and produce a non-zero exit code for
|
|
279
|
+
scripts and automation.
|
|
280
|
+
- **Fix cached tokens being reused for a different account**: passing
|
|
281
|
+
``--email`` for account B while tokens for account A were cached silently
|
|
282
|
+
ran commands against account A's session. Cached tokens are now discarded
|
|
283
|
+
when the provided email does not match the cached one.
|
|
284
|
+
- **Surface OpenEI application errors**: OpenEI reports errors such as an
|
|
285
|
+
invalid API key in the body of an HTTP 200 response; these were masked as
|
|
286
|
+
"no rate plans found". ``fetch_rates()`` now raises ``APIError`` with the
|
|
287
|
+
API's error message.
|
|
288
|
+
- **Fix broken example import**: ``examples/advanced/mqtt_diagnostics.py``
|
|
289
|
+
imported ``MqttConnectionConfig`` from the nonexistent ``nwp500.mqtt_utils``
|
|
290
|
+
module and used the deprecated ``datetime.utcnow()``.
|
|
291
|
+
|
|
292
|
+
Improvements
|
|
293
|
+
------------
|
|
294
|
+
- **MQTT operation acknowledgement timeouts**: connect, publish,
|
|
295
|
+
subscribe, unsubscribe, and disconnect acknowledgements are now awaited
|
|
296
|
+
with a timeout (``MqttConnectionConfig.operation_timeout``, default 30
|
|
297
|
+
seconds). Previously a half-open TCP connection could hang callers
|
|
298
|
+
until the 20-minute keep-alive expired.
|
|
299
|
+
- **Reconnection backoff jitter**: reconnect delays are now randomized
|
|
300
|
+
(±50%, capped at ``max_reconnect_delay``) so fleets of clients
|
|
301
|
+
disconnected simultaneously (e.g. the AWS IoT 24-hour disconnect) no
|
|
302
|
+
longer reconnect in synchronized waves.
|
|
303
|
+
|
|
304
|
+
Security
|
|
305
|
+
--------
|
|
306
|
+
- **Restrict token cache file permissions**: ``~/.nwp500_tokens.json``
|
|
307
|
+
(refresh token and AWS credentials) was written world-readable. It is now
|
|
308
|
+
created with mode ``0600``, and existing files are tightened on save.
|
|
309
|
+
|
|
8
310
|
Version 8.1.3 (2026-06-15)
|
|
9
311
|
==========================
|
|
10
312
|
|
|
@@ -418,7 +418,7 @@ Encodes a floating-point price into an integer for transmission.
|
|
|
418
418
|
|
|
419
419
|
.. code-block:: python
|
|
420
420
|
|
|
421
|
-
from nwp500 import encode_price
|
|
421
|
+
from nwp500.encoding import encode_price
|
|
422
422
|
|
|
423
423
|
# Encode $0.45000 per kWh
|
|
424
424
|
encoded = encode_price(0.45, decimal_point=5)
|
|
@@ -437,7 +437,7 @@ Decodes an integer price back to floating-point.
|
|
|
437
437
|
|
|
438
438
|
.. code-block:: python
|
|
439
439
|
|
|
440
|
-
from nwp500 import decode_price
|
|
440
|
+
from nwp500.encoding import decode_price
|
|
441
441
|
|
|
442
442
|
# Decode price from device
|
|
443
443
|
price = decode_price(45000, decimal_point=5)
|
|
@@ -466,7 +466,7 @@ Encodes a list of day names into a bitfield.
|
|
|
466
466
|
|
|
467
467
|
.. code-block:: python
|
|
468
468
|
|
|
469
|
-
from nwp500 import encode_week_bitfield
|
|
469
|
+
from nwp500.encoding import encode_week_bitfield
|
|
470
470
|
|
|
471
471
|
# Weekdays only
|
|
472
472
|
bitfield = encode_week_bitfield([
|
|
@@ -487,7 +487,7 @@ Decodes a bitfield back into a list of day names.
|
|
|
487
487
|
|
|
488
488
|
.. code-block:: python
|
|
489
489
|
|
|
490
|
-
from nwp500 import decode_week_bitfield
|
|
490
|
+
from nwp500.encoding import decode_week_bitfield
|
|
491
491
|
|
|
492
492
|
# Decode weekday bitfield
|
|
493
493
|
days = decode_week_bitfield(62)
|
|
@@ -504,7 +504,8 @@ Configure two rate periods - off-peak and peak pricing:
|
|
|
504
504
|
.. code-block:: python
|
|
505
505
|
|
|
506
506
|
import asyncio
|
|
507
|
-
from nwp500 import NavienAPIClient, NavienAuthClient, NavienMqttClient
|
|
507
|
+
from nwp500 import NavienAPIClient, NavienAuthClient, NavienMqttClient
|
|
508
|
+
from nwp500.encoding import build_tou_period
|
|
508
509
|
|
|
509
510
|
async def configure_simple_tou():
|
|
510
511
|
async with NavienAuthClient("user@example.com", "password") as auth_client:
|
|
@@ -654,7 +655,7 @@ Query the device for its current TOU configuration:
|
|
|
654
655
|
|
|
655
656
|
.. code-block:: python
|
|
656
657
|
|
|
657
|
-
from nwp500 import decode_week_bitfield, decode_price
|
|
658
|
+
from nwp500.encoding import decode_week_bitfield, decode_price
|
|
658
659
|
|
|
659
660
|
async def check_tou_settings():
|
|
660
661
|
async with NavienAuthClient("user@example.com", "password") as auth_client:
|
|
@@ -64,8 +64,8 @@ Quick Example
|
|
|
64
64
|
NavienAuthClient,
|
|
65
65
|
NavienAPIClient,
|
|
66
66
|
NavienMqttClient,
|
|
67
|
-
build_reservation_entry,
|
|
68
67
|
)
|
|
68
|
+
from nwp500.encoding import build_reservation_entry
|
|
69
69
|
|
|
70
70
|
async def main():
|
|
71
71
|
async with NavienAuthClient(
|
|
@@ -279,7 +279,7 @@ Helper Functions
|
|
|
279
279
|
|
|
280
280
|
.. code-block:: python
|
|
281
281
|
|
|
282
|
-
from nwp500 import build_reservation_entry
|
|
282
|
+
from nwp500.encoding import build_reservation_entry
|
|
283
283
|
|
|
284
284
|
entry = build_reservation_entry(
|
|
285
285
|
enabled=True,
|
|
@@ -589,8 +589,8 @@ want to send the whole weekly program as one typed object.
|
|
|
589
589
|
from nwp500 import (
|
|
590
590
|
WeeklyReservationEntry,
|
|
591
591
|
WeeklyReservationSchedule,
|
|
592
|
-
build_reservation_entry,
|
|
593
592
|
)
|
|
593
|
+
from nwp500.encoding import build_reservation_entry
|
|
594
594
|
|
|
595
595
|
morning = WeeklyReservationEntry.model_validate(
|
|
596
596
|
build_reservation_entry(
|
|
@@ -529,7 +529,6 @@ Error Handling
|
|
|
529
529
|
|
|
530
530
|
from nwp500 import (
|
|
531
531
|
InvalidCredentialsError,
|
|
532
|
-
TokenExpiredError,
|
|
533
532
|
TokenRefreshError,
|
|
534
533
|
AuthenticationError
|
|
535
534
|
)
|
|
@@ -543,9 +542,6 @@ Error Handling
|
|
|
543
542
|
except InvalidCredentialsError:
|
|
544
543
|
print("Wrong email or password")
|
|
545
544
|
|
|
546
|
-
except TokenExpiredError:
|
|
547
|
-
print("Token expired and refresh failed")
|
|
548
|
-
|
|
549
545
|
except TokenRefreshError:
|
|
550
546
|
print("Could not refresh token - sign in again")
|
|
551
547
|
|
|
@@ -224,12 +224,6 @@ Set operation mode.
|
|
|
224
224
|
# High Demand (maximum capacity)
|
|
225
225
|
python3 -m nwp500.cli mode high-demand
|
|
226
226
|
|
|
227
|
-
# Vacation Mode
|
|
228
|
-
python3 -m nwp500.cli mode vacation
|
|
229
|
-
|
|
230
|
-
# Standby
|
|
231
|
-
python3 -m nwp500.cli mode standby
|
|
232
|
-
|
|
233
227
|
**Syntax:**
|
|
234
228
|
|
|
235
229
|
.. code-block:: bash
|
|
@@ -238,12 +232,13 @@ Set operation mode.
|
|
|
238
232
|
|
|
239
233
|
**Available Modes:**
|
|
240
234
|
|
|
241
|
-
* ``
|
|
242
|
-
* ``heat-pump`` - Heat pump only (0)
|
|
235
|
+
* ``heat-pump`` - Heat pump only (1)
|
|
243
236
|
* ``electric`` - Electric heating only (2)
|
|
244
237
|
* ``energy-saver`` - Hybrid/balanced mode (3) **recommended**
|
|
245
238
|
* ``high-demand`` - Maximum heating capacity (4)
|
|
246
|
-
|
|
239
|
+
|
|
240
|
+
For vacation mode use the ``vacation`` command (it requires a day count);
|
|
241
|
+
to power the unit off use the ``power`` command.
|
|
247
242
|
|
|
248
243
|
**Output:** Confirmation message and updated device status.
|
|
249
244
|
|
|
@@ -15,22 +15,17 @@ All library exceptions inherit from ``Nwp500Error``::
|
|
|
15
15
|
Nwp500Error (base)
|
|
16
16
|
├── AuthenticationError
|
|
17
17
|
│ ├── InvalidCredentialsError
|
|
18
|
-
│ ├── TokenExpiredError
|
|
19
18
|
│ └── TokenRefreshError
|
|
20
19
|
├── APIError
|
|
21
20
|
├── MqttError
|
|
22
21
|
│ ├── MqttConnectionError
|
|
23
22
|
│ ├── MqttNotConnectedError
|
|
24
23
|
│ ├── MqttPublishError
|
|
25
|
-
│ ├── MqttSubscriptionError
|
|
26
24
|
│ └── MqttCredentialsError
|
|
27
25
|
├── ValidationError
|
|
28
26
|
│ ├── ParameterValidationError
|
|
29
27
|
│ └── RangeValidationError
|
|
30
28
|
└── DeviceError
|
|
31
|
-
├── DeviceNotFoundError
|
|
32
|
-
├── DeviceOfflineError
|
|
33
|
-
├── DeviceOperationError
|
|
34
29
|
└── DeviceCapabilityError
|
|
35
30
|
|
|
36
31
|
Base Exception
|
|
@@ -134,16 +129,6 @@ InvalidCredentialsError
|
|
|
134
129
|
print("Please check your email and password")
|
|
135
130
|
# Prompt user to re-enter credentials
|
|
136
131
|
|
|
137
|
-
TokenExpiredError
|
|
138
|
-
-----------------
|
|
139
|
-
|
|
140
|
-
.. py:class:: TokenExpiredError
|
|
141
|
-
|
|
142
|
-
Raised when an authentication token has expired.
|
|
143
|
-
|
|
144
|
-
Subclass of :py:class:`AuthenticationError`. Tokens have a limited lifetime
|
|
145
|
-
and must be refreshed periodically.
|
|
146
|
-
|
|
147
132
|
TokenRefreshError
|
|
148
133
|
-----------------
|
|
149
134
|
|
|
@@ -305,16 +290,6 @@ MqttPublishError
|
|
|
305
290
|
else:
|
|
306
291
|
raise # Not retriable or max retries reached
|
|
307
292
|
|
|
308
|
-
MqttSubscriptionError
|
|
309
|
-
---------------------
|
|
310
|
-
|
|
311
|
-
.. py:class:: MqttSubscriptionError
|
|
312
|
-
|
|
313
|
-
Failed to subscribe to MQTT topic.
|
|
314
|
-
|
|
315
|
-
Raised when subscription to an MQTT topic fails. This may occur if the
|
|
316
|
-
connection is interrupted or if the client lacks permissions for the topic.
|
|
317
|
-
|
|
318
293
|
MqttCredentialsError
|
|
319
294
|
--------------------
|
|
320
295
|
|
|
@@ -409,38 +384,6 @@ DeviceError
|
|
|
409
384
|
|
|
410
385
|
All device-related errors inherit from this base class.
|
|
411
386
|
|
|
412
|
-
DeviceNotFoundError
|
|
413
|
-
-------------------
|
|
414
|
-
|
|
415
|
-
.. py:class:: DeviceNotFoundError
|
|
416
|
-
|
|
417
|
-
Requested device not found.
|
|
418
|
-
|
|
419
|
-
Raised when a device cannot be found in the user's device list or when
|
|
420
|
-
attempting to access a non-existent device.
|
|
421
|
-
|
|
422
|
-
DeviceOfflineError
|
|
423
|
-
------------------
|
|
424
|
-
|
|
425
|
-
.. py:class:: DeviceOfflineError
|
|
426
|
-
|
|
427
|
-
Device is offline or unreachable.
|
|
428
|
-
|
|
429
|
-
Raised when a device is offline and cannot respond to commands or status
|
|
430
|
-
requests. The device may be powered off, disconnected from the network,
|
|
431
|
-
or experiencing connectivity issues.
|
|
432
|
-
|
|
433
|
-
DeviceOperationError
|
|
434
|
-
--------------------
|
|
435
|
-
|
|
436
|
-
.. py:class:: DeviceOperationError
|
|
437
|
-
|
|
438
|
-
Device operation failed.
|
|
439
|
-
|
|
440
|
-
Raised when a device operation (mode change, temperature setting, etc.)
|
|
441
|
-
fails. This may occur due to invalid commands, device restrictions, or
|
|
442
|
-
device-side errors.
|
|
443
|
-
|
|
444
387
|
DeviceCapabilityError
|
|
445
388
|
---------------------
|
|
446
389
|
|
|
@@ -27,8 +27,7 @@ from datetime import UTC, datetime
|
|
|
27
27
|
from pathlib import Path
|
|
28
28
|
|
|
29
29
|
from nwp500 import NavienAuthClient, NavienMqttClient
|
|
30
|
-
from nwp500.mqtt import MqttDiagnosticsCollector
|
|
31
|
-
from nwp500.mqtt_utils import MqttConnectionConfig
|
|
30
|
+
from nwp500.mqtt import MqttConnectionConfig, MqttDiagnosticsCollector
|
|
32
31
|
|
|
33
32
|
# Configure logging to show detailed MQTT information
|
|
34
33
|
logging.basicConfig(
|
|
@@ -85,7 +84,7 @@ class MqttDiagnosticsExample:
|
|
|
85
84
|
output_file = self.output_dir / f"diagnostics_{timestamp}.json"
|
|
86
85
|
|
|
87
86
|
json_data = self.diagnostics.export_json()
|
|
88
|
-
with open(
|
|
87
|
+
with output_file.open("w") as f:
|
|
89
88
|
f.write(json_data)
|
|
90
89
|
|
|
91
90
|
_logger.info(f"Exported diagnostics to {output_file}")
|
|
@@ -275,9 +274,9 @@ class MqttDiagnosticsExample:
|
|
|
275
274
|
|
|
276
275
|
# Final export
|
|
277
276
|
_logger.info("Exporting final diagnostics...")
|
|
278
|
-
timestamp = datetime.
|
|
277
|
+
timestamp = datetime.now(UTC).strftime("%Y%m%d_%H%M%S")
|
|
279
278
|
final_file = self.output_dir / f"diagnostics_final_{timestamp}.json"
|
|
280
|
-
with open(
|
|
279
|
+
with final_file.open("w") as f:
|
|
281
280
|
f.write(self.diagnostics.export_json())
|
|
282
281
|
_logger.info(f"Final diagnostics saved to {final_file}")
|
|
283
282
|
|
|
@@ -57,11 +57,30 @@ select = [
|
|
|
57
57
|
"C4", # flake8-comprehensions
|
|
58
58
|
"SIM", # flake8-simplify
|
|
59
59
|
"S", # flake8-bandit (security)
|
|
60
|
+
"RUF", # Ruff-specific rules (incl. unused noqa)
|
|
61
|
+
"DTZ", # flake8-datetimez (naive datetime usage)
|
|
62
|
+
"PTH", # flake8-use-pathlib
|
|
63
|
+
"ASYNC", # flake8-async
|
|
64
|
+
"PERF", # perflint
|
|
60
65
|
]
|
|
61
66
|
|
|
62
67
|
ignore = [
|
|
63
68
|
"E203", # whitespace before ':' (conflicts with black/ruff format)
|
|
64
69
|
"B904", # raise from - will be addressed in a future update
|
|
70
|
+
# Async functions taking a `timeout:` parameter are a deliberate,
|
|
71
|
+
# documented public API of this library (e.g. wait_for, command
|
|
72
|
+
# helpers); switching callers to asyncio.timeout() would be a
|
|
73
|
+
# breaking change with no behavioral benefit.
|
|
74
|
+
"ASYNC109",
|
|
75
|
+
# Degree signs, en-dashes, and multiplication signs in docstrings
|
|
76
|
+
# and user-facing strings are intentional (temperature units,
|
|
77
|
+
# ranges), not typos of ASCII look-alikes.
|
|
78
|
+
"RUF001",
|
|
79
|
+
"RUF002",
|
|
80
|
+
"RUF003",
|
|
81
|
+
# __all__ lists are deliberately grouped by category with comments,
|
|
82
|
+
# not sorted alphabetically.
|
|
83
|
+
"RUF022",
|
|
65
84
|
]
|
|
66
85
|
|
|
67
86
|
# Allow autofix for all enabled rules (when `--fix` is provided)
|