nwp500-python 9.4.3__tar.gz → 9.5.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.4.3 → nwp500_python-9.5.1}/CHANGELOG.rst +811 -93
- {nwp500_python-9.4.3/src/nwp500_python.egg-info → nwp500_python-9.5.1}/PKG-INFO +48 -16
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/README.rst +35 -15
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/docs/explanation/architecture.rst +3 -3
- nwp500_python-9.5.1/docs/explanation/draws.rst +292 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/docs/explanation/heating-elements.rst +202 -96
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/docs/explanation/index.rst +4 -1
- nwp500_python-9.5.1/docs/explanation/recovery-speed.rst +339 -0
- nwp500_python-9.5.1/docs/explanation/simulator.rst +401 -0
- nwp500_python-9.5.1/docs/explanation/standby-loss.rst +237 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/docs/explanation/tank-energy.rst +58 -61
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/docs/explanation/tou-recovery-cap.rst +38 -29
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/docs/explanation/unknown-values.rst +37 -32
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/docs/explanation/what-starts-a-recovery.rst +98 -61
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/docs/how-to/authenticate.rst +35 -2
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/docs/how-to/auto-recovery.rst +2 -2
- nwp500_python-9.5.1/docs/how-to/bluetooth.rst +329 -0
- nwp500_python-9.5.1/docs/how-to/calibrate-simulator.rst +214 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/docs/how-to/diagnose-mqtt.rst +54 -29
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/docs/how-to/home-assistant.rst +7 -7
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/docs/how-to/index.rst +3 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/docs/how-to/maintenance.rst +63 -16
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/docs/how-to/manage-units.rst +19 -20
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/docs/how-to/monitor-status.rst +9 -3
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/docs/how-to/optimize-tou.rst +14 -14
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/docs/how-to/queue-commands.rst +20 -20
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/docs/how-to/schedule-operation.rst +122 -96
- nwp500_python-9.5.1/docs/how-to/simulate-heater.rst +1174 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/docs/how-to/track-energy.rst +36 -28
- nwp500_python-9.5.1/docs/openapi.yaml +1658 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/docs/project/history.rst +161 -108
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/docs/reference/configuration.rst +12 -12
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/docs/reference/enumerations.rst +41 -16
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/docs/reference/index.rst +2 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/docs/reference/installation.rst +18 -12
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/docs/reference/protocol/data_conversions.rst +60 -42
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/docs/reference/protocol/device_features.rst +44 -45
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/docs/reference/protocol/device_status.rst +57 -52
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/docs/reference/protocol/error_codes.rst +6 -6
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/docs/reference/protocol/mqtt_protocol.rst +167 -43
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/docs/reference/protocol/quick_reference.rst +4 -2
- nwp500_python-9.5.1/docs/reference/protocol/rest_api.rst +996 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/docs/reference/python_api/api_client.rst +311 -20
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/docs/reference/python_api/auth_client.rst +76 -10
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/docs/reference/python_api/cli.rst +20 -20
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/docs/reference/python_api/events.rst +87 -21
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/docs/reference/python_api/exceptions.rst +28 -28
- nwp500_python-9.5.1/docs/reference/python_api/models.rst +1053 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/docs/reference/python_api/mqtt_client.rst +140 -27
- nwp500_python-9.5.1/docs/reference/python_api/sim.rst +309 -0
- nwp500_python-9.5.1/docs/reference/simulator-inputs.rst +1010 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/docs/requirements.txt +3 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/docs/tutorials/getting-started.rst +6 -6
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/examples/README.md +17 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/examples/advanced/recirculation_control.py +8 -6
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/examples/intermediate/advanced_auth_patterns.py +2 -2
- nwp500_python-9.5.1/examples/simulator/lower-setpoint.json +8 -0
- nwp500_python-9.5.1/examples/simulator/plan.json +9 -0
- nwp500_python-9.5.1/examples/simulator/scenario.json +91 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/pyproject.toml +16 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/setup.cfg +68 -0
- nwp500_python-9.5.1/src/nwp500/__init__.py +464 -0
- nwp500_python-9.5.1/src/nwp500/api_client.py +1433 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/src/nwp500/auth.py +394 -40
- nwp500_python-9.5.1/src/nwp500/ble/__init__.py +110 -0
- nwp500_python-9.5.1/src/nwp500/ble/__main__.py +163 -0
- nwp500_python-9.5.1/src/nwp500/ble/client.py +546 -0
- nwp500_python-9.5.1/src/nwp500/ble/mqtt_adapter.py +211 -0
- nwp500_python-9.5.1/src/nwp500/ble/protocol.py +890 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/src/nwp500/cli/__main__.py +474 -100
- nwp500_python-9.5.1/src/nwp500/cli/ble_backend.py +196 -0
- nwp500_python-9.5.1/src/nwp500/cli/calibration.py +488 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/src/nwp500/cli/handlers.py +2 -2
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/src/nwp500/cli/output_formatters.py +1 -1
- nwp500_python-9.5.1/src/nwp500/cli/recording.py +126 -0
- nwp500_python-9.5.1/src/nwp500/cli/simulate.py +507 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/src/nwp500/config.py +2 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/src/nwp500/converters.py +2 -2
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/src/nwp500/encoding.py +6 -5
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/src/nwp500/enums.py +79 -19
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/src/nwp500/models/__init__.py +40 -1
- nwp500_python-9.5.1/src/nwp500/models/account.py +61 -0
- nwp500_python-9.5.1/src/nwp500/models/device.py +325 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/src/nwp500/models/diagnostics.py +5 -2
- nwp500_python-9.5.1/src/nwp500/models/maintenance.py +31 -0
- nwp500_python-9.5.1/src/nwp500/models/mqtt_models.py +232 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/src/nwp500/models/schedule.py +15 -8
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/src/nwp500/models/status.py +69 -21
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/src/nwp500/models/tou.py +3 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/src/nwp500/mqtt/_control_commands.py +9 -4
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/src/nwp500/mqtt/_device_subscriptions.py +76 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/src/nwp500/mqtt/client.py +55 -27
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/src/nwp500/mqtt/command_queue.py +1 -1
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/src/nwp500/mqtt/connection.py +30 -5
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/src/nwp500/mqtt/control.py +114 -32
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/src/nwp500/mqtt/periodic.py +1 -1
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/src/nwp500/mqtt/reconnection.py +1 -1
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/src/nwp500/mqtt/subscriptions.py +214 -4
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/src/nwp500/mqtt/types.py +34 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/src/nwp500/mqtt/utils.py +17 -1
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/src/nwp500/mqtt_events.py +82 -1
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/src/nwp500/reservations.py +32 -18
- nwp500_python-9.5.1/src/nwp500/sim/__init__.py +130 -0
- nwp500_python-9.5.1/src/nwp500/sim/api.py +1934 -0
- nwp500_python-9.5.1/src/nwp500/sim/attribution.py +195 -0
- nwp500_python-9.5.1/src/nwp500/sim/book.py +1361 -0
- nwp500_python-9.5.1/src/nwp500/sim/calibration.py +3702 -0
- nwp500_python-9.5.1/src/nwp500/sim/circulation.py +191 -0
- nwp500_python-9.5.1/src/nwp500/sim/codec.py +92 -0
- nwp500_python-9.5.1/src/nwp500/sim/cold_supply.py +167 -0
- nwp500_python-9.5.1/src/nwp500/sim/controller.py +1584 -0
- nwp500_python-9.5.1/src/nwp500/sim/decay.py +1121 -0
- nwp500_python-9.5.1/src/nwp500/sim/defaults.py +197 -0
- nwp500_python-9.5.1/src/nwp500/sim/delivery.py +711 -0
- nwp500_python-9.5.1/src/nwp500/sim/draw_forecast.py +494 -0
- nwp500_python-9.5.1/src/nwp500/sim/engine.py +2308 -0
- nwp500_python-9.5.1/src/nwp500/sim/extensions.py +212 -0
- nwp500_python-9.5.1/src/nwp500/sim/heat_pump_recovery.py +268 -0
- nwp500_python-9.5.1/src/nwp500/sim/heating_trajectory.py +342 -0
- nwp500_python-9.5.1/src/nwp500/sim/inputs.py +352 -0
- nwp500_python-9.5.1/src/nwp500/sim/location_air.py +167 -0
- nwp500_python-9.5.1/src/nwp500/sim/meter.py +130 -0
- nwp500_python-9.5.1/src/nwp500/sim/observed.py +568 -0
- nwp500_python-9.5.1/src/nwp500/sim/params.py +1205 -0
- nwp500_python-9.5.1/src/nwp500/sim/physics/__init__.py +2 -0
- nwp500_python-9.5.1/src/nwp500/sim/physics/compressor.py +450 -0
- nwp500_python-9.5.1/src/nwp500/sim/physics/draw.py +474 -0
- nwp500_python-9.5.1/src/nwp500/sim/physics/element.py +623 -0
- nwp500_python-9.5.1/src/nwp500/sim/physics/refrigerant.py +168 -0
- nwp500_python-9.5.1/src/nwp500/sim/physics/tank.py +72 -0
- nwp500_python-9.5.1/src/nwp500/sim/profile.py +238 -0
- nwp500_python-9.5.1/src/nwp500/sim/ready.py +728 -0
- nwp500_python-9.5.1/src/nwp500/sim/record.py +333 -0
- nwp500_python-9.5.1/src/nwp500/sim/result.py +257 -0
- nwp500_python-9.5.1/src/nwp500/sim/schedules.py +243 -0
- nwp500_python-9.5.1/src/nwp500/sim/tank_physics.py +504 -0
- nwp500_python-9.5.1/src/nwp500/sim/tou_triggers.py +308 -0
- nwp500_python-9.5.1/src/nwp500/sim/units.py +20 -0
- nwp500_python-9.5.1/src/nwp500/sim_adapters.py +1323 -0
- nwp500_python-9.5.1/src/nwp500/sim_home_assistant.py +1157 -0
- nwp500_python-9.5.1/src/nwp500/sim_live.py +360 -0
- nwp500_python-9.5.1/src/nwp500/status_history.py +68 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/src/nwp500/topic_builder.py +36 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1/src/nwp500_python.egg-info}/PKG-INFO +48 -16
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/src/nwp500_python.egg-info/SOURCES.txt +110 -2
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/src/nwp500_python.egg-info/requires.txt +14 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/src/nwp500_python.egg-info/scm_file_list.json +109 -1
- nwp500_python-9.5.1/src/nwp500_python.egg-info/scm_version.json +8 -0
- nwp500_python-9.5.1/tests/_sim_site.py +168 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/tests/conftest.py +20 -0
- nwp500_python-9.5.1/tests/fixtures/sim/engine_golden.json +377 -0
- nwp500_python-9.5.1/tests/fixtures/sim/heater_params.json +65 -0
- nwp500_python-9.5.1/tests/fixtures/sim/heater_params_fitted.json +65 -0
- nwp500_python-9.5.1/tests/test_api_endpoints_201.py +821 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/tests/test_api_helpers.py +45 -13
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/tests/test_app_queries.py +51 -45
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/tests/test_auth.py +85 -56
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/tests/test_auth_session_lifecycle.py +24 -16
- nwp500_python-9.5.1/tests/test_auth_session_refresh.py +847 -0
- nwp500_python-9.5.1/tests/test_ble_mqtt_adapter.py +547 -0
- nwp500_python-9.5.1/tests/test_ble_protocol.py +803 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/tests/test_bug_fixes.py +26 -20
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/tests/test_canonical_schedule.py +10 -10
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/tests/test_cli_basic.py +8 -7
- nwp500_python-9.5.1/tests/test_cli_calibrate.py +646 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/tests/test_cli_commands.py +167 -97
- nwp500_python-9.5.1/tests/test_cli_simulate.py +527 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/tests/test_cli_tristate_presentation.py +28 -9
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/tests/test_command_decorators.py +1 -1
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/tests/test_command_queue.py +7 -7
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/tests/test_device_info_cache.py +23 -16
- nwp500_python-9.5.1/tests/test_device_rest_models.py +241 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/tests/test_events.py +37 -37
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/tests/test_exceptions.py +30 -30
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/tests/test_model_converters.py +69 -65
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/tests/test_models.py +78 -42
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/tests/test_mqtt_clean_session_resume.py +21 -18
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/tests/test_mqtt_client_init.py +73 -67
- nwp500_python-9.5.1/tests/test_mqtt_control_fail.py +423 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/tests/test_mqtt_events.py +17 -8
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/tests/test_mqtt_hypothesis.py +5 -2
- nwp500_python-9.5.1/tests/test_mqtt_last_will.py +524 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/tests/test_mqtt_reconnection.py +39 -17
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/tests/test_mqtt_reconnection_storm.py +55 -33
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/tests/test_mqtt_reliability.py +288 -64
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/tests/test_multi_device.py +26 -15
- nwp500_python-9.5.1/tests/test_navilink_201_enums.py +253 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/tests/test_openei.py +2 -1
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/tests/test_protocol_correctness.py +200 -93
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/tests/test_public_api.py +37 -7
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/tests/test_reservation_validation.py +39 -4
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/tests/test_reservations.py +87 -1
- nwp500_python-9.5.1/tests/test_sim_adapters.py +389 -0
- nwp500_python-9.5.1/tests/test_sim_api.py +800 -0
- nwp500_python-9.5.1/tests/test_sim_book.py +488 -0
- nwp500_python-9.5.1/tests/test_sim_book_history.py +250 -0
- nwp500_python-9.5.1/tests/test_sim_calibration.py +863 -0
- nwp500_python-9.5.1/tests/test_sim_circulation.py +395 -0
- nwp500_python-9.5.1/tests/test_sim_codec.py +165 -0
- nwp500_python-9.5.1/tests/test_sim_cold_supply.py +145 -0
- nwp500_python-9.5.1/tests/test_sim_controller.py +1227 -0
- nwp500_python-9.5.1/tests/test_sim_defaults.py +201 -0
- nwp500_python-9.5.1/tests/test_sim_draw_forecast.py +174 -0
- nwp500_python-9.5.1/tests/test_sim_draw_loss.py +225 -0
- nwp500_python-9.5.1/tests/test_sim_element.py +869 -0
- nwp500_python-9.5.1/tests/test_sim_element_fit.py +135 -0
- nwp500_python-9.5.1/tests/test_sim_forecast.py +288 -0
- nwp500_python-9.5.1/tests/test_sim_golden.py +359 -0
- nwp500_python-9.5.1/tests/test_sim_home_assistant.py +1223 -0
- nwp500_python-9.5.1/tests/test_sim_hot_water.py +361 -0
- nwp500_python-9.5.1/tests/test_sim_import_contract.py +413 -0
- nwp500_python-9.5.1/tests/test_sim_live.py +474 -0
- nwp500_python-9.5.1/tests/test_sim_location_air.py +226 -0
- nwp500_python-9.5.1/tests/test_sim_meter.py +163 -0
- nwp500_python-9.5.1/tests/test_sim_no_clock_no_state.py +363 -0
- nwp500_python-9.5.1/tests/test_sim_observed.py +348 -0
- nwp500_python-9.5.1/tests/test_sim_profile.py +86 -0
- nwp500_python-9.5.1/tests/test_sim_readiness.py +879 -0
- nwp500_python-9.5.1/tests/test_sim_readiness_fit.py +296 -0
- nwp500_python-9.5.1/tests/test_sim_ready.py +406 -0
- nwp500_python-9.5.1/tests/test_sim_ready_api.py +770 -0
- nwp500_python-9.5.1/tests/test_sim_replay.py +232 -0
- nwp500_python-9.5.1/tests/test_sim_schedules.py +336 -0
- nwp500_python-9.5.1/tests/test_sim_tank_physics.py +184 -0
- nwp500_python-9.5.1/tests/test_status_history.py +236 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/tests/test_temperature_converters.py +52 -40
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/tests/test_threading_model.py +32 -28
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/tests/test_token_storage.py +10 -5
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/tests/test_tou_api.py +1 -1
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/tests/test_unit_switching.py +86 -15
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/tests/test_unit_system_process_wide.py +4 -4
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/tests/test_utility_modules.py +38 -22
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/tests/test_utils.py +35 -22
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/tox.ini +6 -1
- nwp500_python-9.4.3/docs/explanation/advanced-features.rst +0 -497
- nwp500_python-9.4.3/docs/openapi.yaml +0 -805
- nwp500_python-9.4.3/docs/reference/protocol/rest_api.rst +0 -435
- nwp500_python-9.4.3/docs/reference/python_api/models.rst +0 -860
- nwp500_python-9.4.3/src/nwp500/__init__.py +0 -253
- nwp500_python-9.4.3/src/nwp500/api_client.py +0 -558
- nwp500_python-9.4.3/src/nwp500/models/device.py +0 -104
- nwp500_python-9.4.3/src/nwp500/models/mqtt_models.py +0 -31
- nwp500_python-9.4.3/src/nwp500_python.egg-info/scm_version.json +0 -8
- nwp500_python-9.4.3/tests/test_device_rest_models.py +0 -128
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/.coveragerc +0 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/.github/RESOLVING_PR_COMMENTS.md +0 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/.github/copilot-instructions.md +0 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/.github/workflows/ci.yml +0 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/.github/workflows/release.yml +0 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/.gitignore +0 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/.pre-commit-config.yaml +0 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/.readthedocs.yml +0 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/AUTHORS.rst +0 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/CONTRIBUTING.rst +0 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/LICENSE.txt +0 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/Makefile +0 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/RELEASE.md +0 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/docs/Makefile +0 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/docs/_static/.gitignore +0 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/docs/conf.py +0 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/docs/index.rst +0 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/docs/project/authors.rst +0 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/docs/project/changelog.rst +0 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/docs/project/contributing.rst +0 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/docs/project/license.rst +0 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/examples/.ruff.toml +0 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/examples/advanced/air_filter_reset.py +0 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/examples/advanced/anti_legionella.py +0 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/examples/advanced/auto_recovery.py +0 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/examples/advanced/combined_callbacks.py +0 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/examples/advanced/demand_response.py +0 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/examples/advanced/device_capabilities.py +0 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/examples/advanced/device_status_debug.py +0 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/examples/advanced/energy_analytics.py +0 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/examples/advanced/error_code_demo.py +0 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/examples/advanced/firmware_payload_capture.py +0 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/examples/advanced/mqtt_diagnostics.py +0 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/examples/advanced/power_control.py +0 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/examples/advanced/reconnection_demo.py +0 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/examples/advanced/reservation_schedule.py +0 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/examples/advanced/simple_auto_recovery.py +0 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/examples/advanced/token_restoration.py +0 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/examples/advanced/tou_openei.py +0 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/examples/advanced/tou_schedule.py +0 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/examples/advanced/water_reservation.py +0 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/examples/beginner/01_authentication.py +0 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/examples/beginner/02_list_devices.py +0 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/examples/beginner/03_get_status.py +0 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/examples/beginner/04_set_temperature.py +0 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/examples/intermediate/command_queue.py +0 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/examples/intermediate/device_status_callback.py +0 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/examples/intermediate/error_handling.py +0 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/examples/intermediate/event_driven_control.py +0 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/examples/intermediate/improved_auth.py +0 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/examples/intermediate/legacy_auth_constructor.py +0 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/examples/intermediate/mqtt_realtime_monitoring.py +0 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/examples/intermediate/periodic_requests.py +0 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/examples/intermediate/set_mode.py +0 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/examples/intermediate/vacation_mode.py +0 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/examples/mask.py +0 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/examples/testing/periodic_device_info.py +0 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/examples/testing/simple_periodic_info.py +0 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/examples/testing/test_api_client.py +0 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/examples/testing/test_mqtt_connection.py +0 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/examples/testing/test_mqtt_messaging.py +0 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/examples/testing/test_periodic_minimal.py +0 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/scripts/README.md +0 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/scripts/bump_version.py +0 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/scripts/diagnose_mqtt_connection.py +0 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/scripts/extract_changelog.py +0 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/scripts/format.py +0 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/scripts/lint.py +0 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/scripts/setup-dev.py +0 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/scripts/validate_version.py +0 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/setup.py +0 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/src/nwp500/_base.py +0 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/src/nwp500/cli/__init__.py +0 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/src/nwp500/cli/monitoring.py +0 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/src/nwp500/cli/presentation.py +0 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/src/nwp500/cli/rich_output.py +0 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/src/nwp500/cli/token_storage.py +0 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/src/nwp500/command_decorators.py +0 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/src/nwp500/device_capabilities.py +0 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/src/nwp500/device_info_cache.py +0 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/src/nwp500/events.py +0 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/src/nwp500/exceptions.py +0 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/src/nwp500/factory.py +0 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/src/nwp500/field_factory.py +0 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/src/nwp500/models/_converters.py +0 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/src/nwp500/models/energy.py +0 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/src/nwp500/models/feature.py +0 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/src/nwp500/models/firmware.py +0 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/src/nwp500/mqtt/__init__.py +0 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/src/nwp500/mqtt/diagnostics.py +0 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/src/nwp500/mqtt/state_tracker.py +0 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/src/nwp500/openei.py +0 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/src/nwp500/py.typed +0 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/src/nwp500/temperature.py +0 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/src/nwp500/tou_schedule.py +0 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/src/nwp500/unit_system.py +0 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/src/nwp500/utils.py +0 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/src/nwp500_python.egg-info/dependency_links.txt +0 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/src/nwp500_python.egg-info/entry_points.txt +0 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/src/nwp500_python.egg-info/not-zip-safe +0 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/src/nwp500_python.egg-info/top_level.txt +0 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/tests/test_device_capabilities.py +0 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/tests/test_tou_schedule.py +0 -0
- {nwp500_python-9.4.3 → nwp500_python-9.5.1}/uv.lock +0 -0
|
@@ -2,8 +2,726 @@
|
|
|
2
2
|
Changelog
|
|
3
3
|
=========
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
Version 9.5.1 (2026-10-21)
|
|
6
|
+
==========================
|
|
7
|
+
|
|
8
|
+
Fixed
|
|
9
|
+
-----
|
|
10
|
+
- **A steady Home Assistant sensor no longer goes stale in the
|
|
11
|
+
simulator** (#174). ``sim_home_assistant.parse_state`` dated a state by
|
|
12
|
+
``last_updated``, which moves only when the value or its attributes
|
|
13
|
+
change. An air sensor holding the same value for over an hour failed
|
|
14
|
+
``fresh()``, and the simulation fell back to the default air
|
|
15
|
+
temperature. A state is now dated by ``last_reported`` (Home Assistant
|
|
16
|
+
2024.3 and later), then ``last_updated``, then ``last_changed``. The
|
|
17
|
+
history parsers still date each change row by its change.
|
|
18
|
+
|
|
19
|
+
Version 9.5.0 (2026-10-20)
|
|
20
|
+
==========================
|
|
21
|
+
|
|
22
|
+
Added
|
|
23
|
+
-----
|
|
24
|
+
- **Bluetooth LE access to the controller** (``nwp500.ble``, optional
|
|
25
|
+
``ble`` extra). The NWP500 exposes a GATT service that carries the
|
|
26
|
+
controller's native class-code protocol once the heater is put in pairing
|
|
27
|
+
mode (with the heater off, hold Back and Power for five seconds; it
|
|
28
|
+
advertises as ``HPWH1…``). The scan also lists a heater in Wi-Fi setup
|
|
29
|
+
mode (``HPWH4610``), which answers nothing, and the CLIs pick an
|
|
30
|
+
``HPWH1`` heater over it.
|
|
31
|
+
The package frames and parses those packets (header, class code, address,
|
|
32
|
+
CRC-16/MODBUS), scans for the heater, and reads data classes with a
|
|
33
|
+
matched request/reply client; ``python -m nwp500.ble scan`` and
|
|
34
|
+
``python -m nwp500.ble read`` dump the blocks. Checked on a unit: it
|
|
35
|
+
answers a DIP-switch block, error history, four diagnostics counters, the stored
|
|
36
|
+
time-of-use plan, device info, the controller serial numbers and a
|
|
37
|
+
software-data block; the boiler parameter classes 51-54 do not answer, so
|
|
38
|
+
installer parameters are not reachable yet. ``write_block()`` exists for
|
|
39
|
+
the eventual parameter write and is marked experimental. ``read``
|
|
40
|
+
dumps the classes the heater answers by default and exits 1 when none
|
|
41
|
+
answered; the readers that decode a block raise ``CrcMismatchError``
|
|
42
|
+
on a reply that failed its checksum.
|
|
43
|
+
``read_tou_records()`` decodes the stored time-of-use plan into
|
|
44
|
+
``TOUPeriod``, the cloud's model, reading the controller's 40-slot table
|
|
45
|
+
whole (a ``TOU_PLAN`` read's payload byte is the last slot, not a
|
|
46
|
+
length; ``build_tou_read()``). ``write_tou_records()`` stores a plan of
|
|
47
|
+
up to twelve intervals (``build_tou_write()``: the NaviLink app's clock
|
|
48
|
+
header, then the records in the read layout) and reads it back. It
|
|
49
|
+
refuses to write over a longer stored plan, whose extra intervals no
|
|
50
|
+
Bluetooth write can clear, and raises ``TouWriteError`` when the
|
|
51
|
+
heater refuses or stores something else. ``TOU_PLAN`` is the only
|
|
52
|
+
class written so far (``NWP500_WRITABLE_CLASSES``). A read that gets no reply
|
|
53
|
+
is sent once more, since the first request on a new connection
|
|
54
|
+
sometimes goes unanswered. ``DeviceInfoBlock`` (``DID``), ``ControllerSerials``
|
|
55
|
+
(``MSCD``) and ``SoftwareInfoBlock`` (``MSWD``) use the cloud models'
|
|
56
|
+
field names for the fields matched against them.
|
|
57
|
+
``read_sub_status()`` decodes the ``SSD`` block into two DIP-switch
|
|
58
|
+
words (``SubStatus``), using the layout Navien's boiler service toolkit
|
|
59
|
+
reads; it is unconfirmed on the NWP500.
|
|
60
|
+
``read_status()`` returns live status: the class-50 session command 200
|
|
61
|
+
makes the heater push the ``RTMD`` block about every 300 ms until the
|
|
62
|
+
connection closes, and ``RealTimeStatus`` decodes the fields checked
|
|
63
|
+
against the MQTT status report, in standby and while heating (operation
|
|
64
|
+
mode, state number, DHW temperature and setpoint, the six thermistors,
|
|
65
|
+
superheat and EEV step), under the ``DeviceStatus`` names and
|
|
66
|
+
conversions. A read ends the stream, and the next ``read_status()``
|
|
67
|
+
starts it again. The
|
|
68
|
+
heater's display unit is in no Bluetooth block, so Bluetooth
|
|
69
|
+
temperatures follow the unit-system preference.
|
|
70
|
+
- ``nwp-cli --backend ble`` (with ``--ble-address`` or a scan) runs
|
|
71
|
+
``serial``, ``info`` and ``status`` over Bluetooth without signing in,
|
|
72
|
+
and refuses other commands. Without ``--backend``, commands use the cloud as before.
|
|
73
|
+
The handlers run unchanged against ``nwp500.ble.BleMqttAdapter``, which
|
|
74
|
+
offers the MQTT client methods the heater serves over Bluetooth and
|
|
75
|
+
raises ``BleUnsupportedError`` for the rest. Bluetooth temperatures use
|
|
76
|
+
the cloud models' conversion and unit-system preference;
|
|
77
|
+
``DeviceInfoBlock.min_operation_temperature()`` follows the preference
|
|
78
|
+
when ``celsius`` is not given (Fahrenheit without one, as before).
|
|
79
|
+
- **New REST endpoints.** ``NavienAPIClient`` gains
|
|
80
|
+
``get_push_settings()`` / ``set_push_settings()``
|
|
81
|
+
(``/user/get-push-settings``, ``/user/set-push-settings``),
|
|
82
|
+
``record_filter_change()`` / ``record_cip_change()`` and
|
|
83
|
+
``get_filter_history()`` / ``get_cip_history()`` (the cloud's maintenance
|
|
84
|
+
log; dates are sent ``YYYY-M-D`` without zero padding, as the app does),
|
|
85
|
+
``get_display_preferences()`` / ``set_display_preferences()``
|
|
86
|
+
(``/device/preferences/format``; the cloud rejects it for the NWP500,
|
|
87
|
+
the app only uses it for the IoT-module hydro unit), ``get_user_profile()`` (``/user/info``),
|
|
88
|
+
``get_firmware_detail()`` (``/firmware/info``; ``run_time`` is the
|
|
89
|
+
estimated update time in minutes), ``delete_tou()``
|
|
90
|
+
(``DELETE /device/tou``), ``get_manufacture_id()``,
|
|
91
|
+
``get_error_history()`` (``/device/error/list``; installer accounts
|
|
92
|
+
only, an owner account gets ``COMMON_NOT_AUTHORIZED``), ``update_device_info()``
|
|
93
|
+
(``/device/change-info``), ``list_simple_devices()`` and
|
|
94
|
+
``get_app_version()`` (unauthenticated; a 403 maintenance notice is
|
|
95
|
+
returned as ``server_check_text``; ``nwp500.get_app_version()`` asks
|
|
96
|
+
before sign-in, without a client). ``set_push_settings()`` keeps the
|
|
97
|
+
current value of a flag left ``None``, since the app always sends
|
|
98
|
+
every flag. ``get_error_history()`` entries carry the list's
|
|
99
|
+
``timeZone`` (``time_zone``, ``occurred_at_local``). New models: ``PushSettings``,
|
|
100
|
+
``DeviceInstaller``, ``FirmwareDeviceGroup``, ``FirmwareDetail``,
|
|
101
|
+
``DisplayPreferences``, ``SimpleDevice``, ``DeviceErrorHistoryEntry``,
|
|
102
|
+
``MaintenanceHistoryEntry``, ``UserProfile``, ``AppVersionInfo``. Push
|
|
103
|
+
flags are 1 = on, 2 = off, the reverse of the device's on/off flags, and
|
|
104
|
+
decode to ``True``/``False``/``None``. ``Device.installer`` and
|
|
105
|
+
``Device.alarm_info`` are parsed from ``/device/info`` as the app does;
|
|
106
|
+
``TOUSchedule.nickname`` is modelled.
|
|
107
|
+
- **AWS credentials renew without the password.**
|
|
108
|
+
``NavienAuthClient.refresh_session()`` posts to ``/auth/session/refresh``
|
|
109
|
+
with the access token and updates only the AWS credential fields the
|
|
110
|
+
server returns, as the app does when the MQTT connection fails with an
|
|
111
|
+
auth error. ``secured_sign_in()`` posts to ``/user/secured-sign-in`` with
|
|
112
|
+
the access token and overlays whatever token fields come back (observed
|
|
113
|
+
live: the AWS credentials only; the JWTs are kept), which the app uses
|
|
114
|
+
for auto-login. When the AWS credentials expire, ``ensure_valid_token()``
|
|
115
|
+
and the context manager's stored-token check now try session refresh,
|
|
116
|
+
then secured sign-in, and only then the password sign-in; both token
|
|
117
|
+
steps are skipped when the access token has expired and cannot be
|
|
118
|
+
refreshed. ``re_authenticate()`` tries secured sign-in while the access
|
|
119
|
+
token is live, then the password sign-in, so a client restored from
|
|
120
|
+
tokens alone can use it too.
|
|
121
|
+
The MQTT client's deep reconnect calls ``refresh_session()`` after the
|
|
122
|
+
forced token refresh. ``AuthTokens`` gained an optional
|
|
123
|
+
``aws_credentials_issued_at`` so a renewed AWS expiry survives
|
|
124
|
+
``to_dict()``/restore. Stored tokens without the field load as before.
|
|
125
|
+
- **Control failures are parsed and surfaced.** When the device refuses a
|
|
126
|
+
control command it publishes on ``cmd/52/navilink-{mac}/res/ctrl-fail``
|
|
127
|
+
instead of acknowledging it; the library silently dropped that message.
|
|
128
|
+
It is now parsed into ``ControlFailResponse``
|
|
129
|
+
(``ControlFailCode.DHW_CONTROL_INTERVAL_EXCEEDED`` = 2 is the only code
|
|
130
|
+
the app interprets: the previous DHW control was too recent) and emitted
|
|
131
|
+
as the ``control_failed`` client event (``ControlFailedEvent`` with
|
|
132
|
+
``own_command`` and ``reason``) for every device subscription, with a
|
|
133
|
+
WARNING log. ``subscribe_control_fail_response()`` /
|
|
134
|
+
``unsubscribe_control_fail_response()`` add a plain callback.
|
|
135
|
+
- **MQTT Last-Will and app-connection presence.** Set
|
|
136
|
+
``MqttConnectionConfig.last_will_device`` to register the app's Last-Will
|
|
137
|
+
on ``evt/52/navilink-{mac}/app-connection`` (``event.connection.status``
|
|
138
|
+
0, QoS 1) so other clients learn when this one drops; every rebuilt
|
|
139
|
+
connection carries it. ``subscribe_app_connection()`` /
|
|
140
|
+
``unsubscribe_app_connection()`` parse the topic into
|
|
141
|
+
``AppConnectionEvent`` and emit ``app_connection_received``
|
|
142
|
+
(``AppConnectionReceivedEvent``). New ``MqttTopicBuilder`` helpers
|
|
143
|
+
``control_fail_response_topic()`` and ``app_connection_topic()``.
|
|
144
|
+
- **``DeviceStatus.air_filter_remaining_hours``**: the app's remaining
|
|
145
|
+
filter life (``None`` when the reminder period is 0, else clamped at 0).
|
|
146
|
+
- **``DeviceStatus.minor_code``** (``minorCode``): the secondary alarm code
|
|
147
|
+
the app shows as ``R%03d``; 101 is the one it lets the user reset.
|
|
148
|
+
Defaults to 0 for payloads that omit it.
|
|
149
|
+
- **``DhwOperationSetting.STANDBY`` (0)**, the app's
|
|
150
|
+
``MgppOperationMode.STANDBY``. ``DeviceStatus.dhw_operation_setting``
|
|
151
|
+
previously rejected a 0. It is status-only: ``set_dhw_mode()`` refuses
|
|
152
|
+
it, as it now refuses any id outside 1-6. ``sim_adapters.settings()``
|
|
153
|
+
refuses a Standby status, which the simulator does not model.
|
|
154
|
+
- **``DREvent.GRID_EMERGENCY`` (6).** The app declares CPE and Grid
|
|
155
|
+
Emergency both as id 5 but decodes ``drEventStatus`` by array position,
|
|
156
|
+
so raw 6 is Grid Emergency; ``CPE`` (5) stays "Customer Peak Event".
|
|
157
|
+
- **``UnitType``** NCB700 (22), NFB700_H (24), CAS_NFB700_H (25),
|
|
158
|
+
NPF_SYSTEM (514); **``DeviceType``** IOT_MODULE (8), NFB700_MAIN (50),
|
|
159
|
+
NFB700_SUB (51) (the app's names; ``NPF700_MAIN``/``NPF700_SUB`` remain
|
|
160
|
+
as aliases) and ``HPWH`` as an alias of 52, which is the NWP500
|
|
161
|
+
(``DefineData.HPWH_MODEL``), not an NPF700 WiFi module. A dumped device
|
|
162
|
+
of type 50 or 51 now names it ``NFB700_MAIN`` or ``NFB700_SUB``.
|
|
163
|
+
- **A recovery model's inputs, through ``nwp500.sim.simulate``** (#165).
|
|
164
|
+
Beside ``recovery=``, ``simulate`` takes what a learned model was
|
|
165
|
+
trained with and brings: ``start_hold`` (a start's hold by the
|
|
166
|
+
minutes since the compressor last stopped), ``running_hold`` (what
|
|
167
|
+
is left of a running compressor's), ``lead_draws`` (draws recorded
|
|
168
|
+
before the start), ``draw_hold_factor`` and ``physics_usable``. Each
|
|
169
|
+
goes to the engine as given. Left at their defaults, no answer
|
|
170
|
+
changes.
|
|
171
|
+
Lead draws move the tank as the scenario's own draws do. Like them,
|
|
172
|
+
they are refused on parameters with no fitted draw response
|
|
173
|
+
(``draw_model_unsupported``), and those the model reads carry
|
|
174
|
+
``draw_uncalibrated`` and ``inlet_temp_modelled`` where they apply.
|
|
175
|
+
- **The air, its humidity and the inlet, live from Home Assistant**
|
|
176
|
+
(#166).
|
|
177
|
+
|
|
178
|
+
- ``nwp500.sim_home_assistant`` reads Home Assistant's REST API,
|
|
179
|
+
read-only. ``HomeAssistantEnvironment`` is a ``sim_live``
|
|
180
|
+
environment source:
|
|
181
|
+
|
|
182
|
+
- the air is the hourly forecast projected through a
|
|
183
|
+
``location_air`` model, anchored to a reading beside the heater at
|
|
184
|
+
most an hour old (without a model, that reading held);
|
|
185
|
+
- the humidity is a reading at most an hour old;
|
|
186
|
+
- the inlet is the plateau of the last 72 hours' long draws, from
|
|
187
|
+
the inlet probe, the meter and the circulation's runs.
|
|
188
|
+
|
|
189
|
+
Units come from each entity's ``unit_of_measurement``. A part that
|
|
190
|
+
can't be read keeps its default, and the source says why.
|
|
191
|
+
``record_inputs`` reads the same entities over a recorded history.
|
|
192
|
+
- ``nwp500.sim.meter.flow_gpm`` is a meter's flow from its volume
|
|
193
|
+
reports and its forward rate: the net volume over a trailing 90 s
|
|
194
|
+
where it holds at the draw line for 120 s, the rate elsewhere. It
|
|
195
|
+
finds the long draws the inlet plateau needs where the rate alone,
|
|
196
|
+
silent through a steady draw, misses some.
|
|
197
|
+
- ``nwp-cli simulate run``, ``snapshot`` and ``calibrate`` take
|
|
198
|
+
``--sources FILE`` (or ``NWP500_SOURCES``), a TOML file naming the
|
|
199
|
+
entities, with the token in an environment variable.
|
|
200
|
+
``calibrate`` reads the air, the humidity, the draws and the
|
|
201
|
+
circulation over the record, which makes the quiet test exact.
|
|
202
|
+
- ``sim_live.compose`` takes the heater's own air reading when the
|
|
203
|
+
environment source has none.
|
|
204
|
+
- **An optional heater simulator, ``nwp500.sim``** (#150, first part). The
|
|
205
|
+
core of a physics simulation of the NWP500: an R-134a compressor model
|
|
206
|
+
coupled to a two-zone tank, standby loss, the heating elements, draws,
|
|
207
|
+
and the heater's own controller (start triggers, the TOU hold and
|
|
208
|
+
recovery cap, the restart lockout, reservation entries, setpoint and
|
|
209
|
+
mode writes), walked as events by ``nwp500.sim.engine.simulate()``. It
|
|
210
|
+
is a port of an engine checked against recorded data from one
|
|
211
|
+
NWP500-65, and returns the same answers to the last bit. Install it
|
|
212
|
+
with the ``sim`` extra (numpy, pandas, scipy). It performs no I/O,
|
|
213
|
+
reads no clock and holds no module state, and ``import nwp500`` does
|
|
214
|
+
not load it. An import-linter contract in ``tox -e lint`` keeps it
|
|
215
|
+
from importing the MQTT or REST clients, aiohttp, the AWS IoT SDK or
|
|
216
|
+
``logging``.
|
|
217
|
+
- **The simulator's API** (#150, second part).
|
|
218
|
+
|
|
219
|
+
- ``nwp500.sim.simulate(scenario, params)`` walks a frozen
|
|
220
|
+
``Scenario``: the heater's state, settings, history, schedules,
|
|
221
|
+
environment, draws and the commands under evaluation.
|
|
222
|
+
- ``recovery()`` answers "when will the tank be hot?", and
|
|
223
|
+
``standby()`` projects the tank coasting.
|
|
224
|
+
- The TOU periods become the windows the heater holds, and the
|
|
225
|
+
reservations become firings, with the local times in the
|
|
226
|
+
scenario's zone (``sim.schedules``).
|
|
227
|
+
- Results name every default they used.
|
|
228
|
+
- ``default_params(volume_code)`` carries one NWP500-65's parameters,
|
|
229
|
+
with the tank volume scaled for the 50 and 80 gal models.
|
|
230
|
+
- ``sim.codec`` reads and writes scenarios as JSON documents.
|
|
231
|
+
- ``nwp500.sim_adapters`` builds the inputs from ``DeviceStatus``,
|
|
232
|
+
``DeviceFeature``, ``ReservationSchedule`` and ``TOUInfo``, reading
|
|
233
|
+
the raw fields so the unit preference cannot change them. It is
|
|
234
|
+
where units are crossed: ``environment``, ``loads``, ``write`` and
|
|
235
|
+
``reservation_entry`` take a caller's values in the preferred units
|
|
236
|
+
(degC and litres under ``metric``), and ``path``, ``temperature``
|
|
237
|
+
and ``volume`` give a result's back in them. The simulator itself
|
|
238
|
+
works in degF and US gallons.
|
|
239
|
+
- Docs: a how-to (``docs/how-to/simulate-heater.rst``), from fetching
|
|
240
|
+
the heater's data to evaluating a change before sending it; a
|
|
241
|
+
reference of every input, its unit, default and source
|
|
242
|
+
(``docs/reference/simulator-inputs.rst``); an explanation of the
|
|
243
|
+
model and its accuracy (``docs/explanation/simulator.rst``); and the
|
|
244
|
+
API reference.
|
|
245
|
+
|
|
246
|
+
- Hot water at the tap: ``nwp500.sim.readiness(state, settings,
|
|
247
|
+
params)`` gives deliverable gallons now; ``Loads.windows`` judge a
|
|
248
|
+
shower or a bath (deliverable gallons and margin at the onset, and
|
|
249
|
+
whether the delivered water ran cold); ``SimulationResult.delivery``
|
|
250
|
+
is the delivered temperature along the walk (``sim.delivery``).
|
|
251
|
+
- The heat book (``sim.book``): the tank's heat by bookkeeping, walked
|
|
252
|
+
through the simulation as the engine's ``HeatLedger`` so a draw that
|
|
253
|
+
spends it takes the zones down. Started from the caller's knowledge
|
|
254
|
+
(``full_tank_book``) or their own record, never guessed from the
|
|
255
|
+
probes.
|
|
256
|
+
- ``HeaterParams`` gains ``readiness`` (the line, the outlet lag, the
|
|
257
|
+
fall rate, the dip tolerance, evidence) and ``book`` parts, with one
|
|
258
|
+
installation's measured defaults and their provenance.
|
|
259
|
+
- Time to ready: ``nwp500.sim.time_to_ready(scenario, params)`` says
|
|
260
|
+
whether the tank can run a bath, when it can in the heat pump and
|
|
261
|
+
in each element mode with the energy to get there, and how many
|
|
262
|
+
showers it is ready for, along the recovery the heater runs next
|
|
263
|
+
(``next_recovery``) (``sim.ready``).
|
|
264
|
+
- ``standby(..., settings=)`` says when the heater would start itself
|
|
265
|
+
on the coast and why; ``element_mode`` projects a recovery in an
|
|
266
|
+
element mode.
|
|
267
|
+
- A walk with draws outside the range the draw terms were fitted over
|
|
268
|
+
says ``draw_out_of_domain`` (``DrawParams.domain_upper_f``,
|
|
269
|
+
``domain_volume_gal``); a write or a written reservation outside the
|
|
270
|
+
setpoint range the simulation runs is refused.
|
|
271
|
+
|
|
272
|
+
**Behaviour notes.** Answers that a client carrying an earlier copy of
|
|
273
|
+
this engine will see change at the switch:
|
|
274
|
+
|
|
275
|
+
- When no ``RecoveryModel`` answers (none given, or it declines) and
|
|
276
|
+
the physics integration stops at its bound short of the target,
|
|
277
|
+
there is no recovery and ``simulate`` raises ``SimulationError``.
|
|
278
|
+
The bound is no longer served as a duration.
|
|
279
|
+
- A recovery's reasons keep the served recovery's own (a truncated
|
|
280
|
+
integration, a model's diagnostics) beside ``recovery_low_confidence``.
|
|
281
|
+
- ``mode_recovery``'s ``upper_end_f`` is the zone's own temperature
|
|
282
|
+
when the element does not start or has nothing to do.
|
|
283
|
+
- A draw during a recovery honours ``draws_on`` and is priced on the
|
|
284
|
+
configured tank, not the 63.2 gal default.
|
|
285
|
+
- No ``hp_lower_on`` disables only the lower trigger; the upper
|
|
286
|
+
zone's at a TOU window's opening still starts the heater.
|
|
287
|
+
- A draw at a draw window's onset counts its cold side when the window
|
|
288
|
+
is judged.
|
|
289
|
+
- TOU prices are compared decoded at their own ``decimal_point``; each
|
|
290
|
+
period is laid out on the dates its day and season masks select
|
|
291
|
+
before windows that touch are joined across midnight.
|
|
292
|
+
- Scenario instants are held in UTC, so a horizon is elapsed time
|
|
293
|
+
across a daylight-saving change; a scenario document keeps which
|
|
294
|
+
fields were left to their defaults.
|
|
295
|
+
- ``sim.book.SpanLoss`` lets a client charge a loss the book does not
|
|
296
|
+
model; without one, the book charges none.
|
|
297
|
+
- Circulation runs within 10 minutes of each other are one run over
|
|
298
|
+
the whole schedule, so a coast boundary between them no longer
|
|
299
|
+
charges each its own displaced volume.
|
|
300
|
+
- ``default_params()`` has no mixing valve; ``site_example_params()``
|
|
301
|
+
is the installation the values were fitted at, its valve included
|
|
302
|
+
(``with_valve``, ``SITE_VALVE``). The core's draw functions default
|
|
303
|
+
to no valve as well. Each parameter says whether it describes the
|
|
304
|
+
heater or the installation (``scope_of``), and the frame it was
|
|
305
|
+
fitted in (``note_of``).
|
|
306
|
+
- Circulation (``sim.circulation``): an installation's runs
|
|
307
|
+
(``Loads.circulation``, ``sim_adapters.circulation`` for a weekly
|
|
308
|
+
schedule) charged as flowing draws and to the heat book while they
|
|
309
|
+
run, on ``HeaterParams.circulation``; with none, nothing is charged.
|
|
310
|
+
- The air where the heater stands, from the heater alone and a public
|
|
311
|
+
forecast: ``sim_adapters.location_air_reading`` (the heater's own
|
|
312
|
+
reading, only while the compressor runs) and
|
|
313
|
+
``sim_adapters.location_air`` (an outdoor forecast projected by
|
|
314
|
+
``sim.location_air``, anchored to that reading, with ``fit`` for
|
|
315
|
+
one's own model). ``time_to_ready`` and ``next_recovery`` take a
|
|
316
|
+
``RecoveryModel``.
|
|
317
|
+
- Calibration from the heater's own record (``sim.calibration``,
|
|
318
|
+
``sim_adapters.history`` to build one from ``DeviceStatus``
|
|
319
|
+
readings): the standby loss law and the zones' mixing constant from
|
|
320
|
+
quiet windows, then the compressor constant and evaporator approach
|
|
321
|
+
from heat-pump recoveries that reached their target, then input
|
|
322
|
+
power and apparent COP against the heater's own power readings, each
|
|
323
|
+
reported
|
|
324
|
+
with what it rested on (held-out error, windows used, a fit pinned
|
|
325
|
+
on a bound). An element run counts as busy in the quiet test, not
|
|
326
|
+
only the compressor and hot water in use.
|
|
327
|
+
- ``forecast``: when a recovery must start to have the tank ready at
|
|
328
|
+
a time, and whether it can (``feasible``, ``shortfall_minutes``),
|
|
329
|
+
to the heater's own stop threshold or a partial charge, with
|
|
330
|
+
``no_heating_required`` (the lower zone at its target, or a zero
|
|
331
|
+
energy count outside a TOU window), ``not_scheduled``, and the
|
|
332
|
+
trajectory's electricity, stored heat and COP.
|
|
333
|
+
``active_cycle``: what is left of a running recovery, its physics
|
|
334
|
+
estimate on the evaporator coil's live reading.
|
|
335
|
+
``Settings.hp_lower_off_f``, ``hp_upper_on_f`` and
|
|
336
|
+
``HeaterState.energy_to_setpoint_wh`` carry what they need, filled
|
|
337
|
+
by the adapters; ``recovery`` says ``not_scheduled`` in Vacation and
|
|
338
|
+
Power Off.
|
|
339
|
+
- The heater's profile (``sim.profile``): its modes and their energy,
|
|
340
|
+
setpoint range and step, depletion threshold and tank, under a
|
|
341
|
+
revision digest; and ``recovery_as_of``, a kept recovery aged to
|
|
342
|
+
now.
|
|
343
|
+
- The element terms from a heater's raw record (``sim.calibration``):
|
|
344
|
+
the upper probe's lag and rate as one line from clean stints
|
|
345
|
+
(``element_stints``, ``fit_element_lag``), where each element
|
|
346
|
+
switches off and what it draws per metered stint
|
|
347
|
+
(``element_off_points``, ``element_power``), and the zones' rates
|
|
348
|
+
and element power on the one-minute grid (``element_grid``,
|
|
349
|
+
``fit_element_rates``), each with day-bootstrap intervals.
|
|
350
|
+
- Draws from a weekly draw forecast (``sim.draw_forecast``): an issue
|
|
351
|
+
checked whole, its expected draws (rate x per-draw mean, the
|
|
352
|
+
per-draw volume kept), the heater's draw windows, and the worst case
|
|
353
|
+
of each window's long draws first (``forecast_loads``,
|
|
354
|
+
``simulate(withheld_windows=)``). A draw's domain is now judged by
|
|
355
|
+
its per-draw volume where it carries one (``Draw.per_draw_gal``),
|
|
356
|
+
not its fractional expectation.
|
|
357
|
+
- The mains from an inlet probe (``sim.cold_supply``,
|
|
358
|
+
``sim_adapters.cold_supply``): the median plateau of recent long
|
|
359
|
+
draws with circulation off and the meter reading flow, from minute 9
|
|
360
|
+
of each; ``on_the_mains`` converts a book's gallons at it.
|
|
361
|
+
- What the heater did over a range, from its record, as the model
|
|
362
|
+
reads it: ``sim.record.Record`` (each signal's change rows, its
|
|
363
|
+
writes and the heater's own drops, TOU switches and window edges,
|
|
364
|
+
starts with elements corroborated by power;
|
|
365
|
+
``sim_adapters.record``), ``sim.attribution`` (why each recorded
|
|
366
|
+
start happened) and ``sim.observed`` (modes and whether each was in
|
|
367
|
+
effect, when each source ran, counted and attributed starts, energy
|
|
368
|
+
per slot, the delivered temperature, and the record's gaps;
|
|
369
|
+
``sim_adapters.observe``). ``replay`` reads its writes from the same
|
|
370
|
+
record.
|
|
371
|
+
- The readiness evidence and the heat book's own terms from a metered
|
|
372
|
+
record (``sim.calibration``, ``sim_adapters.draw_frame``): clean
|
|
373
|
+
draws and their demonstrated volumes reduced to the frontier, with a
|
|
374
|
+
time-ordered held-out check, and the cap after a cold lower zone;
|
|
375
|
+
the book's compressor rate, its slope with the air, standby UA and
|
|
376
|
+
circulation's loss fitted on re-anchors of full tanks
|
|
377
|
+
(``RecordedBook``, ``fit_book_terms``), its error by anchor age, and
|
|
378
|
+
the empty points in the book's frame and the drawable one, and the
|
|
379
|
+
error bands (``book_error_btu``: the p90 pooled over anchors an hour
|
|
380
|
+
to 18 h old, and the 18-24 h band's, each rounded up to 100 BTU).
|
|
381
|
+
With a meter, the COP fit prices the heat each cycle's draws and
|
|
382
|
+
circulation runs took, and the draws just before it
|
|
383
|
+
(``metered_withdrawn``, ``metered_lead``; ``calibrate(draws=,
|
|
384
|
+
circulation=)``), as the model's fit did, each run at the flow it
|
|
385
|
+
ran at (``circulation_flow_at=``, ``flow_lookup``). What could not be fitted
|
|
386
|
+
keeps the base's value and is named in ``HeaterParams.unfitted``;
|
|
387
|
+
every answer it reaches says ``physics_uncalibrated``,
|
|
388
|
+
``energy_uncalibrated``, ``standby_uncalibrated`` or
|
|
389
|
+
``tau_gap_prior``.
|
|
390
|
+
The probes' share of circulation's heat
|
|
391
|
+
(``circulation_windows``, ``fit_ungated_observability``), fitted
|
|
392
|
+
against the displaced volume it is used with.
|
|
393
|
+
Circulation's terms from its own telemetry: each run's measured heat
|
|
394
|
+
(``measure_runs``), the displaced volume that prices them
|
|
395
|
+
(``fit_displaced_gal``), its recent flow and the flow a past run ran
|
|
396
|
+
at (``recent_flow_gpm``, ``flow_lookup``), as the model fitted them.
|
|
397
|
+
The book's response to a draw, fitted on the record's long draws by
|
|
398
|
+
the procedures its values were fitted with: the cold front's place
|
|
399
|
+
and width (``fit_front``), the lower zone's settling after a draw
|
|
400
|
+
(``fit_front_settle``) and the upper zone's mixing
|
|
401
|
+
(``fit_zone_mix``), each with a day bootstrap.
|
|
402
|
+
- The heat book from a recorded history, as the model kept it:
|
|
403
|
+
``sim.book.Step``, ``book_steps`` (the anchor on a quiet full tank
|
|
404
|
+
after a recovery, or a recovery's end during a draw; every step
|
|
405
|
+
booked from it; the inlet the median of 3 h of a probe), and
|
|
406
|
+
``sim_adapters.book_from_history``, which grids the heater's
|
|
407
|
+
statuses, a hot-water meter's flow, circulation's runs and flow and
|
|
408
|
+
an inlet probe onto 10 s steps and books 18 h, else 72 h aged.
|
|
409
|
+
``CirculationParams.meter_share`` is the share of a run's flow the
|
|
410
|
+
meter also reads; ``BookParams`` gains ``history_h``, ``draw_gpm``
|
|
411
|
+
and ``inlet_window_s``.
|
|
412
|
+
- ``sim_adapters.replay``: a past scenario from the statuses the
|
|
413
|
+
heater reported, as the model's replay read its record: the state
|
|
414
|
+
then, the compressor's last stop and the last heating from their
|
|
415
|
+
edges, the recorded setpoint and mode changes as writes (but the
|
|
416
|
+
heater's own drop to Heat Pump at a window), and the TOU switch's
|
|
417
|
+
changes (``Schedules.tou_switches``, which the walk applies).
|
|
418
|
+
- The heater's own TOU marker (``Settings.hp_upper_on_f`` raised):
|
|
419
|
+
``time_to_ready`` and ``element_mode`` read "in a window now" from
|
|
420
|
+
it, and ``next_recovery`` lets it override the schedule for a
|
|
421
|
+
recovery running now (``api.marked_windows``), as the model did;
|
|
422
|
+
an unknown marker counts as in force. ``simulate(tou_windows_in_force=)``
|
|
423
|
+
takes the windows to keep, and TOU on with no periods says
|
|
424
|
+
``tou_schedule_unknown``. ``element_mode(setpoint_f=)`` projects to a
|
|
425
|
+
what-if setpoint while the heater's own decides the window test.
|
|
426
|
+
- ``time_to_ready`` says ``inlet_temp_modelled`` when a booked answer
|
|
427
|
+
converts at an inlet nobody measured (``inlet_measured=``, or a
|
|
428
|
+
book whose steps measured it); ``next_recovery``'s coast loses the
|
|
429
|
+
heat circulation takes on it as well as standby; a served recovery
|
|
430
|
+
under 0.6 confidence says ``recovery_low_confidence``. ``standby``
|
|
431
|
+
reports the gallons drawn and the heat they took, circulation's
|
|
432
|
+
minutes and heat, and the mean air over the coast.
|
|
433
|
+
- ``default_params()`` names the installation's book, readiness and
|
|
434
|
+
draw terms in ``HeaterParams.unfitted`` (``INSTALLATION_TERMS``):
|
|
435
|
+
they are one installation's fits, so answers they reach say
|
|
436
|
+
``book_uncalibrated``, ``readiness_uncalibrated`` or
|
|
437
|
+
``draw_uncalibrated``. A ``Record`` drops missing readings on entry
|
|
438
|
+
and takes its installation's cadences and the heater's resting
|
|
439
|
+
threshold (``drop_within``, ``element_power_slack``,
|
|
440
|
+
``resting_upper_on_f``). ``observe(profile=, revision_since=)``
|
|
441
|
+
names modes by a heater profile. ``schedules.rule_windows`` lays out
|
|
442
|
+
TOU rules that have no prices, such as a learned schedule. The
|
|
443
|
+
engine, the tank physics and the physics models are documented as
|
|
444
|
+
public (:doc:`/reference/python_api/sim`).
|
|
445
|
+
- **``nwp-cli simulate``, and the live sources it composes** (#151).
|
|
446
|
+
|
|
447
|
+
- ``nwp500.sim_live`` reads what a scenario needs from the device:
|
|
448
|
+
``LiveHeaterSource`` (the state, the settings, the air and the
|
|
449
|
+
tank size, from one status and one feature read) and
|
|
450
|
+
``LiveScheduleSource`` (the reservations over MQTT, the TOU
|
|
451
|
+
periods over REST, the TOU switch from the status).
|
|
452
|
+
``compose()`` awaits them, and any other source with an ``async
|
|
453
|
+
read()`` (``Given`` for a value in hand), into a ``Scenario``. A
|
|
454
|
+
read with no reply raises ``LiveReadError``, as does a TOU read
|
|
455
|
+
that fails while TOU is on. The simulator still imports none of
|
|
456
|
+
it.
|
|
457
|
+
- ``nwp-cli simulate run`` walks the heater as it is now, a scenario
|
|
458
|
+
file, or the heater now with a scenario file laid on it
|
|
459
|
+
(``--live``). Each part is chosen on its own: a flag
|
|
460
|
+
(``--start``, ``--hours``, ``--tz``, ``--ambient``,
|
|
461
|
+
``--environment``, ``--inlet``, ``--draws``, ``--plan``,
|
|
462
|
+
``--params``), then the scenario file, then the device. It prints
|
|
463
|
+
a table (the heating intervals, the energy by source, readiness,
|
|
464
|
+
the draw windows and which ran cold, the defaults used), the
|
|
465
|
+
result document (``--format json``) or the path (``--format
|
|
466
|
+
csv``). ``nwp-cli simulate snapshot`` writes the heater and its
|
|
467
|
+
schedules now as a scenario file to edit and replay. Without the
|
|
468
|
+
``sim`` extra the commands stop with an install hint.
|
|
469
|
+
``examples/simulator/`` has a scenario, a plan and an overlay to
|
|
470
|
+
start from; :doc:`/how-to/simulate-heater` shows each use.
|
|
471
|
+
- **Record a heater's history and calibrate the simulator to it**
|
|
472
|
+
(#152).
|
|
473
|
+
|
|
474
|
+
- ``nwp500.status_history`` is the history's format: one JSON line
|
|
475
|
+
per status, ``{"t": <ISO 8601 instant>, "status": <raw payload>}``.
|
|
476
|
+
``line()`` writes one and ``read()`` reads them back as
|
|
477
|
+
``(instant, DeviceStatus)`` pairs. It does no I/O and needs no
|
|
478
|
+
extra.
|
|
479
|
+
- ``nwp-cli simulate record HISTORY`` appends the heater's statuses
|
|
480
|
+
to a file until stopped, by default asking for one every 30 s.
|
|
481
|
+
- ``nwp-cli simulate calibrate HISTORY`` fits the standby loss, the
|
|
482
|
+
compressor and the power to that history
|
|
483
|
+
(``sim.calibration.calibrate``). It prints a row for each term: the
|
|
484
|
+
value, what it was fitted from, its error, and whether it was
|
|
485
|
+
fitted or kept the base's value, and why. ``-o`` writes the fitted
|
|
486
|
+
``HeaterParams`` document for ``simulate run --params``. The
|
|
487
|
+
standby fit needs the air beside the heater, from a sensor
|
|
488
|
+
(``--air``, a CSV file) or a constant (``--ambient``). Given, that
|
|
489
|
+
air is the recoveries' air too. The heater's own reading, filled
|
|
490
|
+
across the gaps between compressor runs, and an outdoor projection
|
|
491
|
+
both biased the fitted loss, so neither stands in for the air.
|
|
492
|
+
Without ``--humidity`` the fits get 50 %, their documented default.
|
|
493
|
+
A gap between statuses of more than 4 times the record's usual
|
|
494
|
+
spacing (at least 5 minutes) is unobserved, so no quiet window or
|
|
495
|
+
recovery spans a stopped recording. If a write fails, ``record``
|
|
496
|
+
stops with an error.
|
|
497
|
+
- ``sim_adapters.history(..., max_gap=)`` marks such gaps: the
|
|
498
|
+
sample before one is set to water in use and excluded.
|
|
499
|
+
- :doc:`/how-to/calibrate-simulator` covers the recording, the air,
|
|
500
|
+
the report, and what a history cannot tell (the draws, a
|
|
501
|
+
circulation loop the heater does not run).
|
|
502
|
+
- Three helpers the issue proposed are left out:
|
|
503
|
+
|
|
504
|
+
- **A mains-temperature estimate from the air.** The draw terms
|
|
505
|
+
were fitted against a measured inlet, so an estimated inlet
|
|
506
|
+
would bias every draw.
|
|
507
|
+
- **A draw-timing profile from the "hot water in use" flag.** The
|
|
508
|
+
flag also fires on circulation, and the expected draws from such
|
|
509
|
+
a profile never predicted better than no draws.
|
|
510
|
+
- **A check against the daily energy totals.** Power and COP are
|
|
511
|
+
fitted cycle by cycle.
|
|
512
|
+
|
|
513
|
+
Changed
|
|
514
|
+
-------
|
|
515
|
+
- **Recirculation mode values follow the NaviLink app.** ``RecirculationMode``
|
|
516
|
+
had 2 and 3 swapped and 4 wrong: the app's
|
|
517
|
+
``MgppRecirculationOperationMode`` (sent by ``recirc-mode`` and decoded
|
|
518
|
+
from ``recircOperationMode``) is 1 Always on, 2 Weekly, 3 HotButton only,
|
|
519
|
+
4 No recirculation. The members are now ``ALWAYS_ON``, ``WEEKLY``,
|
|
520
|
+
``HOT_BUTTON_ONLY`` and ``OFF``; ``ALWAYS`` remains as an alias of
|
|
521
|
+
``ALWAYS_ON``. **Behaviour change:** ``BUTTON``, ``SCHEDULE`` and
|
|
522
|
+
``TEMPERATURE`` are removed, since their meanings were wrong; code that
|
|
523
|
+
sent ``RecirculationMode.BUTTON`` (2) was selecting the weekly schedule.
|
|
524
|
+
``RECIRC_MODE_TEXT``, the ``recirc`` CLI command, the recirculation
|
|
525
|
+
example and the protocol docs follow. ``model_dump()`` writes enum
|
|
526
|
+
names, so a dumped status now says ``"ALWAYS_ON"`` for 1 (was
|
|
527
|
+
``"ALWAYS"``) and ``"WEEKLY"`` for 2 (was ``"BUTTON"``); code keyed on
|
|
528
|
+
the old strings must follow.
|
|
529
|
+
- **Reservation mode 5 is Power Off, not Vacation.** Weekly-schedule
|
|
530
|
+
entries carry the app's ``MgppReservationMode`` (0 not reserved, 1 Heat
|
|
531
|
+
Pump, 2 Electric, 3 Energy Saver, 4 High Demand, 5 Power Off; there is
|
|
532
|
+
no vacation entry), now the ``ReservationMode`` enum.
|
|
533
|
+
``ReservationEntry.mode_name`` resolves through it, so a mode-5 entry
|
|
534
|
+
reads "Power Off" instead of "Vacation" and mode 0 reads "Not Reserved".
|
|
535
|
+
**Behaviour change:** reservation writes (``add_reservation()``,
|
|
536
|
+
``update_reservation()``, ``build_reservation_entry()`` and the MQTT
|
|
537
|
+
reservation validator) now accept mode 1-5 and reject 6, which was never
|
|
538
|
+
a valid entry mode. An entry already on the heater with mode 6 (earlier
|
|
539
|
+
versions documented 6 as Power Off) does not block the reservation
|
|
540
|
+
helpers: they send the entries they did not change as they read them
|
|
541
|
+
(``update_reservations(kept=)``). Changing such an entry needs a mode
|
|
542
|
+
of 1-5. The simulator's reservation entries take the same numbering
|
|
543
|
+
(``sim.inputs.EntryMode``); only 1-4 are modelled, as before.
|
|
544
|
+
- **``dr_override_status`` is an on/off flag, not hours.** The app decodes
|
|
545
|
+
it with its 0/1/2 on-off enum and shows "DR applied" when it is 2. The
|
|
546
|
+
field stays an ``int`` but no longer carries an ``hours`` unit; the new
|
|
547
|
+
``DeviceStatus.dr_override_active`` is True when it is 2.
|
|
548
|
+
- **Air-filter life comes from ``cumulated_op_time_eva_fan``.** The app
|
|
549
|
+
computes remaining filter life as ``air_filter_alarm_period -
|
|
550
|
+
cumulated_op_time_eva_fan`` and never reads ``air_filter_alarm_elapsed``.
|
|
551
|
+
The two descriptions were the wrong way round; ``cumulated_op_time_eva_fan``
|
|
552
|
+
is fan hours since the last filter reset and ``air_filter_alarm_elapsed``
|
|
553
|
+
is unconfirmed.
|
|
554
|
+
- **``signal_app_connection()`` sends the app's message shape.** It
|
|
555
|
+
published ``{clientID, timestamp}``, a shape nothing reads. It now
|
|
556
|
+
publishes the NaviLink app's ``RequestLastWill`` envelope with
|
|
557
|
+
``event.connection.status`` 1.
|
|
558
|
+
- Sign-in and ``/auth/refresh`` requests now send the library's
|
|
559
|
+
``User-Agent`` and ``Content-Type`` headers, as every other request does.
|
|
560
|
+
- **The heater's own air reading is not as close as documented in
|
|
561
|
+
every season.** ``sim_adapters.location_air_reading``,
|
|
562
|
+
``sim.location_air`` and the inputs reference said it read the air
|
|
563
|
+
beside the heater within about 0.35 °F while the compressor runs.
|
|
564
|
+
Measured against a sensor beside the heater, that held from August
|
|
565
|
+
to October. From May to July the reading was up to about 2 °F warm,
|
|
566
|
+
and in a run's first 2 minutes about 2.7 °F warm. The idle reading
|
|
567
|
+
is about 6 °F warm, not 5.
|
|
568
|
+
- **The CLI logs to stderr.** It logged to stdout, so a warning could
|
|
569
|
+
land in the middle of ``--json``, ``--raw`` or CSV output piped to
|
|
570
|
+
another program. Output stays on stdout.
|
|
571
|
+
- **The package root loads its exports on first use** (PEP 562).
|
|
572
|
+
``from nwp500 import NavienMqttClient``, ``nwp500.DeviceStatus`` and
|
|
573
|
+
``nwp500.mqtt`` work as before, and type checkers read the same names,
|
|
574
|
+
but ``import nwp500.sim`` and ``nwp500.sim_adapters`` no longer load
|
|
575
|
+
the clients, aiohttp or the AWS IoT SDK (tested in a fresh
|
|
576
|
+
interpreter).
|
|
577
|
+
- **Superheat is converted as a difference** (#153).
|
|
578
|
+
``DeviceStatus.target_super_heat`` and ``current_super_heat`` converted
|
|
579
|
+
their raw tenths of a degree Celsius as absolute temperatures, so in
|
|
580
|
+
Fahrenheit they added 32. Superheat is a temperature difference (suction
|
|
581
|
+
minus evaporator), so they now use ``DeciCelsiusDelta``, as the
|
|
582
|
+
``*_diff_temp_setting`` properties already do. Raw 50 now reads 9.0 degF
|
|
583
|
+
instead of 41.0 degF. **Behaviour change:** every Fahrenheit value drops
|
|
584
|
+
by 32, so stored or charted Fahrenheit superheat from earlier versions is
|
|
585
|
+
32 degF too high. Celsius values are unchanged. The protocol docs now
|
|
586
|
+
describe both fields as differences.
|
|
587
|
+
- **Vacation accepts 1-99 days** (#155). ``set_dhw_mode()`` (vacation),
|
|
588
|
+
``set_vacation_days()`` and ``set_vacation_duration()`` rejected anything
|
|
589
|
+
over 30 days. The NaviLink app's vacation picker and the front panel
|
|
590
|
+
(User's Information Manual, section 3.4.5) both offer 1-99, so the
|
|
591
|
+
library now accepts the same. The ``vacation`` and
|
|
592
|
+
``vacation-duration`` CLI commands follow.
|
|
593
|
+
|
|
594
|
+
Fixed
|
|
595
|
+
-----
|
|
596
|
+
- ``FirmwareInfo.device_group`` was typed as a string; the app reads an
|
|
597
|
+
object ``{groupSeq, unitNum}`` (plus a top-level ``deviceGroupUnitNo``).
|
|
598
|
+
It is now ``FirmwareDeviceGroup``, with a bare string still accepted.
|
|
599
|
+
- ``convert_tou()`` hard-coded ``userType: "O"``; it now sends the
|
|
600
|
+
signed-in user's type from the sign-in response, defaulting to ``"O"``.
|
|
601
|
+
- **A reservation entry turns a powered-off heater on in the
|
|
602
|
+
simulator.** The controller skipped entries while powered off, as in
|
|
603
|
+
Vacation, on a misreading of the device test, which found that a
|
|
604
|
+
powered-off heater fires the entry and turns on in its mode (two
|
|
605
|
+
runs). ``Settings.powered_off`` tells power-off from Vacation; the
|
|
606
|
+
entry is judged as a mode write out of power-off, and the result
|
|
607
|
+
carries ``powered_on_by_entry``. In Vacation entries are still
|
|
608
|
+
skipped.
|
|
609
|
+
|
|
610
|
+
Documentation
|
|
611
|
+
-------------
|
|
612
|
+
- **What the device tests and the record measured.** Three new
|
|
613
|
+
explanation pages: :doc:`/explanation/recovery-speed` (heat output,
|
|
614
|
+
minutes per degF, compressor power and efficiency, start-up energy,
|
|
615
|
+
the air's effect, how much of the tank heats),
|
|
616
|
+
:doc:`/explanation/standby-loss` (the jacket loss, a circulation run's
|
|
617
|
+
heat and where it shows) and :doc:`/explanation/draws` (the delivered
|
|
618
|
+
temperature, the probes through a draw, how much hot water is left,
|
|
619
|
+
reading a cold-supply probe). The element pages gain hours and energy
|
|
620
|
+
by mode, the off point and the probe's lag; scheduling gains write and
|
|
621
|
+
entry timing; the simulator's Accuracy section gains time to ready and
|
|
622
|
+
the lower zone after a draw.
|
|
623
|
+
- **Where the simulator's defaults come from.** A table in
|
|
624
|
+
:doc:`/reference/simulator-inputs` gives each default parameter's
|
|
625
|
+
evidence: what it was measured or fitted on, its error, and how far
|
|
626
|
+
the per-retrain values move between retrains.
|
|
627
|
+
- **Accuracy figures corrected.** ``simulator.rst`` gave 18 % and the
|
|
628
|
+
13 and 45 min deficit-band errors as physics alone; they scored the
|
|
629
|
+
physics with a learned correction, and are removed. The "cold starts
|
|
630
|
+
about 55 min short" figure is replaced by the recorded errors by time
|
|
631
|
+
left. The lower trigger's in-window wait is given with its full count.
|
|
632
|
+
- ``unknown-values``, ``tank-energy`` and the MQTT protocol reference now
|
|
633
|
+
cite the current NaviLink app release they were checked against.
|
|
634
|
+
- ``docs/openapi.yaml`` and ``rest_api.rst`` describe every endpoint above
|
|
635
|
+
plus ``POST /auth/session/refresh`` and ``POST /user/secured-sign-in``;
|
|
636
|
+
``mqtt_protocol.rst`` documents ``res/ctrl-fail`` and the
|
|
637
|
+
``app-connection`` event / Last-Will payload; ``diagnose-mqtt.rst`` gains
|
|
638
|
+
"Why was my command rejected?"; ``maintenance.rst`` gains a "Maintenance
|
|
639
|
+
Log" section; ``authenticate.rst`` explains the password-free renewal
|
|
640
|
+
chain.
|
|
641
|
+
- **Less noise.** Implementation notes, the docs' own revision history
|
|
642
|
+
and where things were ported from are gone from the pages, and
|
|
643
|
+
section titles are plainer. The ``sim`` extra is listed under
|
|
644
|
+
:doc:`/reference/installation`'s optional dependencies.
|
|
645
|
+
- **Corrections from the device tests and the recorded history.** The
|
|
646
|
+
explanation pages, the how-tos, the protocol reference and the
|
|
647
|
+
simulator's docstrings said things later measurements contradict:
|
|
648
|
+
|
|
649
|
+
- High Demand's element starts somewhere in (0.2, 0.4] degC short,
|
|
650
|
+
not (0.2, 0.7]; Electric ran upper first in the commanded runs, but
|
|
651
|
+
the record also has the lower running alone; the element-run and
|
|
652
|
+
mid-stint counts are the record's.
|
|
653
|
+
- Inside a TOU window the upper tank starts a cycle when it is more
|
|
654
|
+
than 14.4 degF short, at the opening or later. A setpoint write
|
|
655
|
+
starts one in 120 of 122 writes leaving the upper tank short (not
|
|
656
|
+
about 70 %), a lowering to the upper tank or below in none, and in
|
|
657
|
+
``HEAT_PUMP`` a draw does not start one while the upper tank is at
|
|
658
|
+
or above the setpoint. The restart lockout measured 5.5 min.
|
|
659
|
+
- The simulator's accuracy figures are the ones measured with
|
|
660
|
+
start-up charged once (3.6 % and 4.4 % low; starts 31 of 40 held
|
|
661
|
+
out), and the heater's idle air reading is about 6 degF warm.
|
|
662
|
+
- ``HYBRID_BOOST_MODE`` is also Energy Saver with an element running;
|
|
663
|
+
``touStatus`` means TOU is enabled, not that a period is in force.
|
|
664
|
+
- A device has held at least 32 reservation entries and 20 TOU
|
|
665
|
+
periods; a list write can be lost with no error, so read it back;
|
|
666
|
+
a powered-off heater runs its reservation entries; forcing a full
|
|
667
|
+
recovery with ``tou_override_status`` is untested.
|
|
668
|
+
- The ambient effect on the compressor is about 1.18x, not 1.7x; the
|
|
669
|
+
standby soak's figures, the 60 h gap prior and the circulation runs
|
|
670
|
+
behind the lower-zone drop are stated as measured.
|
|
671
|
+
- **Docs that contradicted the code or the vendor's sources** (#155). Each
|
|
672
|
+
now has one value with its source:
|
|
673
|
+
|
|
674
|
+
- The mode-ID table in ``device_features.rst`` numbered Energy Saver 2,
|
|
675
|
+
High Demand 3 and Electric 4. The IDs are Electric 2, Energy Saver 3,
|
|
676
|
+
High Demand 4, as in ``DhwOperationSetting`` and the NaviLink app's
|
|
677
|
+
``MgppOperationMode``.
|
|
678
|
+
- ``track-energy.rst`` used ``comp_running_minute_total`` and
|
|
679
|
+
``heater1/2_running_minute_total``, which do not exist. Lifetime run
|
|
680
|
+
times come from the diagnostics query, in hours
|
|
681
|
+
(``DeviceDiagnostics.ta_data.cumulated_op_time_comp``, ``_uhe``,
|
|
682
|
+
``_lhe``).
|
|
683
|
+
- ``currentHeatUse`` is the ``HeatSource`` enum (0-3), not a bool.
|
|
684
|
+
- ``drEventStatus`` is the ``DREvent`` enum, not a bitfield.
|
|
685
|
+
- ``tou-recovery-cap.rst`` named ``dhw_outlet_temperature``, Home
|
|
686
|
+
Assistant's name for ``dhw_temperature``.
|
|
687
|
+
- Vacation is 1-99 days; the docs said 0-99 in some places, and the
|
|
688
|
+
library said 1-30.
|
|
689
|
+
- Anti-legionella heats to 140 degF (60 degC), defaults to a 7-day
|
|
690
|
+
period, is adjustable from 1 to 30 days and ships disabled (User's
|
|
691
|
+
Information Manual, section 3.14.2). The scheduling how-to said 158
|
|
692
|
+
degF, a 14-day default, periods up to 90 days and "mandatory".
|
|
693
|
+
- ``dhwTemperatureMin`` is the installer's minimum operation
|
|
694
|
+
temperature: 95-113 degF, factory default 95 degF. 104.9 degF was one
|
|
695
|
+
unit's setting, not a contradiction.
|
|
696
|
+
- ``ambientTemperature`` is the air around the unit, per the manual;
|
|
697
|
+
neither "intake" nor "outlet" is sourced. The heat pump runs only
|
|
698
|
+
between 41 and 113 degF.
|
|
699
|
+
- ``docs/explanation/advanced-features.rst`` is removed. Its weather
|
|
700
|
+
thresholds, COP figures, DR bitfield, stratification tables and code
|
|
701
|
+
sample were unsourced or wrong, and the sourced material is covered
|
|
702
|
+
by the protocol reference, ``what-starts-a-recovery`` and
|
|
703
|
+
``tank-energy``.
|
|
704
|
+
|
|
705
|
+
- **Flow-rate units corrected** (#154). The protocol reference said
|
|
706
|
+
``currentDhwFlowRate`` and ``recircDhwFlowRate`` were tenths of a GPM and
|
|
707
|
+
``cumulatedDhwFlowRate`` was gallons, while the properties treated them as
|
|
708
|
+
tenths of an LPM and as litres. The properties are right. Navien's
|
|
709
|
+
NaviLink app (2.01.02, decompiled) shows ``currentDhwFlowRate`` as
|
|
710
|
+
``raw / 10`` LPM in Celsius and ``raw / 3.785 / 10`` GPM in Fahrenheit.
|
|
711
|
+
The NWP500 manual lists the panel's water-flow reading in LPM even though
|
|
712
|
+
the panel defaults to GPM. The app's diagnostics screen shows the heater's
|
|
713
|
+
hot-water volume totals in whole litres in either setting. Flow rates are
|
|
714
|
+
0.1 LPM and the cumulative counter is whole litres in both unit systems.
|
|
715
|
+
``device_status.rst``, ``data_conversions.rst`` and ``mqtt_protocol.rst``
|
|
716
|
+
now say so, and ``DiagnosticsDhwUsage`` documents its units (litres, hours,
|
|
717
|
+
minutes, counts) instead of calling them unknown. ``unknown-values.rst``
|
|
718
|
+
notes that on the unit checked, ``cumulatedDhwFlowRate`` and every draw
|
|
719
|
+
counter read 0 after 392 days in use, so a 0 there does not mean no water
|
|
720
|
+
was used. Library output is unchanged. The raw fields no longer carry a
|
|
721
|
+
fixed ``"GPM"`` or ``"gal"`` ``unit_of_measurement``, which contradicted
|
|
722
|
+
their raw units. ``get_field_unit()``, which was already driven by
|
|
723
|
+
``device_class``, returns the same units as before. New tests pin both
|
|
724
|
+
flow properties to the app's formula in each unit system.
|
|
7
725
|
|
|
8
726
|
Version 9.4.3 (2026-10-01)
|
|
9
727
|
==========================
|
|
@@ -54,7 +772,7 @@ Documentation
|
|
|
54
772
|
running element mode; and the operation setting reports the configured
|
|
55
773
|
mode, not activity. The section's first measurement date is corrected to
|
|
56
774
|
local time (2026-09-19).
|
|
57
|
-
- **New: what the heating elements
|
|
775
|
+
- **New: what the heating elements do**
|
|
58
776
|
(``docs/explanation/heating-elements.rst``). The four
|
|
59
777
|
``he*TempSetting`` fields describe the thermostat, and **on entry to
|
|
60
778
|
a mode the thermostat is not what runs the element**: in
|
|
@@ -85,7 +803,7 @@ Documentation
|
|
|
85
803
|
A sampling note with it: the element runs in two very different
|
|
86
804
|
lengths. Of 115 upper-element runs over eight months, 20 lasted under
|
|
87
805
|
a minute (median 16 s), carrying 0.6 kWh against 93 kWh for the rest.
|
|
88
|
-
They are real elements, and not negligible to the probe
|
|
806
|
+
They are real elements, and not negligible to the probe: a 20 s burst
|
|
89
807
|
is ~0.3 degC. Because ``currentPower`` samples at a median 30 s, a
|
|
90
808
|
one-minute-resampled power series can miss one entirely, making a
|
|
91
809
|
5.5 kW element look like 0.5 kW. Resample power with a maximum.
|
|
@@ -97,7 +815,7 @@ Documentation
|
|
|
97
815
|
~0.9 degC under the setpoint-tracking ``heUpperOffTempSetting``, not
|
|
98
816
|
to a fixed point. Raising the setpoint in that mode starts the element
|
|
99
817
|
through the entry rule. And ``heUpperOnTempSetting`` rests at 40.5
|
|
100
|
-
degC reliably only in ``ENERGY_SAVER
|
|
818
|
+
degC reliably only in ``ENERGY_SAVER``; in ``HEAT_PUMP``, about half
|
|
101
819
|
the time.
|
|
102
820
|
|
|
103
821
|
Also documents Energy Saver's entry behaviour (the element engages
|
|
@@ -132,7 +850,7 @@ Documentation
|
|
|
132
850
|
with no exceptions: 104.9 degF on the primary, raised to about the
|
|
133
851
|
setpoint during the afternoon TOU window on the secondary.
|
|
134
852
|
``energy_to_setpoint`` shifts by the same 2 degC. Because of that,
|
|
135
|
-
``usable_energy``
|
|
853
|
+
``usable_energy`` does not always hold up across the branch. In the window it
|
|
136
854
|
caps at ``full_recovery_energy`` once ``energy_to_setpoint`` hits 0,
|
|
137
855
|
up to about 3.6 degF short of the setpoint, and under-reads by up to
|
|
138
856
|
561 Wh. The page and the ``usable_energy`` docstring now say so and
|
|
@@ -175,7 +893,7 @@ Version 9.4.0 (2026-09-12)
|
|
|
175
893
|
==========================
|
|
176
894
|
|
|
177
895
|
This release realigns the MQTT command set with what the NaviLink app
|
|
178
|
-
|
|
896
|
+
sends. The app (version 2.03.00) was decompiled and its request
|
|
179
897
|
builder compared with the library; every query it sends now has a
|
|
180
898
|
method, six commands it never sends were removed, and the recirculation
|
|
181
899
|
schedule write was corrected. Breaking changes are listed under
|
|
@@ -338,8 +1056,8 @@ Fixed
|
|
|
338
1056
|
The cloud returns ``"error": {"errorCode": null}`` on some devices.
|
|
339
1057
|
:class:`~nwp500.models.DeviceErrorSummary` typed the field
|
|
340
1058
|
``ErrorCode | int``, so validation of the whole listing failed and every
|
|
341
|
-
caller
|
|
342
|
-
set up
|
|
1059
|
+
caller (including the Home Assistant integration, which could no longer
|
|
1060
|
+
set up) lost the device entirely. ``error_code`` is now
|
|
343
1061
|
``ErrorCode | int | None`` and defaults to ``None``: a null means the
|
|
344
1062
|
cloud reported no code, which is not the same claim as ``NO_ERROR``.
|
|
345
1063
|
Callers that treated the field as always-present should handle ``None``.
|
|
@@ -376,8 +1094,8 @@ Added
|
|
|
376
1094
|
:class:`~nwp500.models.DeviceInfo` gains ``model_type_code`` and
|
|
377
1095
|
``installer_id``. ``error`` makes the device's last recorded fault
|
|
378
1096
|
readable without an MQTT connection, including while the device is
|
|
379
|
-
offline. Every new field is optional, so responses that omit them
|
|
380
|
-
as ``/device/info``, which carries no ``error`` block
|
|
1097
|
+
offline. Every new field is optional, so responses that omit them (such
|
|
1098
|
+
as ``/device/info``, which carries no ``error`` block) parse unchanged.
|
|
381
1099
|
``error_code`` is typed ``ErrorCode | int`` (validated left to right, so a
|
|
382
1100
|
known code becomes an ``ErrorCode`` member and only an unknown one stays a
|
|
383
1101
|
plain int) so an unrecognised code cannot make a whole listing unparseable. ``docs/openapi.yaml`` is
|
|
@@ -394,17 +1112,17 @@ Removed
|
|
|
394
1112
|
seconds. ``ctrl/tou/rd`` with that command code is the *write* - it is what
|
|
395
1113
|
:meth:`~nwp500.NavienMqttClient.configure_tou_schedule` publishes and what
|
|
396
1114
|
the vendor app publishes from its TOU editor
|
|
397
|
-
(``TouScheduleViewmodel.setPublishMgppControlTou``)
|
|
1115
|
+
(``TouScheduleViewmodel.setPublishMgppControlTou``), and the device answers
|
|
398
1116
|
on ``res/tou/rd`` only to confirm such a write. The vendor app reads TOU
|
|
399
1117
|
over REST, which is the only TOU read the protocol has.
|
|
400
1118
|
|
|
401
1119
|
Beyond returning nothing, the call published a write-shaped command with no
|
|
402
|
-
schedule attached. This device ignored it
|
|
403
|
-
across repeated calls
|
|
1120
|
+
schedule attached. This device ignored it (``touStatus`` was unchanged
|
|
1121
|
+
across repeated calls), but a firmware that took it at face value could read
|
|
404
1122
|
it as "store an empty TOU schedule".
|
|
405
1123
|
|
|
406
1124
|
**Migration**: use :meth:`~nwp500.NavienAPIClient.get_tou_info`, which
|
|
407
|
-
returns the stored plan
|
|
1125
|
+
returns the stored plan: rate name, utility, ZIP code and the seasonal
|
|
408
1126
|
pricing intervals. The read itself is pure REST; it is keyed by the
|
|
409
1127
|
controller serial number, which only the MQTT device-info response
|
|
410
1128
|
publishes, so fetch that once and cache it.
|
|
@@ -420,12 +1138,12 @@ Fixed
|
|
|
420
1138
|
month list, so ``--months 5`` took the single-month branch and printed
|
|
421
1139
|
the same daily breakdown as ``--month 5`` instead of the monthly
|
|
422
1140
|
summary. The handler now takes an explicit ``daily`` flag set by the
|
|
423
|
-
option the user
|
|
1141
|
+
option the user passed. ``energy`` also rejects ``--month``
|
|
424
1142
|
and ``--months`` together rather than silently preferring ``--month``,
|
|
425
1143
|
and validates the ``--months`` range and format the way ``--month``
|
|
426
1144
|
already did. All of that validation now happens while Click parses the
|
|
427
1145
|
arguments, so a bad invocation is a usage error (exit code 2) before any
|
|
428
|
-
authentication or MQTT connection is attempted
|
|
1146
|
+
authentication or MQTT connection is attempted. Previously the command
|
|
429
1147
|
body raised ``ClickException`` after connecting, where ``async_command``'s
|
|
430
1148
|
catch-all reported it as an "Unexpected Error" with a traceback, and
|
|
431
1149
|
anyone without working credentials saw an authentication failure instead
|
|
@@ -445,11 +1163,11 @@ Fixed
|
|
|
445
1163
|
- **``firmware_payload_capture.py`` captured almost nothing.** Its wildcards
|
|
446
1164
|
covered ``cmd/{type}/{client_id}/res/#`` and the event topic, but the device
|
|
447
1165
|
acknowledges control and query commands on
|
|
448
|
-
``cmd/{type}/navilink-{mac}/{client_id}/res
|
|
449
|
-
matches
|
|
1166
|
+
``cmd/{type}/navilink-{mac}/{client_id}/res``, which neither pattern
|
|
1167
|
+
matches, so the device status and device info responses were missed, along
|
|
450
1168
|
with every request published on the device path. A capture run recorded 2
|
|
451
1169
|
payloads where it should have recorded 11. It now also subscribes to
|
|
452
|
-
``cmd/{type}/navilink-{mac}/#``, which
|
|
1170
|
+
``cmd/{type}/navilink-{mac}/#``, which also picks up traffic from
|
|
453
1171
|
other clients on the same device. Query results the device routes back to a
|
|
454
1172
|
client-keyed topic, such as reservations and energy usage, were already
|
|
455
1173
|
covered by the existing response wildcard.
|
|
@@ -459,8 +1177,8 @@ Version 9.3.0 (2026-08-03)
|
|
|
459
1177
|
|
|
460
1178
|
**BREAKING CHANGES**: two independent corrections land together.
|
|
461
1179
|
|
|
462
|
-
Tank energy values were wrong in two ways
|
|
463
|
-
two actively misleading field names
|
|
1180
|
+
Tank energy values were wrong in two ways, a 2.5x unit-scale error and
|
|
1181
|
+
two actively misleading field names, and both are corrected. Reported
|
|
464
1182
|
tank energy is now 2.5x smaller and two public field names are removed.
|
|
465
1183
|
|
|
466
1184
|
Separately, eight status flags change type from ``bool`` to
|
|
@@ -474,10 +1192,10 @@ Changed
|
|
|
474
1192
|
not. Because ``totalEnergyCapacity`` is a whole-tank quantity, its slope
|
|
475
1193
|
against the setpoint measures the quantum with no stratification
|
|
476
1194
|
assumption: on a 65-gallon NWP500 that is 70.25 raw counts per Kelvin.
|
|
477
|
-
The field
|
|
478
|
-
two values exactly 2 degC apart
|
|
1195
|
+
The field is bimodal (at a fixed setpoint it takes one of
|
|
1196
|
+
two values exactly 2 degC apart), but both branches give the same slope
|
|
479
1197
|
to within 0.2%, so the quantum is unaffected. Converting to Watt-hours needs a
|
|
480
|
-
water mass, and a "65 gallon" tank does not hold 65 gallons
|
|
1198
|
+
water mass, and a "65 gallon" tank does not hold 65 gallons, so taking
|
|
481
1199
|
the quantum to be round, as every other conversion in this protocol is,
|
|
482
1200
|
4 Wh/count is the only candidate implying a water volume below the
|
|
483
1201
|
nameplate (241.7 L). Two further checks agree: 183 individual heating
|
|
@@ -489,7 +1207,7 @@ Changed
|
|
|
489
1207
|
be comparable.
|
|
490
1208
|
|
|
491
1209
|
- **Energy fields renamed.**
|
|
492
|
-
``availableEnergyCapacity`` is not available energy
|
|
1210
|
+
``availableEnergyCapacity`` is not available energy: it is the energy
|
|
493
1211
|
still *needed* to reach the setpoint. It falls as the tank heats and
|
|
494
1212
|
reaches zero when the tank is fully charged, so code treating it as
|
|
495
1213
|
stored energy had the signal backwards (regression against mean tank
|
|
@@ -515,7 +1233,7 @@ Changed
|
|
|
515
1233
|
|
|
516
1234
|
- **Status flags now preserve the device's unknown state.** The protocol
|
|
517
1235
|
encodes these flags as ``0 = unknown, 1 = OFF, 2 = ON``, and the library
|
|
518
|
-
was collapsing 0 to ``False
|
|
1236
|
+
was collapsing 0 to ``False``, inventing an OFF the device never claimed.
|
|
519
1237
|
Confirmed against Navien's own NaviLink app (2.03.00, versionCode 141),
|
|
520
1238
|
which decodes exactly this set of fields through an enum declared
|
|
521
1239
|
``UNKNOWN(0), OFF(1), ON(2)``; two sibling enums render their zero as
|
|
@@ -545,7 +1263,7 @@ Added
|
|
|
545
1263
|
fields are measured from the setpoint, so neither is a state of charge;
|
|
546
1264
|
subtracting them cancels the setpoint and leaves the tank's heat above
|
|
547
1265
|
the device's minimum operating temperature (104.9 degF), which is about
|
|
548
|
-
the lowest temperature usable for a shower.
|
|
1266
|
+
the lowest temperature usable for a shower. It holds despite
|
|
549
1267
|
``full_recovery_energy`` being bimodal, since both fields shift
|
|
550
1268
|
together: the implied tank temperature tracks the thermistor mean to a
|
|
551
1269
|
standard deviation of 0.57 degF over 12275 samples. Rendered by the CLI
|
|
@@ -604,13 +1322,13 @@ Fixed
|
|
|
604
1322
|
in the changelog, the model field description and the protocol
|
|
605
1323
|
reference.
|
|
606
1324
|
- New ``docs/explanation/tank-energy.rst`` records what the two fields
|
|
607
|
-
|
|
1325
|
+
measure and the calibration evidence behind the scale change,
|
|
608
1326
|
including that Navien's own NaviLink app (2.03.00, versionCode 141)
|
|
609
1327
|
reads neither field, so no vendor-side corroboration exists.
|
|
610
1328
|
|
|
611
1329
|
- Documented that **temperature fields carry no sentinel at all**. The app
|
|
612
1330
|
has no out-of-band constant (no ``0xFFFF``/``-999``/``-1``), no zero-guard
|
|
613
|
-
in any display path, and formats whatever arrives
|
|
1331
|
+
in any display path, and formats whatever arrives, so a temperature of
|
|
614
1332
|
zero means zero. This closes a recurring source of bugs where zero-as-none
|
|
615
1333
|
was applied to temperature converters and reported working sensors as
|
|
616
1334
|
missing during cold-weather operation.
|
|
@@ -675,7 +1393,7 @@ Documentation
|
|
|
675
1393
|
(``models/schedule.py``, ``models/_converters.py``). Corrected to
|
|
676
1394
|
"target temperature, half-degrees Celsius; °C = param/2", and fixed the
|
|
677
1395
|
accompanying doctest-style example, which showed a ``minute`` key where
|
|
678
|
-
the function
|
|
1396
|
+
the function returns ``min``.
|
|
679
1397
|
|
|
680
1398
|
Version 9.2.0 (2026-07-06)
|
|
681
1399
|
==========================
|
|
@@ -708,7 +1426,7 @@ Changed
|
|
|
708
1426
|
(``mqtt/_control_commands.py``) and ``DeviceSubscriptionsMixin``
|
|
709
1427
|
(``mqtt/_device_subscriptions.py``). ``NavienMqttClient`` now inherits
|
|
710
1428
|
both, so its public API is unchanged, while ``mqtt/client.py`` shrinks
|
|
711
|
-
from ~1572 to ~1196 lines and reads
|
|
1429
|
+
from ~1572 to ~1196 lines and reads as connection
|
|
712
1430
|
orchestration plus a public façade. No behavior change.
|
|
713
1431
|
- **Event system relationship clarified and de-duplicated** (`#102
|
|
714
1432
|
<https://github.com/eman/nwp500-python/issues/102>`_): ``events.py`` and
|
|
@@ -893,8 +1611,8 @@ Bug Fixes
|
|
|
893
1611
|
``configure_recirculation_schedule()`` dumped the schedule models
|
|
894
1612
|
as-is, double-nesting the request (``request.reservation.reservation``
|
|
895
1613
|
/ ``request.schedule.schedule``) and leaking pydantic computed display
|
|
896
|
-
fields
|
|
897
|
-
half-Celsius ``param``
|
|
1614
|
+
fields (including a unit-converted ``temperature`` alongside the raw
|
|
1615
|
+
half-Celsius ``param``) into device commands. Both now send the flat,
|
|
898
1616
|
raw protocol shape used by ``update_reservations()``. A new
|
|
899
1617
|
``NavienBaseModel.to_protocol_dict()`` dumps only declared protocol
|
|
900
1618
|
fields.
|
|
@@ -922,7 +1640,7 @@ Bug Fixes
|
|
|
922
1640
|
so consumers kept displaying the stale error.
|
|
923
1641
|
- **Fix TOU price encoding**: ``encode_price()`` used banker's rounding,
|
|
924
1642
|
under-encoding exact half values at even boundaries (0.125 at
|
|
925
|
-
``decimal_point=2`` encoded to 12 instead of 13)
|
|
1643
|
+
``decimal_point=2`` encoded to 12 instead of 13). It now uses
|
|
926
1644
|
``Decimal`` half-up rounding. ``build_tou_period()`` also treated
|
|
927
1645
|
``bool`` prices as pre-encoded integers (``True`` sent as price 1).
|
|
928
1646
|
- **Fix protocol documentation errors**: ``decode_reservation_hex()``
|
|
@@ -937,7 +1655,7 @@ Bug Fixes
|
|
|
937
1655
|
an undocumented SDK thread where asyncio operations are unsafe, and the
|
|
938
1656
|
handler registry could be mutated on the event loop while the CRT
|
|
939
1657
|
thread iterated it (``RuntimeError: dictionary changed size during
|
|
940
|
-
iteration``, dropping the message
|
|
1658
|
+
iteration``, dropping the message, most likely during the
|
|
941
1659
|
reconnect/resubscribe window). The awscrt callback now only marshals
|
|
942
1660
|
the raw payload onto the event loop; parsing and dispatch run there,
|
|
943
1661
|
iterating snapshots of the handler registries.
|
|
@@ -950,7 +1668,7 @@ Bug Fixes
|
|
|
950
1668
|
``set_unit_system()`` stored the preference in a ``ContextVar`` set in
|
|
951
1669
|
the caller's task. Tasks scheduled from AWS CRT callback threads never
|
|
952
1670
|
inherit that context, so the preference silently reverted to
|
|
953
|
-
auto-detect for all MQTT-delivered data
|
|
1671
|
+
auto-detect for all MQTT-delivered data: ``nwp-cli --unit-system
|
|
954
1672
|
metric monitor`` logged temperatures in the device's native unit while
|
|
955
1673
|
labeling them °C. The preference is now a process-wide setting visible
|
|
956
1674
|
from every task and thread.
|
|
@@ -966,7 +1684,7 @@ Bug Fixes
|
|
|
966
1684
|
``finally`` block.
|
|
967
1685
|
- **Fix wait_for() docstring examples**: examples showed
|
|
968
1686
|
``args, _ = await emitter.wait_for(...)``, but ``wait_for`` returns
|
|
969
|
-
just the args tuple
|
|
1687
|
+
just the args tuple, so following the documented pattern raised
|
|
970
1688
|
``ValueError`` or silently mis-assigned.
|
|
971
1689
|
- **Serialize concurrent token refresh**: ``ensure_valid_token()`` and
|
|
972
1690
|
``refresh_token()`` had no lock, so concurrent callers (API 401 retry,
|
|
@@ -991,7 +1709,7 @@ Bug Fixes
|
|
|
991
1709
|
- **Make** ``NavienAuthClient.__aenter__`` **idempotent**:
|
|
992
1710
|
``create_navien_clients()`` pre-enters the context and its docstring
|
|
993
1711
|
instructs users to enter again with ``async with auth:``; the second
|
|
994
|
-
entry created a fresh session and orphaned the first
|
|
1712
|
+
entry created a fresh session and orphaned the first, which the API
|
|
995
1713
|
client was still pinned to. Re-entering now reuses the existing
|
|
996
1714
|
session.
|
|
997
1715
|
- **Fall back to full sign-in when stored-token refresh fails**:
|
|
@@ -1100,7 +1818,7 @@ Bug Fixes
|
|
|
1100
1818
|
(e.g. the sleeping task's timer callback had already been enqueued), the
|
|
1101
1819
|
cancellation was silently dropped. The stale ``_reconnect_with_backoff`` task
|
|
1102
1820
|
would then complete its sleep, call ``_reconnect_func``, and tear down an
|
|
1103
|
-
otherwise healthy connection
|
|
1821
|
+
otherwise healthy connection, restarting the entire disconnect → reconnect →
|
|
1104
1822
|
``AWS_ERROR_MQTT_UNEXPECTED_HANGUP`` cycle. Fixed by replacing the direct
|
|
1105
1823
|
``task.cancel()`` call with a ``_cancel_pending_reconnect()`` coroutine scheduled
|
|
1106
1824
|
via ``_schedule_coroutine``, so the cancellation runs on the event loop where
|
|
@@ -1118,7 +1836,7 @@ Bug Fixes
|
|
|
1118
1836
|
SDK connection's built-in auto-reconnect would eventually succeed, creating two
|
|
1119
1837
|
active connections sharing the same client ID. Because AWS IoT allows only one
|
|
1120
1838
|
connection per client ID, the broker would kick one off, triggering
|
|
1121
|
-
``on_connection_interrupted`` and starting yet another reconnection
|
|
1839
|
+
``on_connection_interrupted`` and starting yet another reconnection: an
|
|
1122
1840
|
infinite connect/disconnect loop. Fixed by adding ``MqttConnection.close()``
|
|
1123
1841
|
(unconditional teardown regardless of ``_connected`` state) and calling it
|
|
1124
1842
|
before creating the replacement connection in both ``_active_reconnect()`` and
|
|
@@ -1150,9 +1868,9 @@ Bug Fixes
|
|
|
1150
1868
|
a different loop in multi-loop test setups.
|
|
1151
1869
|
|
|
1152
1870
|
- **Reservation temperature validation was US-only**: ``build_reservation_entry``
|
|
1153
|
-
validated set-point temperatures against hardcoded Fahrenheit bounds (95
|
|
1871
|
+
validated set-point temperatures against hardcoded Fahrenheit bounds (95-150 °F)
|
|
1154
1872
|
regardless of the active unit system. Validation now uses the current unit system
|
|
1155
|
-
context: 35
|
|
1873
|
+
context: 35-65 °C in metric mode, 95-150 °F in US mode. Celsius users previously
|
|
1156
1874
|
received spurious ``ValueError`` rejections for valid temperatures.
|
|
1157
1875
|
|
|
1158
1876
|
- **Malformed reservation data silently dropped**: ``build_reservation_entry`` now
|
|
@@ -1240,11 +1958,11 @@ integrations like Home Assistant:
|
|
|
1240
1958
|
2. **Refactor Control Calls**: Remove ``.control`` from all device command invocations.
|
|
1241
1959
|
Instead of ``await mqtt.control.set_power(...)``, use ``await mqtt.set_power(...)``.
|
|
1242
1960
|
3. **Handle Unit Conversions**: If your integration previously performed its own
|
|
1243
|
-
conversions or relied on the library's eager conversion
|
|
1961
|
+
conversions or relied on the library's eager conversion:
|
|
1244
1962
|
``DeviceStatus`` fields like ``dhw_temperature`` are now properties. They return
|
|
1245
1963
|
values based on the global unit system context (``us_customary`` by default).
|
|
1246
1964
|
|
|
1247
|
-
* **Home Assistant Tip**: To
|
|
1965
|
+
* **Home Assistant Tip**: To keep your state tracking immune to unit system
|
|
1248
1966
|
toggles within the library, use the new ``*_raw`` fields (e.g.,
|
|
1249
1967
|
``status.dhw_temperature_raw``) for comparison logic, and use the properties
|
|
1250
1968
|
only for display or when a converted value is explicitly needed.
|
|
@@ -1329,7 +2047,7 @@ Fixed
|
|
|
1329
2047
|
``are_aws_credentials_expired``, and ``time_until_expiry`` used
|
|
1330
2048
|
``datetime.now()`` (naive, local time). During DST transitions or timezone
|
|
1331
2049
|
changes this could cause incorrect expiry detection, leading to premature
|
|
1332
|
-
re-authentication or use of an
|
|
2050
|
+
re-authentication or use of an expired token. Fixed by using
|
|
1333
2051
|
``datetime.now(UTC)`` throughout, switching the ``issued_at`` field default
|
|
1334
2052
|
to ``datetime.now(UTC)``, and adding a field validator to normalize any
|
|
1335
2053
|
timezone-naive ``issued_at`` values loaded from old stored token files to UTC
|
|
@@ -1339,11 +2057,11 @@ Fixed
|
|
|
1339
2057
|
format written by ``to_dict()`` for tokens stored before this fix.
|
|
1340
2058
|
- **Vacation mode sent wrong MQTT command**: ``set_vacation_days()`` used
|
|
1341
2059
|
``CommandCode.GOOUT_DAY`` (33554466), which the device silently accepted
|
|
1342
|
-
but did not activate vacation mode
|
|
2060
|
+
but did not activate vacation mode; the operating mode remained unchanged.
|
|
1343
2061
|
HAR capture of the official Navien app confirms the correct command is
|
|
1344
2062
|
``DHW_MODE`` (33554437) with ``param=[5, days]``
|
|
1345
2063
|
(``DhwOperationSetting.VACATION``). The valid range has also been corrected
|
|
1346
|
-
from 1
|
|
2064
|
+
from 1-365 to 1-30 to match the device's actual constraint.
|
|
1347
2065
|
- **Duplicate AWS IoT subscribe calls on reconnect**: ``resubscribe_all()``
|
|
1348
2066
|
called ``connection.subscribe()`` (a network round-trip to AWS IoT) once per
|
|
1349
2067
|
handler per topic. If a topic had N handlers, N identical subscribe requests
|
|
@@ -1366,7 +2084,7 @@ Fixed
|
|
|
1366
2084
|
- **Once-Listener Becomes Permanent With Duplicate Callbacks**: ``emit()``
|
|
1367
2085
|
identified once-listeners via a ``set`` of ``(event, callback)`` tuples. If
|
|
1368
2086
|
the same callback was registered twice with ``once=True``, the set
|
|
1369
|
-
deduplicated the tuple
|
|
2087
|
+
deduplicated the tuple: after the first emit the second listener lost its
|
|
1370
2088
|
once-status and became permanent. Fixed by checking ``listener.once``
|
|
1371
2089
|
directly on the ``EventListener`` object.
|
|
1372
2090
|
- **Auth Session Leaked on Client Construction Failure**: In
|
|
@@ -1375,10 +2093,10 @@ Fixed
|
|
|
1375
2093
|
``auth_client.__aenter__()``, the auth session and its underlying
|
|
1376
2094
|
``aiohttp`` session would leak. Client construction is now wrapped in a
|
|
1377
2095
|
``try/except`` that calls ``auth_client.__aexit__()`` on failure.
|
|
1378
|
-
|
|
2096
|
+
Both ``except BaseException`` blocks have been replaced with
|
|
1379
2097
|
``except Exception`` (passing real exception info to ``__aexit__``) plus a
|
|
1380
2098
|
separate ``except asyncio.CancelledError`` block that uses
|
|
1381
|
-
``asyncio.shield()``
|
|
2099
|
+
``asyncio.shield()`` so cleanup completes even when the task is
|
|
1382
2100
|
being cancelled.
|
|
1383
2101
|
- **Hypothesis Tests Broke All Test Collection**: ``test_mqtt_hypothesis.py``
|
|
1384
2102
|
imported ``hypothesis`` at module level; when it was not installed, pytest
|
|
@@ -1427,7 +2145,7 @@ Changed
|
|
|
1427
2145
|
|
|
1428
2146
|
Fixed
|
|
1429
2147
|
-----
|
|
1430
|
-
- **Week Bitfield Encoding
|
|
2148
|
+
- **Week Bitfield Encoding**: Fixed MGPP week bitfield encoding to match NaviLink APK protocol. Sunday is now correctly bit 7 (128), Monday bit 6 (64), ..., Saturday bit 1 (2); bit 0 is unused. Affects all reservation and TOU schedule operations. Verified against reference captures.
|
|
1431
2149
|
- **Enable/Disable Convention**: Fixed reservation and TOU enable/disable flags to use standard device boolean convention (1=OFF, 2=ON) instead of inverted logic. This aligns with other device binary sensors and matches app behavior. Global reservation status now correctly shows DISABLED when ``reservationUse=1``.
|
|
1432
2150
|
- **Reservation Set Command Timeout**: Fixed ``reservations set`` subscription pattern that had extra wildcards preventing response matching. Command now receives confirmations correctly.
|
|
1433
2151
|
- **Intermittent Fetch Bug**: Tightened MQTT topic filter for reservation fetch from ``/res/`` to ``/res/rsv/`` with content validation to prevent false matches on unrelated response messages.
|
|
@@ -1567,13 +2285,13 @@ Changed
|
|
|
1567
2285
|
|
|
1568
2286
|
Fixed
|
|
1569
2287
|
-----
|
|
1570
|
-
- **
|
|
2288
|
+
- **Temperature Unit Bug in Set Operations**: Fixed incorrect temperature conversion when setting DHW temperature and reservations
|
|
1571
2289
|
|
|
1572
2290
|
- ``set_dhw_temperature()`` was calling ``fahrenheit_to_half_celsius()`` with unit-agnostic temperature parameter
|
|
1573
2291
|
- ``build_reservation_entry()`` had the same issue
|
|
1574
2292
|
- **Impact**: If user preferred Celsius, temperature would be interpreted as Fahrenheit, causing wrong setpoints
|
|
1575
2293
|
- **Fix**: Use new ``preferred_to_half_celsius()`` that respects unit system context
|
|
1576
|
-
- This was a
|
|
2294
|
+
- This was a data correctness bug that would cause incorrect device behavior for Celsius users
|
|
1577
2295
|
|
|
1578
2296
|
Version 7.3.2 (2026-01-25)
|
|
1579
2297
|
==========================
|
|
@@ -1623,7 +2341,7 @@ Added
|
|
|
1623
2341
|
- Available devices and regions determine conversion support (validated at runtime)
|
|
1624
2342
|
- ``get_field_unit()`` method retrieves the correct unit suffix for any field
|
|
1625
2343
|
- All conversions use Pydantic ``WrapValidator`` for transparent, automatic conversion
|
|
1626
|
-
-
|
|
2344
|
+
- Documentation in ``docs/guides/unit_conversion.rst``
|
|
1627
2345
|
- Example usage:
|
|
1628
2346
|
|
|
1629
2347
|
.. code-block:: python
|
|
@@ -1678,7 +2396,7 @@ Fixed
|
|
|
1678
2396
|
|
|
1679
2397
|
- **Issue**: Running ``nwp-cli --unit-system metric status`` displayed values in device's native format (imperial) with metric unit strings (e.g., "104.9 °C" instead of "40.5 °C")
|
|
1680
2398
|
- **Root Cause**: MQTT callbacks from AWS CRT execute on different threads where context variables are not set. Since Python's context variables are task-local, the unit_system preference was not visible to validators
|
|
1681
|
-
- **Solution**: Store unit_system in ``NavienMqttClient`` and ``MqttSubscriptionManager``. Before parsing MQTT messages, explicitly set the context variable in message handlers
|
|
2399
|
+
- **Solution**: Store unit_system in ``NavienMqttClient`` and ``MqttSubscriptionManager``. Before parsing MQTT messages, explicitly set the context variable in message handlers so validators use the correct unit system regardless of thread context
|
|
1682
2400
|
- **Result**: Values and units now correctly convert when ``--unit-system`` override is specified
|
|
1683
2401
|
- **Testing**: All 393 tests pass including new unit system context override tests
|
|
1684
2402
|
|
|
@@ -1840,7 +2558,7 @@ Removed
|
|
|
1840
2558
|
- ``DeviceCapabilityChecker`` → ``MqttDeviceCapabilityChecker``
|
|
1841
2559
|
- ``DeviceInfoCache`` → ``MqttDeviceInfoCache``
|
|
1842
2560
|
|
|
1843
|
-
All functionality remains identical
|
|
2561
|
+
All functionality remains identical; only the class names changed.
|
|
1844
2562
|
|
|
1845
2563
|
Added
|
|
1846
2564
|
-----
|
|
@@ -1861,7 +2579,7 @@ Added
|
|
|
1861
2579
|
- Simplified initialization for common use case (API + MQTT)
|
|
1862
2580
|
- Proper async context manager support
|
|
1863
2581
|
- Reduces boilerplate in application code
|
|
1864
|
-
-
|
|
2582
|
+
- Documentation in ``docs/guides/authentication.rst``
|
|
1865
2583
|
- Example: ``examples/intermediate/advanced_auth_patterns.py``
|
|
1866
2584
|
|
|
1867
2585
|
- **VolumeCode Enum**: Tank capacity identification with gallon values
|
|
@@ -1896,7 +2614,7 @@ Added
|
|
|
1896
2614
|
- ``tou_override_to_python()``: TOU override status conversion
|
|
1897
2615
|
- ``div_10()``: Divide by 10.0 utility
|
|
1898
2616
|
- ``enum_validator()``: Generic enum factory
|
|
1899
|
-
-
|
|
2617
|
+
- Documentation explaining device protocol quirks
|
|
1900
2618
|
- New ``converters.py`` module replacing scattered validators
|
|
1901
2619
|
|
|
1902
2620
|
- **MQTT Event System**: Structured event handling for MQTT operations
|
|
@@ -1922,7 +2640,7 @@ Added
|
|
|
1922
2640
|
- Eliminates hardcoded units in output formatter
|
|
1923
2641
|
- Single source of truth: model metadata drives CLI display
|
|
1924
2642
|
|
|
1925
|
-
- **
|
|
2643
|
+
- **Protocol Documentation**: Complete protocol reference documentation
|
|
1926
2644
|
|
|
1927
2645
|
- New ``docs/protocol/quick_reference.rst`` with command codes, field formats, and conversions
|
|
1928
2646
|
- Converted protocol documentation to RST format for Sphinx integration
|
|
@@ -1988,7 +2706,7 @@ Changed
|
|
|
1988
2706
|
- Added factory function patterns and examples
|
|
1989
2707
|
- Improved context manager documentation
|
|
1990
2708
|
- Added best practices and common patterns
|
|
1991
|
-
- More
|
|
2709
|
+
- More code examples
|
|
1992
2710
|
|
|
1993
2711
|
- **Model Refactoring**: Updated to use new converter modules
|
|
1994
2712
|
|
|
@@ -2068,7 +2786,7 @@ Added
|
|
|
2068
2786
|
- ``configure_reservation_water_program()``: Water program reservation management
|
|
2069
2787
|
- ``set_recirculation_mode()`` / ``configure_recirculation_schedule()`` / ``trigger_recirculation_hot_button()``: Recirculation pump control and scheduling
|
|
2070
2788
|
|
|
2071
|
-
- **CLI Documentation Updates**:
|
|
2789
|
+
- **CLI Documentation Updates**: Documentation updates for subcommand-based CLI
|
|
2072
2790
|
- Complete rewrite of ``docs/python_api/cli.rst`` with full command reference
|
|
2073
2791
|
- Updated README.rst with new subcommand syntax and examples
|
|
2074
2792
|
- Added 8+ practical usage examples (cron jobs, automation, monitoring)
|
|
@@ -2151,14 +2869,14 @@ Removed
|
|
|
2151
2869
|
Added
|
|
2152
2870
|
-----
|
|
2153
2871
|
|
|
2154
|
-
- **Python 3.12+ Optimizations**:
|
|
2872
|
+
- **Python 3.12+ Optimizations**: Use the latest Python features
|
|
2155
2873
|
|
|
2156
2874
|
- PEP 695: New type parameter syntax (``def func[T](...)`` instead of ``TypeVar``)
|
|
2157
2875
|
- Use ``datetime.UTC`` constant instead of ``datetime.timezone.utc``
|
|
2158
2876
|
- Native union syntax (``X | Y`` instead of ``Union[X, Y]``)
|
|
2159
2877
|
- Cleaner generic type annotations throughout codebase
|
|
2160
2878
|
|
|
2161
|
-
- **Enumerations Module (``src/nwp500/enums.py``)**:
|
|
2879
|
+
- **Enumerations Module (``src/nwp500/enums.py``)**: Type-safe enums for device control and status
|
|
2162
2880
|
|
|
2163
2881
|
- Status value enums: ``OnOffFlag``, ``Operation``, ``DhwOperationSetting``, ``CurrentOperationMode``, ``HeatSource``, ``DREvent``, ``WaterLevel``, ``FilterChange``, ``RecirculationMode``
|
|
2164
2882
|
- Time of Use enums: ``TouWeekType``, ``TouRateType``
|
|
@@ -2167,7 +2885,7 @@ Added
|
|
|
2167
2885
|
- Error code enum: ``ErrorCode`` with complete error code mappings
|
|
2168
2886
|
- Human-readable text mappings for all enums (e.g., ``DHW_OPERATION_SETTING_TEXT``, ``ERROR_CODE_TEXT``)
|
|
2169
2887
|
- Exported from main package: ``from nwp500 import OnOffFlag, ErrorCode, CommandCode``
|
|
2170
|
-
-
|
|
2888
|
+
- Documentation in ``docs/enumerations.rst``
|
|
2171
2889
|
- Example usage in ``examples/error_code_demo.py``
|
|
2172
2890
|
|
|
2173
2891
|
Changed
|
|
@@ -2194,7 +2912,7 @@ Changed
|
|
|
2194
2912
|
- CLI and other consumers benefit from automatic enum name serialization
|
|
2195
2913
|
- Text mappings available for custom formatting (e.g., `DHW_OPERATION_TEXT[enum]` → "Heat Pump Only")
|
|
2196
2914
|
|
|
2197
|
-
- **Documentation**:
|
|
2915
|
+
- **Documentation**: Updates across protocol and API documentation
|
|
2198
2916
|
|
|
2199
2917
|
- ``docs/guides/time_of_use.rst``: Clarified TOU override status behavior (1=OFF/override active, 2=ON/normal operation)
|
|
2200
2918
|
- ``docs/protocol/data_conversions.rst``: Updated TOU field descriptions with correct enum values
|
|
@@ -2235,7 +2953,7 @@ Added
|
|
|
2235
2953
|
- Export diagnostics to JSON for analysis and debugging
|
|
2236
2954
|
- Home Assistant integration support for custom components
|
|
2237
2955
|
- ``examples/mqtt_diagnostics_example.py`` demonstrating usage patterns
|
|
2238
|
-
-
|
|
2956
|
+
- Documentation in ``docs/MQTT_DIAGNOSTICS.rst``
|
|
2239
2957
|
- Exported from main package: ``from nwp500 import MqttDiagnosticsCollector``
|
|
2240
2958
|
|
|
2241
2959
|
Version 6.1.0 (2025-12-03)
|
|
@@ -2323,7 +3041,7 @@ Version 6.0.6 (2025-11-24)
|
|
|
2323
3041
|
Added
|
|
2324
3042
|
-----
|
|
2325
3043
|
|
|
2326
|
-
- **Field Descriptions**: Added
|
|
3044
|
+
- **Field Descriptions**: Added Field descriptions to ``DeviceStatus`` and ``DeviceFeature`` models with full documentation details including units, ranges, and usage context
|
|
2327
3045
|
|
|
2328
3046
|
Fixed
|
|
2329
3047
|
-----
|
|
@@ -2341,7 +3059,7 @@ Version 6.0.5 (2025-11-21)
|
|
|
2341
3059
|
Fixed
|
|
2342
3060
|
-----
|
|
2343
3061
|
|
|
2344
|
-
- **
|
|
3062
|
+
- **Temperature Conversion Bug**: Corrected temperature conversion formula for 8 sensor fields that were displaying values ~100°F higher than expected. The v6.0.4 change incorrectly used division by 5 (pentacelsius) instead of division by 10 (decicelsius) for these fields:
|
|
2345
3063
|
|
|
2346
3064
|
- ``tank_upper_temperature`` - Water tank upper sensor
|
|
2347
3065
|
- ``tank_lower_temperature`` - Water tank lower sensor
|
|
@@ -2568,7 +3286,7 @@ Fixed
|
|
|
2568
3286
|
- Applied consistent cancellation handling across all MQTT operations (connect, disconnect, subscribe, unsubscribe, publish)
|
|
2569
3287
|
- AWS CRT callbacks can now complete independently without raising InvalidStateError
|
|
2570
3288
|
- Added debug logging when operations are cancelled for better diagnostics
|
|
2571
|
-
-
|
|
3289
|
+
- Clean shutdown without spurious exception messages
|
|
2572
3290
|
|
|
2573
3291
|
Version 5.0.1 (2025-10-27)
|
|
2574
3292
|
==========================
|
|
@@ -2591,7 +3309,7 @@ Changed
|
|
|
2591
3309
|
Version 5.0.0 (2025-10-27)
|
|
2592
3310
|
==========================
|
|
2593
3311
|
|
|
2594
|
-
**BREAKING CHANGES**: This release introduces
|
|
3312
|
+
**BREAKING CHANGES**: This release introduces an enterprise exception architecture.
|
|
2595
3313
|
See migration guide below for details on updating your code.
|
|
2596
3314
|
|
|
2597
3315
|
Added
|
|
@@ -2599,7 +3317,7 @@ Added
|
|
|
2599
3317
|
|
|
2600
3318
|
- **Enterprise Exception Architecture**: Complete exception hierarchy for better error handling
|
|
2601
3319
|
|
|
2602
|
-
- Created ``exceptions.py`` module with
|
|
3320
|
+
- Created ``exceptions.py`` module with an exception hierarchy
|
|
2603
3321
|
- Added ``Nwp500Error`` as base exception for all library errors
|
|
2604
3322
|
- Added MQTT-specific exceptions: ``MqttError``, ``MqttConnectionError``, ``MqttNotConnectedError``,
|
|
2605
3323
|
``MqttPublishError``, ``MqttSubscriptionError``, ``MqttCredentialsError``
|
|
@@ -2608,9 +3326,9 @@ Added
|
|
|
2608
3326
|
``DeviceOperationError``
|
|
2609
3327
|
- All exceptions now include ``error_code``, ``details``, and ``retriable`` attributes
|
|
2610
3328
|
- Added ``to_dict()`` method to all exceptions for structured logging
|
|
2611
|
-
- Added
|
|
2612
|
-
- **Added
|
|
2613
|
-
- **Updated
|
|
3329
|
+
- Added a test suite in ``tests/test_exceptions.py``
|
|
3330
|
+
- **Added an exception handling example** (``examples/exception_handling_example.py``)
|
|
3331
|
+
- **Updated the main examples** to demonstrate new exception handling patterns
|
|
2614
3332
|
|
|
2615
3333
|
Changed
|
|
2616
3334
|
-------
|
|
@@ -2857,7 +3575,7 @@ Fixed
|
|
|
2857
3575
|
- Preserves AWS credential expiration timestamps during token refresh
|
|
2858
3576
|
- Prevents reconnection failures when connection interrupts after AWS credentials expire but before JWT tokens expire
|
|
2859
3577
|
- Resolves AWS_ERROR_HTTP_WEBSOCKET_UPGRADE_FAILURE errors during reconnection attempts
|
|
2860
|
-
- Improved test coverage for auth module from 31% to 60%
|
|
3578
|
+
- Improved test coverage for auth module from 31% to 60%
|
|
2861
3579
|
|
|
2862
3580
|
Version 3.1.3 (2025-10-24)
|
|
2863
3581
|
==========================
|
|
@@ -2905,7 +3623,7 @@ Fixed
|
|
|
2905
3623
|
|
|
2906
3624
|
- Updated callback to match latest AWS IoT SDK signature: ``(connection, error, **kwargs)``
|
|
2907
3625
|
- Fixed type annotations in ``MqttConnection`` for proper type checking
|
|
2908
|
-
- Resolves mypy type checking errors and
|
|
3626
|
+
- Resolves mypy type checking errors and keeps AWS SDK compatibility
|
|
2909
3627
|
- Fixed E501 line length linting issue in connection interruption handler
|
|
2910
3628
|
|
|
2911
3629
|
Version 3.0.0 (Unreleased)
|
|
@@ -2949,7 +3667,7 @@ Added
|
|
|
2949
3667
|
- Prevents accidental comparison of user preferences with real-time states
|
|
2950
3668
|
- Better IDE support with more specific enum types
|
|
2951
3669
|
|
|
2952
|
-
- **Migration Support**:
|
|
3670
|
+
- **Migration Support**: Tools for migration
|
|
2953
3671
|
|
|
2954
3672
|
- ``migrate_operation_mode_usage()`` helper function with programmatic guidance
|
|
2955
3673
|
- ``MIGRATION.md`` with step-by-step migration instructions
|
|
@@ -3001,12 +3719,12 @@ Version 0.2 (Unreleased)
|
|
|
3001
3719
|
Added
|
|
3002
3720
|
-----
|
|
3003
3721
|
|
|
3004
|
-
- **Local/CI Linting Synchronization**:
|
|
3722
|
+
- **Local/CI Linting Synchronization**: Tooling for consistent linting results
|
|
3005
3723
|
|
|
3006
3724
|
- Multiple sync methods: tox (recommended), direct scripts, pre-commit hooks, Makefile commands
|
|
3007
3725
|
- CI-identical scripts: ``scripts/lint.py`` and ``scripts/format.py`` mirror ``tox -e lint`` and ``tox -e format``
|
|
3008
3726
|
- Pre-commit hooks configuration for automatic checking
|
|
3009
|
-
-
|
|
3727
|
+
- Documentation: ``LINTING_SETUP.md``, ``DEVELOPMENT.md``, ``FIX_LINTING.md``
|
|
3010
3728
|
- Makefile commands: ``make ci-lint``, ``make ci-format``, ``make ci-check``
|
|
3011
3729
|
- Standardized ruff configuration across all environments
|
|
3012
3730
|
- Eliminates "passes locally but fails in CI" issues
|
|
@@ -3057,7 +3775,7 @@ Added
|
|
|
3057
3775
|
- Enabled by default for reliability
|
|
3058
3776
|
- ``queued_commands_count`` property for monitoring
|
|
3059
3777
|
- ``clear_command_queue()`` method for manual management
|
|
3060
|
-
-
|
|
3778
|
+
- Works with automatic reconnection
|
|
3061
3779
|
- Example: ``command_queue_demo.py``
|
|
3062
3780
|
- Documentation: ``COMMAND_QUEUE.rst``
|
|
3063
3781
|
|
|
@@ -3096,7 +3814,7 @@ Added
|
|
|
3096
3814
|
- ``subscribe_device_feature()`` - Automatic parsing of feature messages into ``DeviceFeature`` objects
|
|
3097
3815
|
- ``subscribe_energy_usage()`` - Automatic parsing of energy usage responses into ``EnergyUsageResponse`` objects
|
|
3098
3816
|
- Type-safe callbacks with IDE autocomplete support
|
|
3099
|
-
-
|
|
3817
|
+
- Error handling and logging
|
|
3100
3818
|
- Example scripts demonstrating usage patterns
|
|
3101
3819
|
|
|
3102
3820
|
- **Energy Usage API (EMS)**: Historical energy consumption data
|
|
@@ -3111,7 +3829,7 @@ Added
|
|
|
3111
3829
|
- Energy consumption data (Watt-hours)
|
|
3112
3830
|
- Efficiency percentage calculations
|
|
3113
3831
|
|
|
3114
|
-
- **Data Models**:
|
|
3832
|
+
- **Data Models**: Type-safe models
|
|
3115
3833
|
|
|
3116
3834
|
- ``DeviceStatus`` dataclass with 125 sensor and operational fields
|
|
3117
3835
|
- ``DeviceFeature`` dataclass with 46 capability and configuration fields
|
|
@@ -3150,7 +3868,7 @@ Added
|
|
|
3150
3868
|
- Typed callbacks implementation guide
|
|
3151
3869
|
- Control command reference with verified command codes
|
|
3152
3870
|
- Example scripts for common use cases
|
|
3153
|
-
-
|
|
3871
|
+
- Troubleshooting guides
|
|
3154
3872
|
- Complete energy data reference (ENERGY_DATA_SUMMARY.md)
|
|
3155
3873
|
|
|
3156
3874
|
- **Examples**: Production-ready example scripts
|
|
@@ -3192,14 +3910,14 @@ Changed
|
|
|
3192
3910
|
- Removed "Optional Feature" and "not required for basic operation" phrases
|
|
3193
3911
|
- Fixed table rendering in DEVICE_STATUS_FIELDS.rst
|
|
3194
3912
|
- Fixed JSON syntax in code examples
|
|
3195
|
-
- Added
|
|
3196
|
-
- Added
|
|
3913
|
+
- Added reconnection documentation
|
|
3914
|
+
- Added command queue documentation
|
|
3197
3915
|
- Cleaned up backward compatibility references (new library)
|
|
3198
3916
|
|
|
3199
3917
|
Fixed
|
|
3200
3918
|
-----
|
|
3201
3919
|
|
|
3202
|
-
- **
|
|
3920
|
+
- **Bug**: Thread-safe reconnection task creation from MQTT callbacks
|
|
3203
3921
|
|
|
3204
3922
|
- Fixed ``RuntimeError: no running event loop`` when connection is interrupted
|
|
3205
3923
|
- Fixed ``RuntimeWarning: coroutine '_reconnect_with_backoff' was never awaited``
|
|
@@ -3207,9 +3925,9 @@ Fixed
|
|
|
3207
3925
|
- Implemented ``_start_reconnect_task()`` helper method to properly create reconnection tasks
|
|
3208
3926
|
- Uses existing ``_schedule_coroutine()`` method for thread-safe task scheduling
|
|
3209
3927
|
- Prevents crashes during automatic reconnection after connection interruptions
|
|
3210
|
-
-
|
|
3928
|
+
- Reconnection tasks are awaited and executed
|
|
3211
3929
|
|
|
3212
|
-
- **
|
|
3930
|
+
- **Bug**: Thread-safe event emission from MQTT callbacks
|
|
3213
3931
|
|
|
3214
3932
|
- Fixed ``RuntimeError: no running event loop in thread 'Dummy-1'``
|
|
3215
3933
|
- MQTT callbacks run in separate threads created by AWS IoT SDK
|
|
@@ -3234,7 +3952,7 @@ Fixed
|
|
|
3234
3952
|
- Updated built HTML documentation files
|
|
3235
3953
|
- Protects privacy in public documentation
|
|
3236
3954
|
|
|
3237
|
-
- **
|
|
3955
|
+
- **Bug**: Device control command codes
|
|
3238
3956
|
|
|
3239
3957
|
- Fixed incorrect command code usage causing unintended power-off
|
|
3240
3958
|
- Power-off now uses command code ``33554433``
|
|
@@ -3242,7 +3960,7 @@ Fixed
|
|
|
3242
3960
|
- DHW mode control now uses command code ``33554437``
|
|
3243
3961
|
- Discovered through network traffic analysis of official app
|
|
3244
3962
|
|
|
3245
|
-
- **
|
|
3963
|
+
- **Bug**: MQTT topic pattern matching with wildcards
|
|
3246
3964
|
|
|
3247
3965
|
- Fixed ``_topic_matches_pattern()`` to correctly handle ``#`` wildcard
|
|
3248
3966
|
- Topics now match when message arrives on base topic (e.g., ``cmd/52/device/res``)
|
|
@@ -3262,9 +3980,9 @@ Fixed
|
|
|
3262
3980
|
- Examples now subscribe to broader topic patterns
|
|
3263
3981
|
- Subscribe to ``cmd/{device_type}/{device_topic}/#`` to catch all command messages
|
|
3264
3982
|
- Subscribe to ``evt/{device_type}/{device_topic}/#`` to catch all event messages
|
|
3265
|
-
-
|
|
3983
|
+
- All device responses are received
|
|
3266
3984
|
|
|
3267
|
-
- **Enhancement**:
|
|
3985
|
+
- **Enhancement**: Enum conversion with fallbacks
|
|
3268
3986
|
|
|
3269
3987
|
- Added try/except blocks for all enum conversions in ``DeviceStatus.from_dict()``
|
|
3270
3988
|
- Added try/except blocks for all enum conversions in ``DeviceFeature.from_dict()``
|