nwp500-python 9.4.2__tar.gz → 9.5.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/CHANGELOG.rst +929 -89
- {nwp500_python-9.4.2/src/nwp500_python.egg-info → nwp500_python-9.5.0}/PKG-INFO +50 -16
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/README.rst +37 -15
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/docs/explanation/architecture.rst +3 -3
- nwp500_python-9.5.0/docs/explanation/draws.rst +292 -0
- nwp500_python-9.5.0/docs/explanation/heating-elements.rst +558 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/docs/explanation/index.rst +6 -1
- nwp500_python-9.5.0/docs/explanation/recovery-speed.rst +339 -0
- nwp500_python-9.5.0/docs/explanation/simulator.rst +401 -0
- nwp500_python-9.5.0/docs/explanation/standby-loss.rst +237 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/docs/explanation/tank-energy.rst +113 -72
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/docs/explanation/tou-recovery-cap.rst +38 -29
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/docs/explanation/unknown-values.rst +37 -32
- nwp500_python-9.5.0/docs/explanation/what-starts-a-recovery.rst +340 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/docs/how-to/authenticate.rst +35 -2
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/docs/how-to/auto-recovery.rst +2 -2
- nwp500_python-9.5.0/docs/how-to/bluetooth.rst +329 -0
- nwp500_python-9.5.0/docs/how-to/calibrate-simulator.rst +214 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/docs/how-to/diagnose-mqtt.rst +54 -29
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/docs/how-to/home-assistant.rst +7 -7
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/docs/how-to/index.rst +3 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/docs/how-to/maintenance.rst +63 -16
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/docs/how-to/manage-units.rst +19 -20
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/docs/how-to/monitor-status.rst +9 -3
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/docs/how-to/optimize-tou.rst +14 -14
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/docs/how-to/queue-commands.rst +20 -20
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/docs/how-to/schedule-operation.rst +202 -86
- nwp500_python-9.5.0/docs/how-to/simulate-heater.rst +1174 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/docs/how-to/track-energy.rst +44 -31
- nwp500_python-9.5.0/docs/openapi.yaml +1658 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/docs/project/history.rst +161 -108
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/docs/reference/configuration.rst +12 -12
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/docs/reference/enumerations.rst +41 -16
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/docs/reference/index.rst +2 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/docs/reference/installation.rst +18 -12
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/docs/reference/protocol/data_conversions.rst +101 -44
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/docs/reference/protocol/device_features.rst +44 -45
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/docs/reference/protocol/device_status.rst +71 -53
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/docs/reference/protocol/error_codes.rst +6 -6
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/docs/reference/protocol/mqtt_protocol.rst +167 -43
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/docs/reference/protocol/quick_reference.rst +4 -2
- nwp500_python-9.5.0/docs/reference/protocol/rest_api.rst +996 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/docs/reference/python_api/api_client.rst +311 -20
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/docs/reference/python_api/auth_client.rst +76 -10
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/docs/reference/python_api/cli.rst +20 -20
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/docs/reference/python_api/events.rst +87 -21
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/docs/reference/python_api/exceptions.rst +28 -28
- nwp500_python-9.5.0/docs/reference/python_api/models.rst +1053 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/docs/reference/python_api/mqtt_client.rst +157 -26
- nwp500_python-9.5.0/docs/reference/python_api/sim.rst +309 -0
- nwp500_python-9.5.0/docs/reference/simulator-inputs.rst +1010 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/docs/requirements.txt +3 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/docs/tutorials/getting-started.rst +6 -6
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/examples/README.md +17 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/examples/advanced/recirculation_control.py +8 -6
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/examples/intermediate/advanced_auth_patterns.py +2 -2
- nwp500_python-9.5.0/examples/simulator/lower-setpoint.json +8 -0
- nwp500_python-9.5.0/examples/simulator/plan.json +9 -0
- nwp500_python-9.5.0/examples/simulator/scenario.json +91 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/pyproject.toml +16 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/setup.cfg +68 -0
- nwp500_python-9.5.0/src/nwp500/__init__.py +464 -0
- nwp500_python-9.5.0/src/nwp500/api_client.py +1433 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/src/nwp500/auth.py +394 -40
- nwp500_python-9.5.0/src/nwp500/ble/__init__.py +110 -0
- nwp500_python-9.5.0/src/nwp500/ble/__main__.py +163 -0
- nwp500_python-9.5.0/src/nwp500/ble/client.py +546 -0
- nwp500_python-9.5.0/src/nwp500/ble/mqtt_adapter.py +211 -0
- nwp500_python-9.5.0/src/nwp500/ble/protocol.py +890 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/src/nwp500/cli/__main__.py +474 -100
- nwp500_python-9.5.0/src/nwp500/cli/ble_backend.py +196 -0
- nwp500_python-9.5.0/src/nwp500/cli/calibration.py +488 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/src/nwp500/cli/handlers.py +2 -2
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/src/nwp500/cli/output_formatters.py +1 -1
- nwp500_python-9.5.0/src/nwp500/cli/recording.py +126 -0
- nwp500_python-9.5.0/src/nwp500/cli/simulate.py +507 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/src/nwp500/config.py +2 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/src/nwp500/converters.py +2 -2
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/src/nwp500/encoding.py +6 -5
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/src/nwp500/enums.py +79 -19
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/src/nwp500/models/__init__.py +40 -1
- nwp500_python-9.5.0/src/nwp500/models/account.py +61 -0
- nwp500_python-9.5.0/src/nwp500/models/device.py +325 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/src/nwp500/models/diagnostics.py +5 -2
- nwp500_python-9.5.0/src/nwp500/models/maintenance.py +31 -0
- nwp500_python-9.5.0/src/nwp500/models/mqtt_models.py +232 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/src/nwp500/models/schedule.py +15 -8
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/src/nwp500/models/status.py +88 -30
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/src/nwp500/models/tou.py +3 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/src/nwp500/mqtt/_control_commands.py +9 -4
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/src/nwp500/mqtt/_device_subscriptions.py +76 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/src/nwp500/mqtt/client.py +55 -27
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/src/nwp500/mqtt/command_queue.py +1 -1
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/src/nwp500/mqtt/connection.py +30 -5
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/src/nwp500/mqtt/control.py +258 -22
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/src/nwp500/mqtt/periodic.py +1 -1
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/src/nwp500/mqtt/reconnection.py +1 -1
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/src/nwp500/mqtt/subscriptions.py +214 -4
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/src/nwp500/mqtt/types.py +34 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/src/nwp500/mqtt/utils.py +17 -1
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/src/nwp500/mqtt_events.py +82 -1
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/src/nwp500/reservations.py +47 -17
- nwp500_python-9.5.0/src/nwp500/sim/__init__.py +130 -0
- nwp500_python-9.5.0/src/nwp500/sim/api.py +1934 -0
- nwp500_python-9.5.0/src/nwp500/sim/attribution.py +195 -0
- nwp500_python-9.5.0/src/nwp500/sim/book.py +1361 -0
- nwp500_python-9.5.0/src/nwp500/sim/calibration.py +3702 -0
- nwp500_python-9.5.0/src/nwp500/sim/circulation.py +191 -0
- nwp500_python-9.5.0/src/nwp500/sim/codec.py +92 -0
- nwp500_python-9.5.0/src/nwp500/sim/cold_supply.py +167 -0
- nwp500_python-9.5.0/src/nwp500/sim/controller.py +1584 -0
- nwp500_python-9.5.0/src/nwp500/sim/decay.py +1121 -0
- nwp500_python-9.5.0/src/nwp500/sim/defaults.py +197 -0
- nwp500_python-9.5.0/src/nwp500/sim/delivery.py +711 -0
- nwp500_python-9.5.0/src/nwp500/sim/draw_forecast.py +494 -0
- nwp500_python-9.5.0/src/nwp500/sim/engine.py +2308 -0
- nwp500_python-9.5.0/src/nwp500/sim/extensions.py +212 -0
- nwp500_python-9.5.0/src/nwp500/sim/heat_pump_recovery.py +268 -0
- nwp500_python-9.5.0/src/nwp500/sim/heating_trajectory.py +342 -0
- nwp500_python-9.5.0/src/nwp500/sim/inputs.py +352 -0
- nwp500_python-9.5.0/src/nwp500/sim/location_air.py +167 -0
- nwp500_python-9.5.0/src/nwp500/sim/meter.py +130 -0
- nwp500_python-9.5.0/src/nwp500/sim/observed.py +568 -0
- nwp500_python-9.5.0/src/nwp500/sim/params.py +1205 -0
- nwp500_python-9.5.0/src/nwp500/sim/physics/__init__.py +2 -0
- nwp500_python-9.5.0/src/nwp500/sim/physics/compressor.py +450 -0
- nwp500_python-9.5.0/src/nwp500/sim/physics/draw.py +474 -0
- nwp500_python-9.5.0/src/nwp500/sim/physics/element.py +623 -0
- nwp500_python-9.5.0/src/nwp500/sim/physics/refrigerant.py +168 -0
- nwp500_python-9.5.0/src/nwp500/sim/physics/tank.py +72 -0
- nwp500_python-9.5.0/src/nwp500/sim/profile.py +238 -0
- nwp500_python-9.5.0/src/nwp500/sim/ready.py +728 -0
- nwp500_python-9.5.0/src/nwp500/sim/record.py +333 -0
- nwp500_python-9.5.0/src/nwp500/sim/result.py +257 -0
- nwp500_python-9.5.0/src/nwp500/sim/schedules.py +243 -0
- nwp500_python-9.5.0/src/nwp500/sim/tank_physics.py +504 -0
- nwp500_python-9.5.0/src/nwp500/sim/tou_triggers.py +308 -0
- nwp500_python-9.5.0/src/nwp500/sim/units.py +20 -0
- nwp500_python-9.5.0/src/nwp500/sim_adapters.py +1323 -0
- nwp500_python-9.5.0/src/nwp500/sim_home_assistant.py +1153 -0
- nwp500_python-9.5.0/src/nwp500/sim_live.py +360 -0
- nwp500_python-9.5.0/src/nwp500/status_history.py +68 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/src/nwp500/topic_builder.py +36 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0/src/nwp500_python.egg-info}/PKG-INFO +50 -16
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/src/nwp500_python.egg-info/SOURCES.txt +113 -2
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/src/nwp500_python.egg-info/requires.txt +14 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/src/nwp500_python.egg-info/scm_file_list.json +112 -1
- nwp500_python-9.5.0/src/nwp500_python.egg-info/scm_version.json +8 -0
- nwp500_python-9.5.0/tests/_sim_site.py +168 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/tests/conftest.py +20 -0
- nwp500_python-9.5.0/tests/fixtures/sim/engine_golden.json +377 -0
- nwp500_python-9.5.0/tests/fixtures/sim/heater_params.json +65 -0
- nwp500_python-9.5.0/tests/fixtures/sim/heater_params_fitted.json +65 -0
- nwp500_python-9.5.0/tests/test_api_endpoints_201.py +821 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/tests/test_api_helpers.py +45 -13
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/tests/test_app_queries.py +51 -45
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/tests/test_auth.py +85 -56
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/tests/test_auth_session_lifecycle.py +24 -16
- nwp500_python-9.5.0/tests/test_auth_session_refresh.py +847 -0
- nwp500_python-9.5.0/tests/test_ble_mqtt_adapter.py +547 -0
- nwp500_python-9.5.0/tests/test_ble_protocol.py +803 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/tests/test_bug_fixes.py +26 -20
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/tests/test_canonical_schedule.py +10 -10
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/tests/test_cli_basic.py +8 -7
- nwp500_python-9.5.0/tests/test_cli_calibrate.py +646 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/tests/test_cli_commands.py +167 -97
- nwp500_python-9.5.0/tests/test_cli_simulate.py +527 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/tests/test_cli_tristate_presentation.py +28 -9
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/tests/test_command_decorators.py +1 -1
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/tests/test_command_queue.py +7 -7
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/tests/test_device_info_cache.py +23 -16
- nwp500_python-9.5.0/tests/test_device_rest_models.py +241 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/tests/test_events.py +37 -37
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/tests/test_exceptions.py +30 -30
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/tests/test_model_converters.py +69 -65
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/tests/test_models.py +78 -42
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/tests/test_mqtt_clean_session_resume.py +21 -18
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/tests/test_mqtt_client_init.py +73 -67
- nwp500_python-9.5.0/tests/test_mqtt_control_fail.py +423 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/tests/test_mqtt_events.py +17 -8
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/tests/test_mqtt_hypothesis.py +5 -2
- nwp500_python-9.5.0/tests/test_mqtt_last_will.py +524 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/tests/test_mqtt_reconnection.py +39 -17
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/tests/test_mqtt_reconnection_storm.py +55 -33
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/tests/test_mqtt_reliability.py +288 -64
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/tests/test_multi_device.py +26 -15
- nwp500_python-9.5.0/tests/test_navilink_201_enums.py +253 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/tests/test_openei.py +2 -1
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/tests/test_protocol_correctness.py +200 -93
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/tests/test_public_api.py +37 -7
- nwp500_python-9.5.0/tests/test_reservation_validation.py +341 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/tests/test_reservations.py +87 -1
- nwp500_python-9.5.0/tests/test_sim_adapters.py +389 -0
- nwp500_python-9.5.0/tests/test_sim_api.py +800 -0
- nwp500_python-9.5.0/tests/test_sim_book.py +488 -0
- nwp500_python-9.5.0/tests/test_sim_book_history.py +250 -0
- nwp500_python-9.5.0/tests/test_sim_calibration.py +863 -0
- nwp500_python-9.5.0/tests/test_sim_circulation.py +395 -0
- nwp500_python-9.5.0/tests/test_sim_codec.py +165 -0
- nwp500_python-9.5.0/tests/test_sim_cold_supply.py +145 -0
- nwp500_python-9.5.0/tests/test_sim_controller.py +1227 -0
- nwp500_python-9.5.0/tests/test_sim_defaults.py +201 -0
- nwp500_python-9.5.0/tests/test_sim_draw_forecast.py +174 -0
- nwp500_python-9.5.0/tests/test_sim_draw_loss.py +225 -0
- nwp500_python-9.5.0/tests/test_sim_element.py +869 -0
- nwp500_python-9.5.0/tests/test_sim_element_fit.py +135 -0
- nwp500_python-9.5.0/tests/test_sim_forecast.py +288 -0
- nwp500_python-9.5.0/tests/test_sim_golden.py +359 -0
- nwp500_python-9.5.0/tests/test_sim_home_assistant.py +1211 -0
- nwp500_python-9.5.0/tests/test_sim_hot_water.py +361 -0
- nwp500_python-9.5.0/tests/test_sim_import_contract.py +413 -0
- nwp500_python-9.5.0/tests/test_sim_live.py +474 -0
- nwp500_python-9.5.0/tests/test_sim_location_air.py +226 -0
- nwp500_python-9.5.0/tests/test_sim_meter.py +163 -0
- nwp500_python-9.5.0/tests/test_sim_no_clock_no_state.py +363 -0
- nwp500_python-9.5.0/tests/test_sim_observed.py +348 -0
- nwp500_python-9.5.0/tests/test_sim_profile.py +86 -0
- nwp500_python-9.5.0/tests/test_sim_readiness.py +879 -0
- nwp500_python-9.5.0/tests/test_sim_readiness_fit.py +296 -0
- nwp500_python-9.5.0/tests/test_sim_ready.py +406 -0
- nwp500_python-9.5.0/tests/test_sim_ready_api.py +770 -0
- nwp500_python-9.5.0/tests/test_sim_replay.py +232 -0
- nwp500_python-9.5.0/tests/test_sim_schedules.py +336 -0
- nwp500_python-9.5.0/tests/test_sim_tank_physics.py +184 -0
- nwp500_python-9.5.0/tests/test_status_history.py +236 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/tests/test_temperature_converters.py +52 -40
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/tests/test_threading_model.py +32 -28
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/tests/test_token_storage.py +10 -5
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/tests/test_tou_api.py +1 -1
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/tests/test_unit_switching.py +86 -15
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/tests/test_unit_system_process_wide.py +4 -4
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/tests/test_utility_modules.py +38 -22
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/tests/test_utils.py +35 -22
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/tox.ini +6 -1
- nwp500_python-9.4.2/docs/explanation/advanced-features.rst +0 -497
- nwp500_python-9.4.2/docs/openapi.yaml +0 -805
- nwp500_python-9.4.2/docs/reference/protocol/rest_api.rst +0 -435
- nwp500_python-9.4.2/docs/reference/python_api/models.rst +0 -860
- nwp500_python-9.4.2/src/nwp500/__init__.py +0 -253
- nwp500_python-9.4.2/src/nwp500/api_client.py +0 -558
- nwp500_python-9.4.2/src/nwp500/models/device.py +0 -104
- nwp500_python-9.4.2/src/nwp500/models/mqtt_models.py +0 -31
- nwp500_python-9.4.2/src/nwp500_python.egg-info/scm_version.json +0 -8
- nwp500_python-9.4.2/tests/test_device_rest_models.py +0 -128
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/.coveragerc +0 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/.github/RESOLVING_PR_COMMENTS.md +0 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/.github/copilot-instructions.md +0 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/.github/workflows/ci.yml +0 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/.github/workflows/release.yml +0 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/.gitignore +0 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/.pre-commit-config.yaml +0 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/.readthedocs.yml +0 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/AUTHORS.rst +0 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/CONTRIBUTING.rst +0 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/LICENSE.txt +0 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/Makefile +0 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/RELEASE.md +0 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/docs/Makefile +0 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/docs/_static/.gitignore +0 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/docs/conf.py +0 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/docs/index.rst +0 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/docs/project/authors.rst +0 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/docs/project/changelog.rst +0 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/docs/project/contributing.rst +0 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/docs/project/license.rst +0 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/examples/.ruff.toml +0 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/examples/advanced/air_filter_reset.py +0 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/examples/advanced/anti_legionella.py +0 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/examples/advanced/auto_recovery.py +0 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/examples/advanced/combined_callbacks.py +0 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/examples/advanced/demand_response.py +0 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/examples/advanced/device_capabilities.py +0 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/examples/advanced/device_status_debug.py +0 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/examples/advanced/energy_analytics.py +0 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/examples/advanced/error_code_demo.py +0 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/examples/advanced/firmware_payload_capture.py +0 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/examples/advanced/mqtt_diagnostics.py +0 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/examples/advanced/power_control.py +0 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/examples/advanced/reconnection_demo.py +0 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/examples/advanced/reservation_schedule.py +0 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/examples/advanced/simple_auto_recovery.py +0 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/examples/advanced/token_restoration.py +0 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/examples/advanced/tou_openei.py +0 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/examples/advanced/tou_schedule.py +0 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/examples/advanced/water_reservation.py +0 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/examples/beginner/01_authentication.py +0 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/examples/beginner/02_list_devices.py +0 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/examples/beginner/03_get_status.py +0 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/examples/beginner/04_set_temperature.py +0 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/examples/intermediate/command_queue.py +0 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/examples/intermediate/device_status_callback.py +0 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/examples/intermediate/error_handling.py +0 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/examples/intermediate/event_driven_control.py +0 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/examples/intermediate/improved_auth.py +0 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/examples/intermediate/legacy_auth_constructor.py +0 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/examples/intermediate/mqtt_realtime_monitoring.py +0 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/examples/intermediate/periodic_requests.py +0 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/examples/intermediate/set_mode.py +0 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/examples/intermediate/vacation_mode.py +0 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/examples/mask.py +0 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/examples/testing/periodic_device_info.py +0 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/examples/testing/simple_periodic_info.py +0 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/examples/testing/test_api_client.py +0 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/examples/testing/test_mqtt_connection.py +0 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/examples/testing/test_mqtt_messaging.py +0 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/examples/testing/test_periodic_minimal.py +0 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/scripts/README.md +0 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/scripts/bump_version.py +0 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/scripts/diagnose_mqtt_connection.py +0 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/scripts/extract_changelog.py +0 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/scripts/format.py +0 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/scripts/lint.py +0 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/scripts/setup-dev.py +0 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/scripts/validate_version.py +0 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/setup.py +0 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/src/nwp500/_base.py +0 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/src/nwp500/cli/__init__.py +0 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/src/nwp500/cli/monitoring.py +0 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/src/nwp500/cli/presentation.py +0 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/src/nwp500/cli/rich_output.py +0 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/src/nwp500/cli/token_storage.py +0 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/src/nwp500/command_decorators.py +0 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/src/nwp500/device_capabilities.py +0 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/src/nwp500/device_info_cache.py +0 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/src/nwp500/events.py +0 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/src/nwp500/exceptions.py +0 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/src/nwp500/factory.py +0 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/src/nwp500/field_factory.py +0 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/src/nwp500/models/_converters.py +0 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/src/nwp500/models/energy.py +0 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/src/nwp500/models/feature.py +0 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/src/nwp500/models/firmware.py +0 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/src/nwp500/mqtt/__init__.py +0 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/src/nwp500/mqtt/diagnostics.py +0 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/src/nwp500/mqtt/state_tracker.py +0 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/src/nwp500/openei.py +0 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/src/nwp500/py.typed +0 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/src/nwp500/temperature.py +0 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/src/nwp500/tou_schedule.py +0 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/src/nwp500/unit_system.py +0 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/src/nwp500/utils.py +0 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/src/nwp500_python.egg-info/dependency_links.txt +0 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/src/nwp500_python.egg-info/entry_points.txt +0 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/src/nwp500_python.egg-info/not-zip-safe +0 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/src/nwp500_python.egg-info/top_level.txt +0 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/tests/test_device_capabilities.py +0 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/tests/test_tou_schedule.py +0 -0
- {nwp500_python-9.4.2 → nwp500_python-9.5.0}/uv.lock +0 -0
|
@@ -2,8 +2,848 @@
|
|
|
2
2
|
Changelog
|
|
3
3
|
=========
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
Version 9.5.0 (2026-10-20)
|
|
6
|
+
==========================
|
|
7
|
+
|
|
8
|
+
Added
|
|
9
|
+
-----
|
|
10
|
+
- **Bluetooth LE access to the controller** (``nwp500.ble``, optional
|
|
11
|
+
``ble`` extra). The NWP500 exposes a GATT service that carries the
|
|
12
|
+
controller's native class-code protocol once the heater is put in pairing
|
|
13
|
+
mode (with the heater off, hold Back and Power for five seconds; it
|
|
14
|
+
advertises as ``HPWH1…``). The scan also lists a heater in Wi-Fi setup
|
|
15
|
+
mode (``HPWH4610``), which answers nothing, and the CLIs pick an
|
|
16
|
+
``HPWH1`` heater over it.
|
|
17
|
+
The package frames and parses those packets (header, class code, address,
|
|
18
|
+
CRC-16/MODBUS), scans for the heater, and reads data classes with a
|
|
19
|
+
matched request/reply client; ``python -m nwp500.ble scan`` and
|
|
20
|
+
``python -m nwp500.ble read`` dump the blocks. Checked on a unit: it
|
|
21
|
+
answers a DIP-switch block, error history, four diagnostics counters, the stored
|
|
22
|
+
time-of-use plan, device info, the controller serial numbers and a
|
|
23
|
+
software-data block; the boiler parameter classes 51-54 do not answer, so
|
|
24
|
+
installer parameters are not reachable yet. ``write_block()`` exists for
|
|
25
|
+
the eventual parameter write and is marked experimental. ``read``
|
|
26
|
+
dumps the classes the heater answers by default and exits 1 when none
|
|
27
|
+
answered; the readers that decode a block raise ``CrcMismatchError``
|
|
28
|
+
on a reply that failed its checksum.
|
|
29
|
+
``read_tou_records()`` decodes the stored time-of-use plan into
|
|
30
|
+
``TOUPeriod``, the cloud's model, reading the controller's 40-slot table
|
|
31
|
+
whole (a ``TOU_PLAN`` read's payload byte is the last slot, not a
|
|
32
|
+
length; ``build_tou_read()``). ``write_tou_records()`` stores a plan of
|
|
33
|
+
up to twelve intervals (``build_tou_write()``: the NaviLink app's clock
|
|
34
|
+
header, then the records in the read layout) and reads it back. It
|
|
35
|
+
refuses to write over a longer stored plan, whose extra intervals no
|
|
36
|
+
Bluetooth write can clear, and raises ``TouWriteError`` when the
|
|
37
|
+
heater refuses or stores something else. ``TOU_PLAN`` is the only
|
|
38
|
+
class written so far (``NWP500_WRITABLE_CLASSES``). A read that gets no reply
|
|
39
|
+
is sent once more, since the first request on a new connection
|
|
40
|
+
sometimes goes unanswered. ``DeviceInfoBlock`` (``DID``), ``ControllerSerials``
|
|
41
|
+
(``MSCD``) and ``SoftwareInfoBlock`` (``MSWD``) use the cloud models'
|
|
42
|
+
field names for the fields matched against them.
|
|
43
|
+
``read_sub_status()`` decodes the ``SSD`` block into two DIP-switch
|
|
44
|
+
words (``SubStatus``), using the layout Navien's boiler service toolkit
|
|
45
|
+
reads; it is unconfirmed on the NWP500.
|
|
46
|
+
``read_status()`` returns live status: the class-50 session command 200
|
|
47
|
+
makes the heater push the ``RTMD`` block about every 300 ms until the
|
|
48
|
+
connection closes, and ``RealTimeStatus`` decodes the fields checked
|
|
49
|
+
against the MQTT status report, in standby and while heating (operation
|
|
50
|
+
mode, state number, DHW temperature and setpoint, the six thermistors,
|
|
51
|
+
superheat and EEV step), under the ``DeviceStatus`` names and
|
|
52
|
+
conversions. A read ends the stream, and the next ``read_status()``
|
|
53
|
+
starts it again. The
|
|
54
|
+
heater's display unit is in no Bluetooth block, so Bluetooth
|
|
55
|
+
temperatures follow the unit-system preference.
|
|
56
|
+
- ``nwp-cli --backend ble`` (with ``--ble-address`` or a scan) runs
|
|
57
|
+
``serial``, ``info`` and ``status`` over Bluetooth without signing in,
|
|
58
|
+
and refuses other commands. Without ``--backend``, commands use the cloud as before.
|
|
59
|
+
The handlers run unchanged against ``nwp500.ble.BleMqttAdapter``, which
|
|
60
|
+
offers the MQTT client methods the heater serves over Bluetooth and
|
|
61
|
+
raises ``BleUnsupportedError`` for the rest. Bluetooth temperatures use
|
|
62
|
+
the cloud models' conversion and unit-system preference;
|
|
63
|
+
``DeviceInfoBlock.min_operation_temperature()`` follows the preference
|
|
64
|
+
when ``celsius`` is not given (Fahrenheit without one, as before).
|
|
65
|
+
- **New REST endpoints.** ``NavienAPIClient`` gains
|
|
66
|
+
``get_push_settings()`` / ``set_push_settings()``
|
|
67
|
+
(``/user/get-push-settings``, ``/user/set-push-settings``),
|
|
68
|
+
``record_filter_change()`` / ``record_cip_change()`` and
|
|
69
|
+
``get_filter_history()`` / ``get_cip_history()`` (the cloud's maintenance
|
|
70
|
+
log; dates are sent ``YYYY-M-D`` without zero padding, as the app does),
|
|
71
|
+
``get_display_preferences()`` / ``set_display_preferences()``
|
|
72
|
+
(``/device/preferences/format``; the cloud rejects it for the NWP500,
|
|
73
|
+
the app only uses it for the IoT-module hydro unit), ``get_user_profile()`` (``/user/info``),
|
|
74
|
+
``get_firmware_detail()`` (``/firmware/info``; ``run_time`` is the
|
|
75
|
+
estimated update time in minutes), ``delete_tou()``
|
|
76
|
+
(``DELETE /device/tou``), ``get_manufacture_id()``,
|
|
77
|
+
``get_error_history()`` (``/device/error/list``; installer accounts
|
|
78
|
+
only, an owner account gets ``COMMON_NOT_AUTHORIZED``), ``update_device_info()``
|
|
79
|
+
(``/device/change-info``), ``list_simple_devices()`` and
|
|
80
|
+
``get_app_version()`` (unauthenticated; a 403 maintenance notice is
|
|
81
|
+
returned as ``server_check_text``; ``nwp500.get_app_version()`` asks
|
|
82
|
+
before sign-in, without a client). ``set_push_settings()`` keeps the
|
|
83
|
+
current value of a flag left ``None``, since the app always sends
|
|
84
|
+
every flag. ``get_error_history()`` entries carry the list's
|
|
85
|
+
``timeZone`` (``time_zone``, ``occurred_at_local``). New models: ``PushSettings``,
|
|
86
|
+
``DeviceInstaller``, ``FirmwareDeviceGroup``, ``FirmwareDetail``,
|
|
87
|
+
``DisplayPreferences``, ``SimpleDevice``, ``DeviceErrorHistoryEntry``,
|
|
88
|
+
``MaintenanceHistoryEntry``, ``UserProfile``, ``AppVersionInfo``. Push
|
|
89
|
+
flags are 1 = on, 2 = off, the reverse of the device's on/off flags, and
|
|
90
|
+
decode to ``True``/``False``/``None``. ``Device.installer`` and
|
|
91
|
+
``Device.alarm_info`` are parsed from ``/device/info`` as the app does;
|
|
92
|
+
``TOUSchedule.nickname`` is modelled.
|
|
93
|
+
- **AWS credentials renew without the password.**
|
|
94
|
+
``NavienAuthClient.refresh_session()`` posts to ``/auth/session/refresh``
|
|
95
|
+
with the access token and updates only the AWS credential fields the
|
|
96
|
+
server returns, as the app does when the MQTT connection fails with an
|
|
97
|
+
auth error. ``secured_sign_in()`` posts to ``/user/secured-sign-in`` with
|
|
98
|
+
the access token and overlays whatever token fields come back (observed
|
|
99
|
+
live: the AWS credentials only; the JWTs are kept), which the app uses
|
|
100
|
+
for auto-login. When the AWS credentials expire, ``ensure_valid_token()``
|
|
101
|
+
and the context manager's stored-token check now try session refresh,
|
|
102
|
+
then secured sign-in, and only then the password sign-in; both token
|
|
103
|
+
steps are skipped when the access token has expired and cannot be
|
|
104
|
+
refreshed. ``re_authenticate()`` tries secured sign-in while the access
|
|
105
|
+
token is live, then the password sign-in, so a client restored from
|
|
106
|
+
tokens alone can use it too.
|
|
107
|
+
The MQTT client's deep reconnect calls ``refresh_session()`` after the
|
|
108
|
+
forced token refresh. ``AuthTokens`` gained an optional
|
|
109
|
+
``aws_credentials_issued_at`` so a renewed AWS expiry survives
|
|
110
|
+
``to_dict()``/restore. Stored tokens without the field load as before.
|
|
111
|
+
- **Control failures are parsed and surfaced.** When the device refuses a
|
|
112
|
+
control command it publishes on ``cmd/52/navilink-{mac}/res/ctrl-fail``
|
|
113
|
+
instead of acknowledging it; the library silently dropped that message.
|
|
114
|
+
It is now parsed into ``ControlFailResponse``
|
|
115
|
+
(``ControlFailCode.DHW_CONTROL_INTERVAL_EXCEEDED`` = 2 is the only code
|
|
116
|
+
the app interprets: the previous DHW control was too recent) and emitted
|
|
117
|
+
as the ``control_failed`` client event (``ControlFailedEvent`` with
|
|
118
|
+
``own_command`` and ``reason``) for every device subscription, with a
|
|
119
|
+
WARNING log. ``subscribe_control_fail_response()`` /
|
|
120
|
+
``unsubscribe_control_fail_response()`` add a plain callback.
|
|
121
|
+
- **MQTT Last-Will and app-connection presence.** Set
|
|
122
|
+
``MqttConnectionConfig.last_will_device`` to register the app's Last-Will
|
|
123
|
+
on ``evt/52/navilink-{mac}/app-connection`` (``event.connection.status``
|
|
124
|
+
0, QoS 1) so other clients learn when this one drops; every rebuilt
|
|
125
|
+
connection carries it. ``subscribe_app_connection()`` /
|
|
126
|
+
``unsubscribe_app_connection()`` parse the topic into
|
|
127
|
+
``AppConnectionEvent`` and emit ``app_connection_received``
|
|
128
|
+
(``AppConnectionReceivedEvent``). New ``MqttTopicBuilder`` helpers
|
|
129
|
+
``control_fail_response_topic()`` and ``app_connection_topic()``.
|
|
130
|
+
- **``DeviceStatus.air_filter_remaining_hours``**: the app's remaining
|
|
131
|
+
filter life (``None`` when the reminder period is 0, else clamped at 0).
|
|
132
|
+
- **``DeviceStatus.minor_code``** (``minorCode``): the secondary alarm code
|
|
133
|
+
the app shows as ``R%03d``; 101 is the one it lets the user reset.
|
|
134
|
+
Defaults to 0 for payloads that omit it.
|
|
135
|
+
- **``DhwOperationSetting.STANDBY`` (0)**, the app's
|
|
136
|
+
``MgppOperationMode.STANDBY``. ``DeviceStatus.dhw_operation_setting``
|
|
137
|
+
previously rejected a 0. It is status-only: ``set_dhw_mode()`` refuses
|
|
138
|
+
it, as it now refuses any id outside 1-6. ``sim_adapters.settings()``
|
|
139
|
+
refuses a Standby status, which the simulator does not model.
|
|
140
|
+
- **``DREvent.GRID_EMERGENCY`` (6).** The app declares CPE and Grid
|
|
141
|
+
Emergency both as id 5 but decodes ``drEventStatus`` by array position,
|
|
142
|
+
so raw 6 is Grid Emergency; ``CPE`` (5) stays "Customer Peak Event".
|
|
143
|
+
- **``UnitType``** NCB700 (22), NFB700_H (24), CAS_NFB700_H (25),
|
|
144
|
+
NPF_SYSTEM (514); **``DeviceType``** IOT_MODULE (8), NFB700_MAIN (50),
|
|
145
|
+
NFB700_SUB (51) (the app's names; ``NPF700_MAIN``/``NPF700_SUB`` remain
|
|
146
|
+
as aliases) and ``HPWH`` as an alias of 52, which is the NWP500
|
|
147
|
+
(``DefineData.HPWH_MODEL``), not an NPF700 WiFi module. A dumped device
|
|
148
|
+
of type 50 or 51 now names it ``NFB700_MAIN`` or ``NFB700_SUB``.
|
|
149
|
+
- **A recovery model's inputs, through ``nwp500.sim.simulate``** (#165).
|
|
150
|
+
Beside ``recovery=``, ``simulate`` takes what a learned model was
|
|
151
|
+
trained with and brings: ``start_hold`` (a start's hold by the
|
|
152
|
+
minutes since the compressor last stopped), ``running_hold`` (what
|
|
153
|
+
is left of a running compressor's), ``lead_draws`` (draws recorded
|
|
154
|
+
before the start), ``draw_hold_factor`` and ``physics_usable``. Each
|
|
155
|
+
goes to the engine as given. Left at their defaults, no answer
|
|
156
|
+
changes.
|
|
157
|
+
Lead draws move the tank as the scenario's own draws do. Like them,
|
|
158
|
+
they are refused on parameters with no fitted draw response
|
|
159
|
+
(``draw_model_unsupported``), and those the model reads carry
|
|
160
|
+
``draw_uncalibrated`` and ``inlet_temp_modelled`` where they apply.
|
|
161
|
+
- **The air, its humidity and the inlet, live from Home Assistant**
|
|
162
|
+
(#166).
|
|
163
|
+
|
|
164
|
+
- ``nwp500.sim_home_assistant`` reads Home Assistant's REST API,
|
|
165
|
+
read-only. ``HomeAssistantEnvironment`` is a ``sim_live``
|
|
166
|
+
environment source:
|
|
167
|
+
|
|
168
|
+
- the air is the hourly forecast projected through a
|
|
169
|
+
``location_air`` model, anchored to a reading beside the heater at
|
|
170
|
+
most an hour old (without a model, that reading held);
|
|
171
|
+
- the humidity is a reading at most an hour old;
|
|
172
|
+
- the inlet is the plateau of the last 72 hours' long draws, from
|
|
173
|
+
the inlet probe, the meter and the circulation's runs.
|
|
174
|
+
|
|
175
|
+
Units come from each entity's ``unit_of_measurement``. A part that
|
|
176
|
+
can't be read keeps its default, and the source says why.
|
|
177
|
+
``record_inputs`` reads the same entities over a recorded history.
|
|
178
|
+
- ``nwp500.sim.meter.flow_gpm`` is a meter's flow from its volume
|
|
179
|
+
reports and its forward rate: the net volume over a trailing 90 s
|
|
180
|
+
where it holds at the draw line for 120 s, the rate elsewhere. It
|
|
181
|
+
finds the long draws the inlet plateau needs where the rate alone,
|
|
182
|
+
silent through a steady draw, misses some.
|
|
183
|
+
- ``nwp-cli simulate run``, ``snapshot`` and ``calibrate`` take
|
|
184
|
+
``--sources FILE`` (or ``NWP500_SOURCES``), a TOML file naming the
|
|
185
|
+
entities, with the token in an environment variable.
|
|
186
|
+
``calibrate`` reads the air, the humidity, the draws and the
|
|
187
|
+
circulation over the record, which makes the quiet test exact.
|
|
188
|
+
- ``sim_live.compose`` takes the heater's own air reading when the
|
|
189
|
+
environment source has none.
|
|
190
|
+
- **An optional heater simulator, ``nwp500.sim``** (#150, first part). The
|
|
191
|
+
core of a physics simulation of the NWP500: an R-134a compressor model
|
|
192
|
+
coupled to a two-zone tank, standby loss, the heating elements, draws,
|
|
193
|
+
and the heater's own controller (start triggers, the TOU hold and
|
|
194
|
+
recovery cap, the restart lockout, reservation entries, setpoint and
|
|
195
|
+
mode writes), walked as events by ``nwp500.sim.engine.simulate()``. It
|
|
196
|
+
is a port of an engine checked against recorded data from one
|
|
197
|
+
NWP500-65, and returns the same answers to the last bit. Install it
|
|
198
|
+
with the ``sim`` extra (numpy, pandas, scipy). It performs no I/O,
|
|
199
|
+
reads no clock and holds no module state, and ``import nwp500`` does
|
|
200
|
+
not load it. An import-linter contract in ``tox -e lint`` keeps it
|
|
201
|
+
from importing the MQTT or REST clients, aiohttp, the AWS IoT SDK or
|
|
202
|
+
``logging``.
|
|
203
|
+
- **The simulator's API** (#150, second part).
|
|
204
|
+
|
|
205
|
+
- ``nwp500.sim.simulate(scenario, params)`` walks a frozen
|
|
206
|
+
``Scenario``: the heater's state, settings, history, schedules,
|
|
207
|
+
environment, draws and the commands under evaluation.
|
|
208
|
+
- ``recovery()`` answers "when will the tank be hot?", and
|
|
209
|
+
``standby()`` projects the tank coasting.
|
|
210
|
+
- The TOU periods become the windows the heater holds, and the
|
|
211
|
+
reservations become firings, with the local times in the
|
|
212
|
+
scenario's zone (``sim.schedules``).
|
|
213
|
+
- Results name every default they used.
|
|
214
|
+
- ``default_params(volume_code)`` carries one NWP500-65's parameters,
|
|
215
|
+
with the tank volume scaled for the 50 and 80 gal models.
|
|
216
|
+
- ``sim.codec`` reads and writes scenarios as JSON documents.
|
|
217
|
+
- ``nwp500.sim_adapters`` builds the inputs from ``DeviceStatus``,
|
|
218
|
+
``DeviceFeature``, ``ReservationSchedule`` and ``TOUInfo``, reading
|
|
219
|
+
the raw fields so the unit preference cannot change them. It is
|
|
220
|
+
where units are crossed: ``environment``, ``loads``, ``write`` and
|
|
221
|
+
``reservation_entry`` take a caller's values in the preferred units
|
|
222
|
+
(degC and litres under ``metric``), and ``path``, ``temperature``
|
|
223
|
+
and ``volume`` give a result's back in them. The simulator itself
|
|
224
|
+
works in degF and US gallons.
|
|
225
|
+
- Docs: a how-to (``docs/how-to/simulate-heater.rst``), from fetching
|
|
226
|
+
the heater's data to evaluating a change before sending it; a
|
|
227
|
+
reference of every input, its unit, default and source
|
|
228
|
+
(``docs/reference/simulator-inputs.rst``); an explanation of the
|
|
229
|
+
model and its accuracy (``docs/explanation/simulator.rst``); and the
|
|
230
|
+
API reference.
|
|
231
|
+
|
|
232
|
+
- Hot water at the tap: ``nwp500.sim.readiness(state, settings,
|
|
233
|
+
params)`` gives deliverable gallons now; ``Loads.windows`` judge a
|
|
234
|
+
shower or a bath (deliverable gallons and margin at the onset, and
|
|
235
|
+
whether the delivered water ran cold); ``SimulationResult.delivery``
|
|
236
|
+
is the delivered temperature along the walk (``sim.delivery``).
|
|
237
|
+
- The heat book (``sim.book``): the tank's heat by bookkeeping, walked
|
|
238
|
+
through the simulation as the engine's ``HeatLedger`` so a draw that
|
|
239
|
+
spends it takes the zones down. Started from the caller's knowledge
|
|
240
|
+
(``full_tank_book``) or their own record, never guessed from the
|
|
241
|
+
probes.
|
|
242
|
+
- ``HeaterParams`` gains ``readiness`` (the line, the outlet lag, the
|
|
243
|
+
fall rate, the dip tolerance, evidence) and ``book`` parts, with one
|
|
244
|
+
installation's measured defaults and their provenance.
|
|
245
|
+
- Time to ready: ``nwp500.sim.time_to_ready(scenario, params)`` says
|
|
246
|
+
whether the tank can run a bath, when it can in the heat pump and
|
|
247
|
+
in each element mode with the energy to get there, and how many
|
|
248
|
+
showers it is ready for, along the recovery the heater runs next
|
|
249
|
+
(``next_recovery``) (``sim.ready``).
|
|
250
|
+
- ``standby(..., settings=)`` says when the heater would start itself
|
|
251
|
+
on the coast and why; ``element_mode`` projects a recovery in an
|
|
252
|
+
element mode.
|
|
253
|
+
- A walk with draws outside the range the draw terms were fitted over
|
|
254
|
+
says ``draw_out_of_domain`` (``DrawParams.domain_upper_f``,
|
|
255
|
+
``domain_volume_gal``); a write or a written reservation outside the
|
|
256
|
+
setpoint range the simulation runs is refused.
|
|
257
|
+
|
|
258
|
+
**Behaviour notes.** Answers that a client carrying an earlier copy of
|
|
259
|
+
this engine will see change at the switch:
|
|
260
|
+
|
|
261
|
+
- When no ``RecoveryModel`` answers (none given, or it declines) and
|
|
262
|
+
the physics integration stops at its bound short of the target,
|
|
263
|
+
there is no recovery and ``simulate`` raises ``SimulationError``.
|
|
264
|
+
The bound is no longer served as a duration.
|
|
265
|
+
- A recovery's reasons keep the served recovery's own (a truncated
|
|
266
|
+
integration, a model's diagnostics) beside ``recovery_low_confidence``.
|
|
267
|
+
- ``mode_recovery``'s ``upper_end_f`` is the zone's own temperature
|
|
268
|
+
when the element does not start or has nothing to do.
|
|
269
|
+
- A draw during a recovery honours ``draws_on`` and is priced on the
|
|
270
|
+
configured tank, not the 63.2 gal default.
|
|
271
|
+
- No ``hp_lower_on`` disables only the lower trigger; the upper
|
|
272
|
+
zone's at a TOU window's opening still starts the heater.
|
|
273
|
+
- A draw at a draw window's onset counts its cold side when the window
|
|
274
|
+
is judged.
|
|
275
|
+
- TOU prices are compared decoded at their own ``decimal_point``; each
|
|
276
|
+
period is laid out on the dates its day and season masks select
|
|
277
|
+
before windows that touch are joined across midnight.
|
|
278
|
+
- Scenario instants are held in UTC, so a horizon is elapsed time
|
|
279
|
+
across a daylight-saving change; a scenario document keeps which
|
|
280
|
+
fields were left to their defaults.
|
|
281
|
+
- ``sim.book.SpanLoss`` lets a client charge a loss the book does not
|
|
282
|
+
model; without one, the book charges none.
|
|
283
|
+
- Circulation runs within 10 minutes of each other are one run over
|
|
284
|
+
the whole schedule, so a coast boundary between them no longer
|
|
285
|
+
charges each its own displaced volume.
|
|
286
|
+
- ``default_params()`` has no mixing valve; ``site_example_params()``
|
|
287
|
+
is the installation the values were fitted at, its valve included
|
|
288
|
+
(``with_valve``, ``SITE_VALVE``). The core's draw functions default
|
|
289
|
+
to no valve as well. Each parameter says whether it describes the
|
|
290
|
+
heater or the installation (``scope_of``), and the frame it was
|
|
291
|
+
fitted in (``note_of``).
|
|
292
|
+
- Circulation (``sim.circulation``): an installation's runs
|
|
293
|
+
(``Loads.circulation``, ``sim_adapters.circulation`` for a weekly
|
|
294
|
+
schedule) charged as flowing draws and to the heat book while they
|
|
295
|
+
run, on ``HeaterParams.circulation``; with none, nothing is charged.
|
|
296
|
+
- The air where the heater stands, from the heater alone and a public
|
|
297
|
+
forecast: ``sim_adapters.location_air_reading`` (the heater's own
|
|
298
|
+
reading, only while the compressor runs) and
|
|
299
|
+
``sim_adapters.location_air`` (an outdoor forecast projected by
|
|
300
|
+
``sim.location_air``, anchored to that reading, with ``fit`` for
|
|
301
|
+
one's own model). ``time_to_ready`` and ``next_recovery`` take a
|
|
302
|
+
``RecoveryModel``.
|
|
303
|
+
- Calibration from the heater's own record (``sim.calibration``,
|
|
304
|
+
``sim_adapters.history`` to build one from ``DeviceStatus``
|
|
305
|
+
readings): the standby loss law and the zones' mixing constant from
|
|
306
|
+
quiet windows, then the compressor constant and evaporator approach
|
|
307
|
+
from heat-pump recoveries that reached their target, then input
|
|
308
|
+
power and apparent COP against the heater's own power readings, each
|
|
309
|
+
reported
|
|
310
|
+
with what it rested on (held-out error, windows used, a fit pinned
|
|
311
|
+
on a bound). An element run counts as busy in the quiet test, not
|
|
312
|
+
only the compressor and hot water in use.
|
|
313
|
+
- ``forecast``: when a recovery must start to have the tank ready at
|
|
314
|
+
a time, and whether it can (``feasible``, ``shortfall_minutes``),
|
|
315
|
+
to the heater's own stop threshold or a partial charge, with
|
|
316
|
+
``no_heating_required`` (the lower zone at its target, or a zero
|
|
317
|
+
energy count outside a TOU window), ``not_scheduled``, and the
|
|
318
|
+
trajectory's electricity, stored heat and COP.
|
|
319
|
+
``active_cycle``: what is left of a running recovery, its physics
|
|
320
|
+
estimate on the evaporator coil's live reading.
|
|
321
|
+
``Settings.hp_lower_off_f``, ``hp_upper_on_f`` and
|
|
322
|
+
``HeaterState.energy_to_setpoint_wh`` carry what they need, filled
|
|
323
|
+
by the adapters; ``recovery`` says ``not_scheduled`` in Vacation and
|
|
324
|
+
Power Off.
|
|
325
|
+
- The heater's profile (``sim.profile``): its modes and their energy,
|
|
326
|
+
setpoint range and step, depletion threshold and tank, under a
|
|
327
|
+
revision digest; and ``recovery_as_of``, a kept recovery aged to
|
|
328
|
+
now.
|
|
329
|
+
- The element terms from a heater's raw record (``sim.calibration``):
|
|
330
|
+
the upper probe's lag and rate as one line from clean stints
|
|
331
|
+
(``element_stints``, ``fit_element_lag``), where each element
|
|
332
|
+
switches off and what it draws per metered stint
|
|
333
|
+
(``element_off_points``, ``element_power``), and the zones' rates
|
|
334
|
+
and element power on the one-minute grid (``element_grid``,
|
|
335
|
+
``fit_element_rates``), each with day-bootstrap intervals.
|
|
336
|
+
- Draws from a weekly draw forecast (``sim.draw_forecast``): an issue
|
|
337
|
+
checked whole, its expected draws (rate x per-draw mean, the
|
|
338
|
+
per-draw volume kept), the heater's draw windows, and the worst case
|
|
339
|
+
of each window's long draws first (``forecast_loads``,
|
|
340
|
+
``simulate(withheld_windows=)``). A draw's domain is now judged by
|
|
341
|
+
its per-draw volume where it carries one (``Draw.per_draw_gal``),
|
|
342
|
+
not its fractional expectation.
|
|
343
|
+
- The mains from an inlet probe (``sim.cold_supply``,
|
|
344
|
+
``sim_adapters.cold_supply``): the median plateau of recent long
|
|
345
|
+
draws with circulation off and the meter reading flow, from minute 9
|
|
346
|
+
of each; ``on_the_mains`` converts a book's gallons at it.
|
|
347
|
+
- What the heater did over a range, from its record, as the model
|
|
348
|
+
reads it: ``sim.record.Record`` (each signal's change rows, its
|
|
349
|
+
writes and the heater's own drops, TOU switches and window edges,
|
|
350
|
+
starts with elements corroborated by power;
|
|
351
|
+
``sim_adapters.record``), ``sim.attribution`` (why each recorded
|
|
352
|
+
start happened) and ``sim.observed`` (modes and whether each was in
|
|
353
|
+
effect, when each source ran, counted and attributed starts, energy
|
|
354
|
+
per slot, the delivered temperature, and the record's gaps;
|
|
355
|
+
``sim_adapters.observe``). ``replay`` reads its writes from the same
|
|
356
|
+
record.
|
|
357
|
+
- The readiness evidence and the heat book's own terms from a metered
|
|
358
|
+
record (``sim.calibration``, ``sim_adapters.draw_frame``): clean
|
|
359
|
+
draws and their demonstrated volumes reduced to the frontier, with a
|
|
360
|
+
time-ordered held-out check, and the cap after a cold lower zone;
|
|
361
|
+
the book's compressor rate, its slope with the air, standby UA and
|
|
362
|
+
circulation's loss fitted on re-anchors of full tanks
|
|
363
|
+
(``RecordedBook``, ``fit_book_terms``), its error by anchor age, and
|
|
364
|
+
the empty points in the book's frame and the drawable one, and the
|
|
365
|
+
error bands (``book_error_btu``: the p90 pooled over anchors an hour
|
|
366
|
+
to 18 h old, and the 18-24 h band's, each rounded up to 100 BTU).
|
|
367
|
+
With a meter, the COP fit prices the heat each cycle's draws and
|
|
368
|
+
circulation runs took, and the draws just before it
|
|
369
|
+
(``metered_withdrawn``, ``metered_lead``; ``calibrate(draws=,
|
|
370
|
+
circulation=)``), as the model's fit did, each run at the flow it
|
|
371
|
+
ran at (``circulation_flow_at=``, ``flow_lookup``). What could not be fitted
|
|
372
|
+
keeps the base's value and is named in ``HeaterParams.unfitted``;
|
|
373
|
+
every answer it reaches says ``physics_uncalibrated``,
|
|
374
|
+
``energy_uncalibrated``, ``standby_uncalibrated`` or
|
|
375
|
+
``tau_gap_prior``.
|
|
376
|
+
The probes' share of circulation's heat
|
|
377
|
+
(``circulation_windows``, ``fit_ungated_observability``), fitted
|
|
378
|
+
against the displaced volume it is used with.
|
|
379
|
+
Circulation's terms from its own telemetry: each run's measured heat
|
|
380
|
+
(``measure_runs``), the displaced volume that prices them
|
|
381
|
+
(``fit_displaced_gal``), its recent flow and the flow a past run ran
|
|
382
|
+
at (``recent_flow_gpm``, ``flow_lookup``), as the model fitted them.
|
|
383
|
+
The book's response to a draw, fitted on the record's long draws by
|
|
384
|
+
the procedures its values were fitted with: the cold front's place
|
|
385
|
+
and width (``fit_front``), the lower zone's settling after a draw
|
|
386
|
+
(``fit_front_settle``) and the upper zone's mixing
|
|
387
|
+
(``fit_zone_mix``), each with a day bootstrap.
|
|
388
|
+
- The heat book from a recorded history, as the model kept it:
|
|
389
|
+
``sim.book.Step``, ``book_steps`` (the anchor on a quiet full tank
|
|
390
|
+
after a recovery, or a recovery's end during a draw; every step
|
|
391
|
+
booked from it; the inlet the median of 3 h of a probe), and
|
|
392
|
+
``sim_adapters.book_from_history``, which grids the heater's
|
|
393
|
+
statuses, a hot-water meter's flow, circulation's runs and flow and
|
|
394
|
+
an inlet probe onto 10 s steps and books 18 h, else 72 h aged.
|
|
395
|
+
``CirculationParams.meter_share`` is the share of a run's flow the
|
|
396
|
+
meter also reads; ``BookParams`` gains ``history_h``, ``draw_gpm``
|
|
397
|
+
and ``inlet_window_s``.
|
|
398
|
+
- ``sim_adapters.replay``: a past scenario from the statuses the
|
|
399
|
+
heater reported, as the model's replay read its record: the state
|
|
400
|
+
then, the compressor's last stop and the last heating from their
|
|
401
|
+
edges, the recorded setpoint and mode changes as writes (but the
|
|
402
|
+
heater's own drop to Heat Pump at a window), and the TOU switch's
|
|
403
|
+
changes (``Schedules.tou_switches``, which the walk applies).
|
|
404
|
+
- The heater's own TOU marker (``Settings.hp_upper_on_f`` raised):
|
|
405
|
+
``time_to_ready`` and ``element_mode`` read "in a window now" from
|
|
406
|
+
it, and ``next_recovery`` lets it override the schedule for a
|
|
407
|
+
recovery running now (``api.marked_windows``), as the model did;
|
|
408
|
+
an unknown marker counts as in force. ``simulate(tou_windows_in_force=)``
|
|
409
|
+
takes the windows to keep, and TOU on with no periods says
|
|
410
|
+
``tou_schedule_unknown``. ``element_mode(setpoint_f=)`` projects to a
|
|
411
|
+
what-if setpoint while the heater's own decides the window test.
|
|
412
|
+
- ``time_to_ready`` says ``inlet_temp_modelled`` when a booked answer
|
|
413
|
+
converts at an inlet nobody measured (``inlet_measured=``, or a
|
|
414
|
+
book whose steps measured it); ``next_recovery``'s coast loses the
|
|
415
|
+
heat circulation takes on it as well as standby; a served recovery
|
|
416
|
+
under 0.6 confidence says ``recovery_low_confidence``. ``standby``
|
|
417
|
+
reports the gallons drawn and the heat they took, circulation's
|
|
418
|
+
minutes and heat, and the mean air over the coast.
|
|
419
|
+
- ``default_params()`` names the installation's book, readiness and
|
|
420
|
+
draw terms in ``HeaterParams.unfitted`` (``INSTALLATION_TERMS``):
|
|
421
|
+
they are one installation's fits, so answers they reach say
|
|
422
|
+
``book_uncalibrated``, ``readiness_uncalibrated`` or
|
|
423
|
+
``draw_uncalibrated``. A ``Record`` drops missing readings on entry
|
|
424
|
+
and takes its installation's cadences and the heater's resting
|
|
425
|
+
threshold (``drop_within``, ``element_power_slack``,
|
|
426
|
+
``resting_upper_on_f``). ``observe(profile=, revision_since=)``
|
|
427
|
+
names modes by a heater profile. ``schedules.rule_windows`` lays out
|
|
428
|
+
TOU rules that have no prices, such as a learned schedule. The
|
|
429
|
+
engine, the tank physics and the physics models are documented as
|
|
430
|
+
public (:doc:`/reference/python_api/sim`).
|
|
431
|
+
- **``nwp-cli simulate``, and the live sources it composes** (#151).
|
|
432
|
+
|
|
433
|
+
- ``nwp500.sim_live`` reads what a scenario needs from the device:
|
|
434
|
+
``LiveHeaterSource`` (the state, the settings, the air and the
|
|
435
|
+
tank size, from one status and one feature read) and
|
|
436
|
+
``LiveScheduleSource`` (the reservations over MQTT, the TOU
|
|
437
|
+
periods over REST, the TOU switch from the status).
|
|
438
|
+
``compose()`` awaits them, and any other source with an ``async
|
|
439
|
+
read()`` (``Given`` for a value in hand), into a ``Scenario``. A
|
|
440
|
+
read with no reply raises ``LiveReadError``, as does a TOU read
|
|
441
|
+
that fails while TOU is on. The simulator still imports none of
|
|
442
|
+
it.
|
|
443
|
+
- ``nwp-cli simulate run`` walks the heater as it is now, a scenario
|
|
444
|
+
file, or the heater now with a scenario file laid on it
|
|
445
|
+
(``--live``). Each part is chosen on its own: a flag
|
|
446
|
+
(``--start``, ``--hours``, ``--tz``, ``--ambient``,
|
|
447
|
+
``--environment``, ``--inlet``, ``--draws``, ``--plan``,
|
|
448
|
+
``--params``), then the scenario file, then the device. It prints
|
|
449
|
+
a table (the heating intervals, the energy by source, readiness,
|
|
450
|
+
the draw windows and which ran cold, the defaults used), the
|
|
451
|
+
result document (``--format json``) or the path (``--format
|
|
452
|
+
csv``). ``nwp-cli simulate snapshot`` writes the heater and its
|
|
453
|
+
schedules now as a scenario file to edit and replay. Without the
|
|
454
|
+
``sim`` extra the commands stop with an install hint.
|
|
455
|
+
``examples/simulator/`` has a scenario, a plan and an overlay to
|
|
456
|
+
start from; :doc:`/how-to/simulate-heater` shows each use.
|
|
457
|
+
- **Record a heater's history and calibrate the simulator to it**
|
|
458
|
+
(#152).
|
|
459
|
+
|
|
460
|
+
- ``nwp500.status_history`` is the history's format: one JSON line
|
|
461
|
+
per status, ``{"t": <ISO 8601 instant>, "status": <raw payload>}``.
|
|
462
|
+
``line()`` writes one and ``read()`` reads them back as
|
|
463
|
+
``(instant, DeviceStatus)`` pairs. It does no I/O and needs no
|
|
464
|
+
extra.
|
|
465
|
+
- ``nwp-cli simulate record HISTORY`` appends the heater's statuses
|
|
466
|
+
to a file until stopped, by default asking for one every 30 s.
|
|
467
|
+
- ``nwp-cli simulate calibrate HISTORY`` fits the standby loss, the
|
|
468
|
+
compressor and the power to that history
|
|
469
|
+
(``sim.calibration.calibrate``). It prints a row for each term: the
|
|
470
|
+
value, what it was fitted from, its error, and whether it was
|
|
471
|
+
fitted or kept the base's value, and why. ``-o`` writes the fitted
|
|
472
|
+
``HeaterParams`` document for ``simulate run --params``. The
|
|
473
|
+
standby fit needs the air beside the heater, from a sensor
|
|
474
|
+
(``--air``, a CSV file) or a constant (``--ambient``). Given, that
|
|
475
|
+
air is the recoveries' air too. The heater's own reading, filled
|
|
476
|
+
across the gaps between compressor runs, and an outdoor projection
|
|
477
|
+
both biased the fitted loss, so neither stands in for the air.
|
|
478
|
+
Without ``--humidity`` the fits get 50 %, their documented default.
|
|
479
|
+
A gap between statuses of more than 4 times the record's usual
|
|
480
|
+
spacing (at least 5 minutes) is unobserved, so no quiet window or
|
|
481
|
+
recovery spans a stopped recording. If a write fails, ``record``
|
|
482
|
+
stops with an error.
|
|
483
|
+
- ``sim_adapters.history(..., max_gap=)`` marks such gaps: the
|
|
484
|
+
sample before one is set to water in use and excluded.
|
|
485
|
+
- :doc:`/how-to/calibrate-simulator` covers the recording, the air,
|
|
486
|
+
the report, and what a history cannot tell (the draws, a
|
|
487
|
+
circulation loop the heater does not run).
|
|
488
|
+
- Three helpers the issue proposed are left out:
|
|
489
|
+
|
|
490
|
+
- **A mains-temperature estimate from the air.** The draw terms
|
|
491
|
+
were fitted against a measured inlet, so an estimated inlet
|
|
492
|
+
would bias every draw.
|
|
493
|
+
- **A draw-timing profile from the "hot water in use" flag.** The
|
|
494
|
+
flag also fires on circulation, and the expected draws from such
|
|
495
|
+
a profile never predicted better than no draws.
|
|
496
|
+
- **A check against the daily energy totals.** Power and COP are
|
|
497
|
+
fitted cycle by cycle.
|
|
498
|
+
|
|
499
|
+
Changed
|
|
500
|
+
-------
|
|
501
|
+
- **Recirculation mode values follow the NaviLink app.** ``RecirculationMode``
|
|
502
|
+
had 2 and 3 swapped and 4 wrong: the app's
|
|
503
|
+
``MgppRecirculationOperationMode`` (sent by ``recirc-mode`` and decoded
|
|
504
|
+
from ``recircOperationMode``) is 1 Always on, 2 Weekly, 3 HotButton only,
|
|
505
|
+
4 No recirculation. The members are now ``ALWAYS_ON``, ``WEEKLY``,
|
|
506
|
+
``HOT_BUTTON_ONLY`` and ``OFF``; ``ALWAYS`` remains as an alias of
|
|
507
|
+
``ALWAYS_ON``. **Behaviour change:** ``BUTTON``, ``SCHEDULE`` and
|
|
508
|
+
``TEMPERATURE`` are removed, since their meanings were wrong; code that
|
|
509
|
+
sent ``RecirculationMode.BUTTON`` (2) was selecting the weekly schedule.
|
|
510
|
+
``RECIRC_MODE_TEXT``, the ``recirc`` CLI command, the recirculation
|
|
511
|
+
example and the protocol docs follow. ``model_dump()`` writes enum
|
|
512
|
+
names, so a dumped status now says ``"ALWAYS_ON"`` for 1 (was
|
|
513
|
+
``"ALWAYS"``) and ``"WEEKLY"`` for 2 (was ``"BUTTON"``); code keyed on
|
|
514
|
+
the old strings must follow.
|
|
515
|
+
- **Reservation mode 5 is Power Off, not Vacation.** Weekly-schedule
|
|
516
|
+
entries carry the app's ``MgppReservationMode`` (0 not reserved, 1 Heat
|
|
517
|
+
Pump, 2 Electric, 3 Energy Saver, 4 High Demand, 5 Power Off; there is
|
|
518
|
+
no vacation entry), now the ``ReservationMode`` enum.
|
|
519
|
+
``ReservationEntry.mode_name`` resolves through it, so a mode-5 entry
|
|
520
|
+
reads "Power Off" instead of "Vacation" and mode 0 reads "Not Reserved".
|
|
521
|
+
**Behaviour change:** reservation writes (``add_reservation()``,
|
|
522
|
+
``update_reservation()``, ``build_reservation_entry()`` and the MQTT
|
|
523
|
+
reservation validator) now accept mode 1-5 and reject 6, which was never
|
|
524
|
+
a valid entry mode. An entry already on the heater with mode 6 (earlier
|
|
525
|
+
versions documented 6 as Power Off) does not block the reservation
|
|
526
|
+
helpers: they send the entries they did not change as they read them
|
|
527
|
+
(``update_reservations(kept=)``). Changing such an entry needs a mode
|
|
528
|
+
of 1-5. The simulator's reservation entries take the same numbering
|
|
529
|
+
(``sim.inputs.EntryMode``); only 1-4 are modelled, as before.
|
|
530
|
+
- **``dr_override_status`` is an on/off flag, not hours.** The app decodes
|
|
531
|
+
it with its 0/1/2 on-off enum and shows "DR applied" when it is 2. The
|
|
532
|
+
field stays an ``int`` but no longer carries an ``hours`` unit; the new
|
|
533
|
+
``DeviceStatus.dr_override_active`` is True when it is 2.
|
|
534
|
+
- **Air-filter life comes from ``cumulated_op_time_eva_fan``.** The app
|
|
535
|
+
computes remaining filter life as ``air_filter_alarm_period -
|
|
536
|
+
cumulated_op_time_eva_fan`` and never reads ``air_filter_alarm_elapsed``.
|
|
537
|
+
The two descriptions were the wrong way round; ``cumulated_op_time_eva_fan``
|
|
538
|
+
is fan hours since the last filter reset and ``air_filter_alarm_elapsed``
|
|
539
|
+
is unconfirmed.
|
|
540
|
+
- **``signal_app_connection()`` sends the app's message shape.** It
|
|
541
|
+
published ``{clientID, timestamp}``, a shape nothing reads. It now
|
|
542
|
+
publishes the NaviLink app's ``RequestLastWill`` envelope with
|
|
543
|
+
``event.connection.status`` 1.
|
|
544
|
+
- Sign-in and ``/auth/refresh`` requests now send the library's
|
|
545
|
+
``User-Agent`` and ``Content-Type`` headers, as every other request does.
|
|
546
|
+
- **The heater's own air reading is not as close as documented in
|
|
547
|
+
every season.** ``sim_adapters.location_air_reading``,
|
|
548
|
+
``sim.location_air`` and the inputs reference said it read the air
|
|
549
|
+
beside the heater within about 0.35 °F while the compressor runs.
|
|
550
|
+
Measured against a sensor beside the heater, that held from August
|
|
551
|
+
to October. From May to July the reading was up to about 2 °F warm,
|
|
552
|
+
and in a run's first 2 minutes about 2.7 °F warm. The idle reading
|
|
553
|
+
is about 6 °F warm, not 5.
|
|
554
|
+
- **The CLI logs to stderr.** It logged to stdout, so a warning could
|
|
555
|
+
land in the middle of ``--json``, ``--raw`` or CSV output piped to
|
|
556
|
+
another program. Output stays on stdout.
|
|
557
|
+
- **The package root loads its exports on first use** (PEP 562).
|
|
558
|
+
``from nwp500 import NavienMqttClient``, ``nwp500.DeviceStatus`` and
|
|
559
|
+
``nwp500.mqtt`` work as before, and type checkers read the same names,
|
|
560
|
+
but ``import nwp500.sim`` and ``nwp500.sim_adapters`` no longer load
|
|
561
|
+
the clients, aiohttp or the AWS IoT SDK (tested in a fresh
|
|
562
|
+
interpreter).
|
|
563
|
+
- **Superheat is converted as a difference** (#153).
|
|
564
|
+
``DeviceStatus.target_super_heat`` and ``current_super_heat`` converted
|
|
565
|
+
their raw tenths of a degree Celsius as absolute temperatures, so in
|
|
566
|
+
Fahrenheit they added 32. Superheat is a temperature difference (suction
|
|
567
|
+
minus evaporator), so they now use ``DeciCelsiusDelta``, as the
|
|
568
|
+
``*_diff_temp_setting`` properties already do. Raw 50 now reads 9.0 degF
|
|
569
|
+
instead of 41.0 degF. **Behaviour change:** every Fahrenheit value drops
|
|
570
|
+
by 32, so stored or charted Fahrenheit superheat from earlier versions is
|
|
571
|
+
32 degF too high. Celsius values are unchanged. The protocol docs now
|
|
572
|
+
describe both fields as differences.
|
|
573
|
+
- **Vacation accepts 1-99 days** (#155). ``set_dhw_mode()`` (vacation),
|
|
574
|
+
``set_vacation_days()`` and ``set_vacation_duration()`` rejected anything
|
|
575
|
+
over 30 days. The NaviLink app's vacation picker and the front panel
|
|
576
|
+
(User's Information Manual, section 3.4.5) both offer 1-99, so the
|
|
577
|
+
library now accepts the same. The ``vacation`` and
|
|
578
|
+
``vacation-duration`` CLI commands follow.
|
|
579
|
+
|
|
580
|
+
Fixed
|
|
581
|
+
-----
|
|
582
|
+
- ``FirmwareInfo.device_group`` was typed as a string; the app reads an
|
|
583
|
+
object ``{groupSeq, unitNum}`` (plus a top-level ``deviceGroupUnitNo``).
|
|
584
|
+
It is now ``FirmwareDeviceGroup``, with a bare string still accepted.
|
|
585
|
+
- ``convert_tou()`` hard-coded ``userType: "O"``; it now sends the
|
|
586
|
+
signed-in user's type from the sign-in response, defaulting to ``"O"``.
|
|
587
|
+
- **A reservation entry turns a powered-off heater on in the
|
|
588
|
+
simulator.** The controller skipped entries while powered off, as in
|
|
589
|
+
Vacation, on a misreading of the device test, which found that a
|
|
590
|
+
powered-off heater fires the entry and turns on in its mode (two
|
|
591
|
+
runs). ``Settings.powered_off`` tells power-off from Vacation; the
|
|
592
|
+
entry is judged as a mode write out of power-off, and the result
|
|
593
|
+
carries ``powered_on_by_entry``. In Vacation entries are still
|
|
594
|
+
skipped.
|
|
595
|
+
|
|
596
|
+
Documentation
|
|
597
|
+
-------------
|
|
598
|
+
- **What the device tests and the record measured.** Three new
|
|
599
|
+
explanation pages: :doc:`/explanation/recovery-speed` (heat output,
|
|
600
|
+
minutes per degF, compressor power and efficiency, start-up energy,
|
|
601
|
+
the air's effect, how much of the tank heats),
|
|
602
|
+
:doc:`/explanation/standby-loss` (the jacket loss, a circulation run's
|
|
603
|
+
heat and where it shows) and :doc:`/explanation/draws` (the delivered
|
|
604
|
+
temperature, the probes through a draw, how much hot water is left,
|
|
605
|
+
reading a cold-supply probe). The element pages gain hours and energy
|
|
606
|
+
by mode, the off point and the probe's lag; scheduling gains write and
|
|
607
|
+
entry timing; the simulator's Accuracy section gains time to ready and
|
|
608
|
+
the lower zone after a draw.
|
|
609
|
+
- **Where the simulator's defaults come from.** A table in
|
|
610
|
+
:doc:`/reference/simulator-inputs` gives each default parameter's
|
|
611
|
+
evidence: what it was measured or fitted on, its error, and how far
|
|
612
|
+
the per-retrain values move between retrains.
|
|
613
|
+
- **Accuracy figures corrected.** ``simulator.rst`` gave 18 % and the
|
|
614
|
+
13 and 45 min deficit-band errors as physics alone; they scored the
|
|
615
|
+
physics with a learned correction, and are removed. The "cold starts
|
|
616
|
+
about 55 min short" figure is replaced by the recorded errors by time
|
|
617
|
+
left. The lower trigger's in-window wait is given with its full count.
|
|
618
|
+
- ``unknown-values``, ``tank-energy`` and the MQTT protocol reference now
|
|
619
|
+
cite the current NaviLink app release they were checked against.
|
|
620
|
+
- ``docs/openapi.yaml`` and ``rest_api.rst`` describe every endpoint above
|
|
621
|
+
plus ``POST /auth/session/refresh`` and ``POST /user/secured-sign-in``;
|
|
622
|
+
``mqtt_protocol.rst`` documents ``res/ctrl-fail`` and the
|
|
623
|
+
``app-connection`` event / Last-Will payload; ``diagnose-mqtt.rst`` gains
|
|
624
|
+
"Why was my command rejected?"; ``maintenance.rst`` gains a "Maintenance
|
|
625
|
+
Log" section; ``authenticate.rst`` explains the password-free renewal
|
|
626
|
+
chain.
|
|
627
|
+
- **Less noise.** Implementation notes, the docs' own revision history
|
|
628
|
+
and where things were ported from are gone from the pages, and
|
|
629
|
+
section titles are plainer. The ``sim`` extra is listed under
|
|
630
|
+
:doc:`/reference/installation`'s optional dependencies.
|
|
631
|
+
- **Corrections from the device tests and the recorded history.** The
|
|
632
|
+
explanation pages, the how-tos, the protocol reference and the
|
|
633
|
+
simulator's docstrings said things later measurements contradict:
|
|
634
|
+
|
|
635
|
+
- High Demand's element starts somewhere in (0.2, 0.4] degC short,
|
|
636
|
+
not (0.2, 0.7]; Electric ran upper first in the commanded runs, but
|
|
637
|
+
the record also has the lower running alone; the element-run and
|
|
638
|
+
mid-stint counts are the record's.
|
|
639
|
+
- Inside a TOU window the upper tank starts a cycle when it is more
|
|
640
|
+
than 14.4 degF short, at the opening or later. A setpoint write
|
|
641
|
+
starts one in 120 of 122 writes leaving the upper tank short (not
|
|
642
|
+
about 70 %), a lowering to the upper tank or below in none, and in
|
|
643
|
+
``HEAT_PUMP`` a draw does not start one while the upper tank is at
|
|
644
|
+
or above the setpoint. The restart lockout measured 5.5 min.
|
|
645
|
+
- The simulator's accuracy figures are the ones measured with
|
|
646
|
+
start-up charged once (3.6 % and 4.4 % low; starts 31 of 40 held
|
|
647
|
+
out), and the heater's idle air reading is about 6 degF warm.
|
|
648
|
+
- ``HYBRID_BOOST_MODE`` is also Energy Saver with an element running;
|
|
649
|
+
``touStatus`` means TOU is enabled, not that a period is in force.
|
|
650
|
+
- A device has held at least 32 reservation entries and 20 TOU
|
|
651
|
+
periods; a list write can be lost with no error, so read it back;
|
|
652
|
+
a powered-off heater runs its reservation entries; forcing a full
|
|
653
|
+
recovery with ``tou_override_status`` is untested.
|
|
654
|
+
- The ambient effect on the compressor is about 1.18x, not 1.7x; the
|
|
655
|
+
standby soak's figures, the 60 h gap prior and the circulation runs
|
|
656
|
+
behind the lower-zone drop are stated as measured.
|
|
657
|
+
- **Docs that contradicted the code or the vendor's sources** (#155). Each
|
|
658
|
+
now has one value with its source:
|
|
659
|
+
|
|
660
|
+
- The mode-ID table in ``device_features.rst`` numbered Energy Saver 2,
|
|
661
|
+
High Demand 3 and Electric 4. The IDs are Electric 2, Energy Saver 3,
|
|
662
|
+
High Demand 4, as in ``DhwOperationSetting`` and the NaviLink app's
|
|
663
|
+
``MgppOperationMode``.
|
|
664
|
+
- ``track-energy.rst`` used ``comp_running_minute_total`` and
|
|
665
|
+
``heater1/2_running_minute_total``, which do not exist. Lifetime run
|
|
666
|
+
times come from the diagnostics query, in hours
|
|
667
|
+
(``DeviceDiagnostics.ta_data.cumulated_op_time_comp``, ``_uhe``,
|
|
668
|
+
``_lhe``).
|
|
669
|
+
- ``currentHeatUse`` is the ``HeatSource`` enum (0-3), not a bool.
|
|
670
|
+
- ``drEventStatus`` is the ``DREvent`` enum, not a bitfield.
|
|
671
|
+
- ``tou-recovery-cap.rst`` named ``dhw_outlet_temperature``, Home
|
|
672
|
+
Assistant's name for ``dhw_temperature``.
|
|
673
|
+
- Vacation is 1-99 days; the docs said 0-99 in some places, and the
|
|
674
|
+
library said 1-30.
|
|
675
|
+
- Anti-legionella heats to 140 degF (60 degC), defaults to a 7-day
|
|
676
|
+
period, is adjustable from 1 to 30 days and ships disabled (User's
|
|
677
|
+
Information Manual, section 3.14.2). The scheduling how-to said 158
|
|
678
|
+
degF, a 14-day default, periods up to 90 days and "mandatory".
|
|
679
|
+
- ``dhwTemperatureMin`` is the installer's minimum operation
|
|
680
|
+
temperature: 95-113 degF, factory default 95 degF. 104.9 degF was one
|
|
681
|
+
unit's setting, not a contradiction.
|
|
682
|
+
- ``ambientTemperature`` is the air around the unit, per the manual;
|
|
683
|
+
neither "intake" nor "outlet" is sourced. The heat pump runs only
|
|
684
|
+
between 41 and 113 degF.
|
|
685
|
+
- ``docs/explanation/advanced-features.rst`` is removed. Its weather
|
|
686
|
+
thresholds, COP figures, DR bitfield, stratification tables and code
|
|
687
|
+
sample were unsourced or wrong, and the sourced material is covered
|
|
688
|
+
by the protocol reference, ``what-starts-a-recovery`` and
|
|
689
|
+
``tank-energy``.
|
|
690
|
+
|
|
691
|
+
- **Flow-rate units corrected** (#154). The protocol reference said
|
|
692
|
+
``currentDhwFlowRate`` and ``recircDhwFlowRate`` were tenths of a GPM and
|
|
693
|
+
``cumulatedDhwFlowRate`` was gallons, while the properties treated them as
|
|
694
|
+
tenths of an LPM and as litres. The properties are right. Navien's
|
|
695
|
+
NaviLink app (2.01.02, decompiled) shows ``currentDhwFlowRate`` as
|
|
696
|
+
``raw / 10`` LPM in Celsius and ``raw / 3.785 / 10`` GPM in Fahrenheit.
|
|
697
|
+
The NWP500 manual lists the panel's water-flow reading in LPM even though
|
|
698
|
+
the panel defaults to GPM. The app's diagnostics screen shows the heater's
|
|
699
|
+
hot-water volume totals in whole litres in either setting. Flow rates are
|
|
700
|
+
0.1 LPM and the cumulative counter is whole litres in both unit systems.
|
|
701
|
+
``device_status.rst``, ``data_conversions.rst`` and ``mqtt_protocol.rst``
|
|
702
|
+
now say so, and ``DiagnosticsDhwUsage`` documents its units (litres, hours,
|
|
703
|
+
minutes, counts) instead of calling them unknown. ``unknown-values.rst``
|
|
704
|
+
notes that on the unit checked, ``cumulatedDhwFlowRate`` and every draw
|
|
705
|
+
counter read 0 after 392 days in use, so a 0 there does not mean no water
|
|
706
|
+
was used. Library output is unchanged. The raw fields no longer carry a
|
|
707
|
+
fixed ``"GPM"`` or ``"gal"`` ``unit_of_measurement``, which contradicted
|
|
708
|
+
their raw units. ``get_field_unit()``, which was already driven by
|
|
709
|
+
``device_class``, returns the same units as before. New tests pin both
|
|
710
|
+
flow properties to the app's formula in each unit system.
|
|
711
|
+
|
|
712
|
+
Version 9.4.3 (2026-10-01)
|
|
713
|
+
==========================
|
|
714
|
+
|
|
715
|
+
Changed
|
|
716
|
+
-------
|
|
717
|
+
- **Raw reservation writes are now validated** (#148).
|
|
718
|
+
``update_reservations()`` and ``update_reservations_confirmed()`` sent
|
|
719
|
+
their entry dicts unchecked, so an unknown mode such as 7, an hour of 99
|
|
720
|
+
or a setpoint of 200 (100 degC) went to the device as-is. A Python
|
|
721
|
+
``True`` for ``enable`` was worse: the confirmed helper read it as 1,
|
|
722
|
+
which means disabled. Every entry is now checked before anything is
|
|
723
|
+
sent, with the same rules as ``build_reservation_entry()``: the six
|
|
724
|
+
protocol fields present and plain integers, ``enable`` 1 or 2, ``week``
|
|
725
|
+
a day bitfield, ``hour`` 0-23, ``min`` 0-59 and ``mode`` a
|
|
726
|
+
``DhwOperationSetting`` id. ``param`` is held to the setpoint range the
|
|
727
|
+
heater reports in its feature data (``dhw_temperature_min_raw`` to
|
|
728
|
+
``dhw_temperature_max_raw``), which is requested if it is not cached;
|
|
729
|
+
clearing the schedule with an empty list needs none. A bad entry raises
|
|
730
|
+
``ParameterValidationError`` or ``RangeValidationError``, and missing
|
|
731
|
+
feature data raises ``DeviceCapabilityError``. Callers that wrote
|
|
732
|
+
entries outside these ranges and relied on the heater to clamp them now
|
|
733
|
+
get an error instead. The checks are also available on their own as
|
|
734
|
+
``nwp500.mqtt.control.validate_reservation_entries()``.
|
|
735
|
+
|
|
736
|
+
Documentation
|
|
737
|
+
-------------
|
|
738
|
+
- **New: what starts a recovery**
|
|
739
|
+
(``docs/explanation/what-starts-a-recovery.rst``). The lower-tank
|
|
740
|
+
turn-on setting is one trigger among several. Measured on one unit:
|
|
741
|
+
outside a TOU window the lower setting reads 104.9 degF at every
|
|
742
|
+
setpoint and never follows it; at a window's opening the upper tank
|
|
743
|
+
starts a cycle when it is well below the setpoint, and the lower trigger
|
|
744
|
+
waits for the close. **A control write re-evaluates**: outside a window,
|
|
745
|
+
in ``HEAT_PUMP`` with the compressor off, a setpoint write leaving the
|
|
746
|
+
upper tank below the new setpoint started the compressor within
|
|
747
|
+
2 minutes in 112 of 117 writes (a median 30 s later), lowerings
|
|
748
|
+
included. 46 of 81 mode writes made with the compressor off did the
|
|
749
|
+
same. A setpoint dropped well below the upper tank stopped a running
|
|
750
|
+
compressor within 5 s (one run). A lowered setpoint did not hold off a
|
|
751
|
+
draw-driven start (one run, in ``ENERGY_SAVER``). The page also covers
|
|
752
|
+
traps for anyone re-deriving this from history.
|
|
753
|
+
- **TOU window: the two open cases measured, and three more results.** In
|
|
754
|
+
``docs/how-to/schedule-operation.rst`` (one or two runs each): an entry
|
|
755
|
+
scheduled after a window fires on its minute; a mode held in a window
|
|
756
|
+
takes effect when the window ends naturally; writing ``HEAT_PUMP`` over
|
|
757
|
+
a held mode withdraws it; turning TOU back on inside a window drops a
|
|
758
|
+
running element mode; and the operation setting reports the configured
|
|
759
|
+
mode, not activity. The section's first measurement date is corrected to
|
|
760
|
+
local time (2026-09-19).
|
|
761
|
+
- **New: what the heating elements do**
|
|
762
|
+
(``docs/explanation/heating-elements.rst``). The four
|
|
763
|
+
``he*TempSetting`` fields describe the thermostat, and **on entry to
|
|
764
|
+
a mode the thermostat is not what runs the element**: in
|
|
765
|
+
Electric and High Demand the on and off settings are the same value,
|
|
766
|
+
and every ``he*DiffTempSetting`` reads 0. Measured on one unit by
|
|
767
|
+
commanding the modes and watching, the start differential is
|
|
768
|
+
**mode-dependent**, one bracket per mode: ``ELECTRIC`` at most
|
|
769
|
+
**0.3 degC** below the setpoint, ``HIGH_DEMAND`` within
|
|
770
|
+
**(0.2, 0.7] degC**, ``ENERGY_SAVER`` within **(0.8, 1.0] degC**.
|
|
771
|
+
Whether the first two share one threshold is an open question, not a
|
|
772
|
+
finding. At 0.7 degC short, High Demand runs the element and Energy
|
|
773
|
+
Saver does not. These
|
|
774
|
+
are **entry** thresholds, not thermostats: in High Demand's steady
|
|
775
|
+
state this unit's history has the element on in 1 of 47 minutes
|
|
776
|
+
0.2-0.3 degC short and 168 of 169 more than 2.2 degC short.
|
|
777
|
+
Neither differential is published anywhere in the status message.
|
|
778
|
+
|
|
779
|
+
**Later in a stint it depends on the mode.** From one
|
|
780
|
+
unit's recorded history, 17 of 23 mid-stint upper-element starts in
|
|
781
|
+
``ENERGY_SAVER`` have the probe in the 39.4-41.1 degC band where
|
|
782
|
+
``heUpperOnTempSetting`` rests, a median 0.2 degC below the field -
|
|
783
|
+
the element comes on as the probe reaches it. ``HIGH_DEMAND``, whose
|
|
784
|
+
ON setting tracks the setpoint, re-engages a median 2.1 degC *below*
|
|
785
|
+
it, which the field does not describe. So in ``ENERGY_SAVER`` entry is
|
|
786
|
+
the exception and the reference description is the rule; in
|
|
787
|
+
``HIGH_DEMAND`` it is not; ``ELECTRIC`` is uncharacterised.
|
|
788
|
+
``data_conversions.rst``'s warning now says which applies when.
|
|
789
|
+
A sampling note with it: the element runs in two very different
|
|
790
|
+
lengths. Of 115 upper-element runs over eight months, 20 lasted under
|
|
791
|
+
a minute (median 16 s), carrying 0.6 kWh against 93 kWh for the rest.
|
|
792
|
+
They are real elements, and not negligible to the probe: a 20 s burst
|
|
793
|
+
is ~0.3 degC. Because ``currentPower`` samples at a median 30 s, a
|
|
794
|
+
one-minute-resampled power series can miss one entirely, making a
|
|
795
|
+
5.5 kW element look like 0.5 kW. Resample power with a maximum.
|
|
796
|
+
|
|
797
|
+
Confirmed on the device, one unit, 2026-09-21: in ``ENERGY_SAVER``
|
|
798
|
+
the upper element engages near ``heUpperOnTempSetting`` rather than a
|
|
799
|
+
fixed gap below the setpoint (setpoint lowered to 42.0 degC; a gap
|
|
800
|
+
predicted 22 degC; it engaged at 39.8, three times). It then runs to
|
|
801
|
+
~0.9 degC under the setpoint-tracking ``heUpperOffTempSetting``, not
|
|
802
|
+
to a fixed point. Raising the setpoint in that mode starts the element
|
|
803
|
+
through the entry rule. And ``heUpperOnTempSetting`` rests at 40.5
|
|
804
|
+
degC reliably only in ``ENERGY_SAVER``; in ``HEAT_PUMP``, about half
|
|
805
|
+
the time.
|
|
806
|
+
|
|
807
|
+
Also documents Energy Saver's entry behaviour (the element engages
|
|
808
|
+
although ``heUpperOnTempSetting`` rests 33 degC below the tank), Electric's
|
|
809
|
+
upper-to-lower handover and the upper probe **falling** 1.9 degC while
|
|
810
|
+
the lower element runs, measured element power of 5,219 W against the
|
|
811
|
+
5,000 W rating, and that a mode write may not take effect while a TOU
|
|
812
|
+
window is in force. ``device_status.rst``'s Heating Elements notes now
|
|
813
|
+
point at it.
|
|
814
|
+
- **currentInstPower includes the heating elements.** The field
|
|
815
|
+
table and *Power and Energy Fields* in the protocol reference, and the
|
|
816
|
+
``current_inst_power`` field description, said it excludes element
|
|
817
|
+
power; ``track-energy.rst`` said it includes it. On a 240 V NWP500-65
|
|
818
|
+
it includes it: element-only minutes read about 5.1 kW, compressor
|
|
819
|
+
and element together about 5.5 kW, and the tank's heat gain agrees.
|
|
820
|
+
All four now say so, with the measurement and its limits (one model,
|
|
821
|
+
240 V only). (#139)
|
|
822
|
+
- **TOU does not suspend reservations.** ``schedule-operation.rst``
|
|
823
|
+
said twice that it does, and its own priority table said otherwise. On
|
|
824
|
+
one NWP500, inside an active peak window, a reservation entry moved the
|
|
825
|
+
setpoint at its scheduled minute, twice. What the window holds back is
|
|
826
|
+
the entry's **mode**, as it does a direct ``set_operation_mode()``.
|
|
827
|
+
The mode is held, not discarded: a mode written in the window took
|
|
828
|
+
effect when TOU was switched off, and the upper element came on. The
|
|
829
|
+
priority table and Important Notes are corrected, and a new section
|
|
830
|
+
gives the measurements and their limits. ``heating-elements.rst`` now
|
|
831
|
+
links to it instead of to the issue. (#141)
|
|
832
|
+
- **tank-energy: what selects the second branch, and where usable_energy
|
|
833
|
+
under-reads.** ``tank-energy.rst`` left open what selects
|
|
834
|
+
``full_recovery_energy``'s second branch. On one NWP500-65 over 50,819
|
|
835
|
+
``HEAT_PUMP`` minutes, ``hpUpperOnTempSetting`` separates the branches
|
|
836
|
+
with no exceptions: 104.9 degF on the primary, raised to about the
|
|
837
|
+
setpoint during the afternoon TOU window on the secondary.
|
|
838
|
+
``energy_to_setpoint`` shifts by the same 2 degC. Because of that,
|
|
839
|
+
``usable_energy`` does not always hold up across the branch. In the window it
|
|
840
|
+
caps at ``full_recovery_energy`` once ``energy_to_setpoint`` hits 0,
|
|
841
|
+
up to about 3.6 degF short of the setpoint, and under-reads by up to
|
|
842
|
+
561 Wh. The page and the ``usable_energy`` docstring now say so and
|
|
843
|
+
recommend the thermistors in that case. The ``full_recovery_energy``
|
|
844
|
+
and ``energy_to_setpoint`` descriptions in the model, the protocol
|
|
845
|
+
reference, the API reference and ``track-energy.rst`` now note the
|
|
846
|
+
window too. (#140)
|
|
7
847
|
|
|
8
848
|
Version 9.4.2 (2026-09-17)
|
|
9
849
|
==========================
|
|
@@ -39,7 +879,7 @@ Version 9.4.0 (2026-09-12)
|
|
|
39
879
|
==========================
|
|
40
880
|
|
|
41
881
|
This release realigns the MQTT command set with what the NaviLink app
|
|
42
|
-
|
|
882
|
+
sends. The app (version 2.03.00) was decompiled and its request
|
|
43
883
|
builder compared with the library; every query it sends now has a
|
|
44
884
|
method, six commands it never sends were removed, and the recirculation
|
|
45
885
|
schedule write was corrected. Breaking changes are listed under
|
|
@@ -202,8 +1042,8 @@ Fixed
|
|
|
202
1042
|
The cloud returns ``"error": {"errorCode": null}`` on some devices.
|
|
203
1043
|
:class:`~nwp500.models.DeviceErrorSummary` typed the field
|
|
204
1044
|
``ErrorCode | int``, so validation of the whole listing failed and every
|
|
205
|
-
caller
|
|
206
|
-
set up
|
|
1045
|
+
caller (including the Home Assistant integration, which could no longer
|
|
1046
|
+
set up) lost the device entirely. ``error_code`` is now
|
|
207
1047
|
``ErrorCode | int | None`` and defaults to ``None``: a null means the
|
|
208
1048
|
cloud reported no code, which is not the same claim as ``NO_ERROR``.
|
|
209
1049
|
Callers that treated the field as always-present should handle ``None``.
|
|
@@ -240,8 +1080,8 @@ Added
|
|
|
240
1080
|
:class:`~nwp500.models.DeviceInfo` gains ``model_type_code`` and
|
|
241
1081
|
``installer_id``. ``error`` makes the device's last recorded fault
|
|
242
1082
|
readable without an MQTT connection, including while the device is
|
|
243
|
-
offline. Every new field is optional, so responses that omit them
|
|
244
|
-
as ``/device/info``, which carries no ``error`` block
|
|
1083
|
+
offline. Every new field is optional, so responses that omit them (such
|
|
1084
|
+
as ``/device/info``, which carries no ``error`` block) parse unchanged.
|
|
245
1085
|
``error_code`` is typed ``ErrorCode | int`` (validated left to right, so a
|
|
246
1086
|
known code becomes an ``ErrorCode`` member and only an unknown one stays a
|
|
247
1087
|
plain int) so an unrecognised code cannot make a whole listing unparseable. ``docs/openapi.yaml`` is
|
|
@@ -258,17 +1098,17 @@ Removed
|
|
|
258
1098
|
seconds. ``ctrl/tou/rd`` with that command code is the *write* - it is what
|
|
259
1099
|
:meth:`~nwp500.NavienMqttClient.configure_tou_schedule` publishes and what
|
|
260
1100
|
the vendor app publishes from its TOU editor
|
|
261
|
-
(``TouScheduleViewmodel.setPublishMgppControlTou``)
|
|
1101
|
+
(``TouScheduleViewmodel.setPublishMgppControlTou``), and the device answers
|
|
262
1102
|
on ``res/tou/rd`` only to confirm such a write. The vendor app reads TOU
|
|
263
1103
|
over REST, which is the only TOU read the protocol has.
|
|
264
1104
|
|
|
265
1105
|
Beyond returning nothing, the call published a write-shaped command with no
|
|
266
|
-
schedule attached. This device ignored it
|
|
267
|
-
across repeated calls
|
|
1106
|
+
schedule attached. This device ignored it (``touStatus`` was unchanged
|
|
1107
|
+
across repeated calls), but a firmware that took it at face value could read
|
|
268
1108
|
it as "store an empty TOU schedule".
|
|
269
1109
|
|
|
270
1110
|
**Migration**: use :meth:`~nwp500.NavienAPIClient.get_tou_info`, which
|
|
271
|
-
returns the stored plan
|
|
1111
|
+
returns the stored plan: rate name, utility, ZIP code and the seasonal
|
|
272
1112
|
pricing intervals. The read itself is pure REST; it is keyed by the
|
|
273
1113
|
controller serial number, which only the MQTT device-info response
|
|
274
1114
|
publishes, so fetch that once and cache it.
|
|
@@ -284,12 +1124,12 @@ Fixed
|
|
|
284
1124
|
month list, so ``--months 5`` took the single-month branch and printed
|
|
285
1125
|
the same daily breakdown as ``--month 5`` instead of the monthly
|
|
286
1126
|
summary. The handler now takes an explicit ``daily`` flag set by the
|
|
287
|
-
option the user
|
|
1127
|
+
option the user passed. ``energy`` also rejects ``--month``
|
|
288
1128
|
and ``--months`` together rather than silently preferring ``--month``,
|
|
289
1129
|
and validates the ``--months`` range and format the way ``--month``
|
|
290
1130
|
already did. All of that validation now happens while Click parses the
|
|
291
1131
|
arguments, so a bad invocation is a usage error (exit code 2) before any
|
|
292
|
-
authentication or MQTT connection is attempted
|
|
1132
|
+
authentication or MQTT connection is attempted. Previously the command
|
|
293
1133
|
body raised ``ClickException`` after connecting, where ``async_command``'s
|
|
294
1134
|
catch-all reported it as an "Unexpected Error" with a traceback, and
|
|
295
1135
|
anyone without working credentials saw an authentication failure instead
|
|
@@ -309,11 +1149,11 @@ Fixed
|
|
|
309
1149
|
- **``firmware_payload_capture.py`` captured almost nothing.** Its wildcards
|
|
310
1150
|
covered ``cmd/{type}/{client_id}/res/#`` and the event topic, but the device
|
|
311
1151
|
acknowledges control and query commands on
|
|
312
|
-
``cmd/{type}/navilink-{mac}/{client_id}/res
|
|
313
|
-
matches
|
|
1152
|
+
``cmd/{type}/navilink-{mac}/{client_id}/res``, which neither pattern
|
|
1153
|
+
matches, so the device status and device info responses were missed, along
|
|
314
1154
|
with every request published on the device path. A capture run recorded 2
|
|
315
1155
|
payloads where it should have recorded 11. It now also subscribes to
|
|
316
|
-
``cmd/{type}/navilink-{mac}/#``, which
|
|
1156
|
+
``cmd/{type}/navilink-{mac}/#``, which also picks up traffic from
|
|
317
1157
|
other clients on the same device. Query results the device routes back to a
|
|
318
1158
|
client-keyed topic, such as reservations and energy usage, were already
|
|
319
1159
|
covered by the existing response wildcard.
|
|
@@ -323,8 +1163,8 @@ Version 9.3.0 (2026-08-03)
|
|
|
323
1163
|
|
|
324
1164
|
**BREAKING CHANGES**: two independent corrections land together.
|
|
325
1165
|
|
|
326
|
-
Tank energy values were wrong in two ways
|
|
327
|
-
two actively misleading field names
|
|
1166
|
+
Tank energy values were wrong in two ways, a 2.5x unit-scale error and
|
|
1167
|
+
two actively misleading field names, and both are corrected. Reported
|
|
328
1168
|
tank energy is now 2.5x smaller and two public field names are removed.
|
|
329
1169
|
|
|
330
1170
|
Separately, eight status flags change type from ``bool`` to
|
|
@@ -338,10 +1178,10 @@ Changed
|
|
|
338
1178
|
not. Because ``totalEnergyCapacity`` is a whole-tank quantity, its slope
|
|
339
1179
|
against the setpoint measures the quantum with no stratification
|
|
340
1180
|
assumption: on a 65-gallon NWP500 that is 70.25 raw counts per Kelvin.
|
|
341
|
-
The field
|
|
342
|
-
two values exactly 2 degC apart
|
|
1181
|
+
The field is bimodal (at a fixed setpoint it takes one of
|
|
1182
|
+
two values exactly 2 degC apart), but both branches give the same slope
|
|
343
1183
|
to within 0.2%, so the quantum is unaffected. Converting to Watt-hours needs a
|
|
344
|
-
water mass, and a "65 gallon" tank does not hold 65 gallons
|
|
1184
|
+
water mass, and a "65 gallon" tank does not hold 65 gallons, so taking
|
|
345
1185
|
the quantum to be round, as every other conversion in this protocol is,
|
|
346
1186
|
4 Wh/count is the only candidate implying a water volume below the
|
|
347
1187
|
nameplate (241.7 L). Two further checks agree: 183 individual heating
|
|
@@ -353,7 +1193,7 @@ Changed
|
|
|
353
1193
|
be comparable.
|
|
354
1194
|
|
|
355
1195
|
- **Energy fields renamed.**
|
|
356
|
-
``availableEnergyCapacity`` is not available energy
|
|
1196
|
+
``availableEnergyCapacity`` is not available energy: it is the energy
|
|
357
1197
|
still *needed* to reach the setpoint. It falls as the tank heats and
|
|
358
1198
|
reaches zero when the tank is fully charged, so code treating it as
|
|
359
1199
|
stored energy had the signal backwards (regression against mean tank
|
|
@@ -379,7 +1219,7 @@ Changed
|
|
|
379
1219
|
|
|
380
1220
|
- **Status flags now preserve the device's unknown state.** The protocol
|
|
381
1221
|
encodes these flags as ``0 = unknown, 1 = OFF, 2 = ON``, and the library
|
|
382
|
-
was collapsing 0 to ``False
|
|
1222
|
+
was collapsing 0 to ``False``, inventing an OFF the device never claimed.
|
|
383
1223
|
Confirmed against Navien's own NaviLink app (2.03.00, versionCode 141),
|
|
384
1224
|
which decodes exactly this set of fields through an enum declared
|
|
385
1225
|
``UNKNOWN(0), OFF(1), ON(2)``; two sibling enums render their zero as
|
|
@@ -409,7 +1249,7 @@ Added
|
|
|
409
1249
|
fields are measured from the setpoint, so neither is a state of charge;
|
|
410
1250
|
subtracting them cancels the setpoint and leaves the tank's heat above
|
|
411
1251
|
the device's minimum operating temperature (104.9 degF), which is about
|
|
412
|
-
the lowest temperature usable for a shower.
|
|
1252
|
+
the lowest temperature usable for a shower. It holds despite
|
|
413
1253
|
``full_recovery_energy`` being bimodal, since both fields shift
|
|
414
1254
|
together: the implied tank temperature tracks the thermistor mean to a
|
|
415
1255
|
standard deviation of 0.57 degF over 12275 samples. Rendered by the CLI
|
|
@@ -468,13 +1308,13 @@ Fixed
|
|
|
468
1308
|
in the changelog, the model field description and the protocol
|
|
469
1309
|
reference.
|
|
470
1310
|
- New ``docs/explanation/tank-energy.rst`` records what the two fields
|
|
471
|
-
|
|
1311
|
+
measure and the calibration evidence behind the scale change,
|
|
472
1312
|
including that Navien's own NaviLink app (2.03.00, versionCode 141)
|
|
473
1313
|
reads neither field, so no vendor-side corroboration exists.
|
|
474
1314
|
|
|
475
1315
|
- Documented that **temperature fields carry no sentinel at all**. The app
|
|
476
1316
|
has no out-of-band constant (no ``0xFFFF``/``-999``/``-1``), no zero-guard
|
|
477
|
-
in any display path, and formats whatever arrives
|
|
1317
|
+
in any display path, and formats whatever arrives, so a temperature of
|
|
478
1318
|
zero means zero. This closes a recurring source of bugs where zero-as-none
|
|
479
1319
|
was applied to temperature converters and reported working sensors as
|
|
480
1320
|
missing during cold-weather operation.
|
|
@@ -539,7 +1379,7 @@ Documentation
|
|
|
539
1379
|
(``models/schedule.py``, ``models/_converters.py``). Corrected to
|
|
540
1380
|
"target temperature, half-degrees Celsius; °C = param/2", and fixed the
|
|
541
1381
|
accompanying doctest-style example, which showed a ``minute`` key where
|
|
542
|
-
the function
|
|
1382
|
+
the function returns ``min``.
|
|
543
1383
|
|
|
544
1384
|
Version 9.2.0 (2026-07-06)
|
|
545
1385
|
==========================
|
|
@@ -572,7 +1412,7 @@ Changed
|
|
|
572
1412
|
(``mqtt/_control_commands.py``) and ``DeviceSubscriptionsMixin``
|
|
573
1413
|
(``mqtt/_device_subscriptions.py``). ``NavienMqttClient`` now inherits
|
|
574
1414
|
both, so its public API is unchanged, while ``mqtt/client.py`` shrinks
|
|
575
|
-
from ~1572 to ~1196 lines and reads
|
|
1415
|
+
from ~1572 to ~1196 lines and reads as connection
|
|
576
1416
|
orchestration plus a public façade. No behavior change.
|
|
577
1417
|
- **Event system relationship clarified and de-duplicated** (`#102
|
|
578
1418
|
<https://github.com/eman/nwp500-python/issues/102>`_): ``events.py`` and
|
|
@@ -757,8 +1597,8 @@ Bug Fixes
|
|
|
757
1597
|
``configure_recirculation_schedule()`` dumped the schedule models
|
|
758
1598
|
as-is, double-nesting the request (``request.reservation.reservation``
|
|
759
1599
|
/ ``request.schedule.schedule``) and leaking pydantic computed display
|
|
760
|
-
fields
|
|
761
|
-
half-Celsius ``param``
|
|
1600
|
+
fields (including a unit-converted ``temperature`` alongside the raw
|
|
1601
|
+
half-Celsius ``param``) into device commands. Both now send the flat,
|
|
762
1602
|
raw protocol shape used by ``update_reservations()``. A new
|
|
763
1603
|
``NavienBaseModel.to_protocol_dict()`` dumps only declared protocol
|
|
764
1604
|
fields.
|
|
@@ -786,7 +1626,7 @@ Bug Fixes
|
|
|
786
1626
|
so consumers kept displaying the stale error.
|
|
787
1627
|
- **Fix TOU price encoding**: ``encode_price()`` used banker's rounding,
|
|
788
1628
|
under-encoding exact half values at even boundaries (0.125 at
|
|
789
|
-
``decimal_point=2`` encoded to 12 instead of 13)
|
|
1629
|
+
``decimal_point=2`` encoded to 12 instead of 13). It now uses
|
|
790
1630
|
``Decimal`` half-up rounding. ``build_tou_period()`` also treated
|
|
791
1631
|
``bool`` prices as pre-encoded integers (``True`` sent as price 1).
|
|
792
1632
|
- **Fix protocol documentation errors**: ``decode_reservation_hex()``
|
|
@@ -801,7 +1641,7 @@ Bug Fixes
|
|
|
801
1641
|
an undocumented SDK thread where asyncio operations are unsafe, and the
|
|
802
1642
|
handler registry could be mutated on the event loop while the CRT
|
|
803
1643
|
thread iterated it (``RuntimeError: dictionary changed size during
|
|
804
|
-
iteration``, dropping the message
|
|
1644
|
+
iteration``, dropping the message, most likely during the
|
|
805
1645
|
reconnect/resubscribe window). The awscrt callback now only marshals
|
|
806
1646
|
the raw payload onto the event loop; parsing and dispatch run there,
|
|
807
1647
|
iterating snapshots of the handler registries.
|
|
@@ -814,7 +1654,7 @@ Bug Fixes
|
|
|
814
1654
|
``set_unit_system()`` stored the preference in a ``ContextVar`` set in
|
|
815
1655
|
the caller's task. Tasks scheduled from AWS CRT callback threads never
|
|
816
1656
|
inherit that context, so the preference silently reverted to
|
|
817
|
-
auto-detect for all MQTT-delivered data
|
|
1657
|
+
auto-detect for all MQTT-delivered data: ``nwp-cli --unit-system
|
|
818
1658
|
metric monitor`` logged temperatures in the device's native unit while
|
|
819
1659
|
labeling them °C. The preference is now a process-wide setting visible
|
|
820
1660
|
from every task and thread.
|
|
@@ -830,7 +1670,7 @@ Bug Fixes
|
|
|
830
1670
|
``finally`` block.
|
|
831
1671
|
- **Fix wait_for() docstring examples**: examples showed
|
|
832
1672
|
``args, _ = await emitter.wait_for(...)``, but ``wait_for`` returns
|
|
833
|
-
just the args tuple
|
|
1673
|
+
just the args tuple, so following the documented pattern raised
|
|
834
1674
|
``ValueError`` or silently mis-assigned.
|
|
835
1675
|
- **Serialize concurrent token refresh**: ``ensure_valid_token()`` and
|
|
836
1676
|
``refresh_token()`` had no lock, so concurrent callers (API 401 retry,
|
|
@@ -855,7 +1695,7 @@ Bug Fixes
|
|
|
855
1695
|
- **Make** ``NavienAuthClient.__aenter__`` **idempotent**:
|
|
856
1696
|
``create_navien_clients()`` pre-enters the context and its docstring
|
|
857
1697
|
instructs users to enter again with ``async with auth:``; the second
|
|
858
|
-
entry created a fresh session and orphaned the first
|
|
1698
|
+
entry created a fresh session and orphaned the first, which the API
|
|
859
1699
|
client was still pinned to. Re-entering now reuses the existing
|
|
860
1700
|
session.
|
|
861
1701
|
- **Fall back to full sign-in when stored-token refresh fails**:
|
|
@@ -964,7 +1804,7 @@ Bug Fixes
|
|
|
964
1804
|
(e.g. the sleeping task's timer callback had already been enqueued), the
|
|
965
1805
|
cancellation was silently dropped. The stale ``_reconnect_with_backoff`` task
|
|
966
1806
|
would then complete its sleep, call ``_reconnect_func``, and tear down an
|
|
967
|
-
otherwise healthy connection
|
|
1807
|
+
otherwise healthy connection, restarting the entire disconnect → reconnect →
|
|
968
1808
|
``AWS_ERROR_MQTT_UNEXPECTED_HANGUP`` cycle. Fixed by replacing the direct
|
|
969
1809
|
``task.cancel()`` call with a ``_cancel_pending_reconnect()`` coroutine scheduled
|
|
970
1810
|
via ``_schedule_coroutine``, so the cancellation runs on the event loop where
|
|
@@ -982,7 +1822,7 @@ Bug Fixes
|
|
|
982
1822
|
SDK connection's built-in auto-reconnect would eventually succeed, creating two
|
|
983
1823
|
active connections sharing the same client ID. Because AWS IoT allows only one
|
|
984
1824
|
connection per client ID, the broker would kick one off, triggering
|
|
985
|
-
``on_connection_interrupted`` and starting yet another reconnection
|
|
1825
|
+
``on_connection_interrupted`` and starting yet another reconnection: an
|
|
986
1826
|
infinite connect/disconnect loop. Fixed by adding ``MqttConnection.close()``
|
|
987
1827
|
(unconditional teardown regardless of ``_connected`` state) and calling it
|
|
988
1828
|
before creating the replacement connection in both ``_active_reconnect()`` and
|
|
@@ -1014,9 +1854,9 @@ Bug Fixes
|
|
|
1014
1854
|
a different loop in multi-loop test setups.
|
|
1015
1855
|
|
|
1016
1856
|
- **Reservation temperature validation was US-only**: ``build_reservation_entry``
|
|
1017
|
-
validated set-point temperatures against hardcoded Fahrenheit bounds (95
|
|
1857
|
+
validated set-point temperatures against hardcoded Fahrenheit bounds (95-150 °F)
|
|
1018
1858
|
regardless of the active unit system. Validation now uses the current unit system
|
|
1019
|
-
context: 35
|
|
1859
|
+
context: 35-65 °C in metric mode, 95-150 °F in US mode. Celsius users previously
|
|
1020
1860
|
received spurious ``ValueError`` rejections for valid temperatures.
|
|
1021
1861
|
|
|
1022
1862
|
- **Malformed reservation data silently dropped**: ``build_reservation_entry`` now
|
|
@@ -1104,11 +1944,11 @@ integrations like Home Assistant:
|
|
|
1104
1944
|
2. **Refactor Control Calls**: Remove ``.control`` from all device command invocations.
|
|
1105
1945
|
Instead of ``await mqtt.control.set_power(...)``, use ``await mqtt.set_power(...)``.
|
|
1106
1946
|
3. **Handle Unit Conversions**: If your integration previously performed its own
|
|
1107
|
-
conversions or relied on the library's eager conversion
|
|
1947
|
+
conversions or relied on the library's eager conversion:
|
|
1108
1948
|
``DeviceStatus`` fields like ``dhw_temperature`` are now properties. They return
|
|
1109
1949
|
values based on the global unit system context (``us_customary`` by default).
|
|
1110
1950
|
|
|
1111
|
-
* **Home Assistant Tip**: To
|
|
1951
|
+
* **Home Assistant Tip**: To keep your state tracking immune to unit system
|
|
1112
1952
|
toggles within the library, use the new ``*_raw`` fields (e.g.,
|
|
1113
1953
|
``status.dhw_temperature_raw``) for comparison logic, and use the properties
|
|
1114
1954
|
only for display or when a converted value is explicitly needed.
|
|
@@ -1193,7 +2033,7 @@ Fixed
|
|
|
1193
2033
|
``are_aws_credentials_expired``, and ``time_until_expiry`` used
|
|
1194
2034
|
``datetime.now()`` (naive, local time). During DST transitions or timezone
|
|
1195
2035
|
changes this could cause incorrect expiry detection, leading to premature
|
|
1196
|
-
re-authentication or use of an
|
|
2036
|
+
re-authentication or use of an expired token. Fixed by using
|
|
1197
2037
|
``datetime.now(UTC)`` throughout, switching the ``issued_at`` field default
|
|
1198
2038
|
to ``datetime.now(UTC)``, and adding a field validator to normalize any
|
|
1199
2039
|
timezone-naive ``issued_at`` values loaded from old stored token files to UTC
|
|
@@ -1203,11 +2043,11 @@ Fixed
|
|
|
1203
2043
|
format written by ``to_dict()`` for tokens stored before this fix.
|
|
1204
2044
|
- **Vacation mode sent wrong MQTT command**: ``set_vacation_days()`` used
|
|
1205
2045
|
``CommandCode.GOOUT_DAY`` (33554466), which the device silently accepted
|
|
1206
|
-
but did not activate vacation mode
|
|
2046
|
+
but did not activate vacation mode; the operating mode remained unchanged.
|
|
1207
2047
|
HAR capture of the official Navien app confirms the correct command is
|
|
1208
2048
|
``DHW_MODE`` (33554437) with ``param=[5, days]``
|
|
1209
2049
|
(``DhwOperationSetting.VACATION``). The valid range has also been corrected
|
|
1210
|
-
from 1
|
|
2050
|
+
from 1-365 to 1-30 to match the device's actual constraint.
|
|
1211
2051
|
- **Duplicate AWS IoT subscribe calls on reconnect**: ``resubscribe_all()``
|
|
1212
2052
|
called ``connection.subscribe()`` (a network round-trip to AWS IoT) once per
|
|
1213
2053
|
handler per topic. If a topic had N handlers, N identical subscribe requests
|
|
@@ -1230,7 +2070,7 @@ Fixed
|
|
|
1230
2070
|
- **Once-Listener Becomes Permanent With Duplicate Callbacks**: ``emit()``
|
|
1231
2071
|
identified once-listeners via a ``set`` of ``(event, callback)`` tuples. If
|
|
1232
2072
|
the same callback was registered twice with ``once=True``, the set
|
|
1233
|
-
deduplicated the tuple
|
|
2073
|
+
deduplicated the tuple: after the first emit the second listener lost its
|
|
1234
2074
|
once-status and became permanent. Fixed by checking ``listener.once``
|
|
1235
2075
|
directly on the ``EventListener`` object.
|
|
1236
2076
|
- **Auth Session Leaked on Client Construction Failure**: In
|
|
@@ -1239,10 +2079,10 @@ Fixed
|
|
|
1239
2079
|
``auth_client.__aenter__()``, the auth session and its underlying
|
|
1240
2080
|
``aiohttp`` session would leak. Client construction is now wrapped in a
|
|
1241
2081
|
``try/except`` that calls ``auth_client.__aexit__()`` on failure.
|
|
1242
|
-
|
|
2082
|
+
Both ``except BaseException`` blocks have been replaced with
|
|
1243
2083
|
``except Exception`` (passing real exception info to ``__aexit__``) plus a
|
|
1244
2084
|
separate ``except asyncio.CancelledError`` block that uses
|
|
1245
|
-
``asyncio.shield()``
|
|
2085
|
+
``asyncio.shield()`` so cleanup completes even when the task is
|
|
1246
2086
|
being cancelled.
|
|
1247
2087
|
- **Hypothesis Tests Broke All Test Collection**: ``test_mqtt_hypothesis.py``
|
|
1248
2088
|
imported ``hypothesis`` at module level; when it was not installed, pytest
|
|
@@ -1291,7 +2131,7 @@ Changed
|
|
|
1291
2131
|
|
|
1292
2132
|
Fixed
|
|
1293
2133
|
-----
|
|
1294
|
-
- **Week Bitfield Encoding
|
|
2134
|
+
- **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.
|
|
1295
2135
|
- **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``.
|
|
1296
2136
|
- **Reservation Set Command Timeout**: Fixed ``reservations set`` subscription pattern that had extra wildcards preventing response matching. Command now receives confirmations correctly.
|
|
1297
2137
|
- **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.
|
|
@@ -1431,13 +2271,13 @@ Changed
|
|
|
1431
2271
|
|
|
1432
2272
|
Fixed
|
|
1433
2273
|
-----
|
|
1434
|
-
- **
|
|
2274
|
+
- **Temperature Unit Bug in Set Operations**: Fixed incorrect temperature conversion when setting DHW temperature and reservations
|
|
1435
2275
|
|
|
1436
2276
|
- ``set_dhw_temperature()`` was calling ``fahrenheit_to_half_celsius()`` with unit-agnostic temperature parameter
|
|
1437
2277
|
- ``build_reservation_entry()`` had the same issue
|
|
1438
2278
|
- **Impact**: If user preferred Celsius, temperature would be interpreted as Fahrenheit, causing wrong setpoints
|
|
1439
2279
|
- **Fix**: Use new ``preferred_to_half_celsius()`` that respects unit system context
|
|
1440
|
-
- This was a
|
|
2280
|
+
- This was a data correctness bug that would cause incorrect device behavior for Celsius users
|
|
1441
2281
|
|
|
1442
2282
|
Version 7.3.2 (2026-01-25)
|
|
1443
2283
|
==========================
|
|
@@ -1487,7 +2327,7 @@ Added
|
|
|
1487
2327
|
- Available devices and regions determine conversion support (validated at runtime)
|
|
1488
2328
|
- ``get_field_unit()`` method retrieves the correct unit suffix for any field
|
|
1489
2329
|
- All conversions use Pydantic ``WrapValidator`` for transparent, automatic conversion
|
|
1490
|
-
-
|
|
2330
|
+
- Documentation in ``docs/guides/unit_conversion.rst``
|
|
1491
2331
|
- Example usage:
|
|
1492
2332
|
|
|
1493
2333
|
.. code-block:: python
|
|
@@ -1542,7 +2382,7 @@ Fixed
|
|
|
1542
2382
|
|
|
1543
2383
|
- **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")
|
|
1544
2384
|
- **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
|
|
1545
|
-
- **Solution**: Store unit_system in ``NavienMqttClient`` and ``MqttSubscriptionManager``. Before parsing MQTT messages, explicitly set the context variable in message handlers
|
|
2385
|
+
- **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
|
|
1546
2386
|
- **Result**: Values and units now correctly convert when ``--unit-system`` override is specified
|
|
1547
2387
|
- **Testing**: All 393 tests pass including new unit system context override tests
|
|
1548
2388
|
|
|
@@ -1704,7 +2544,7 @@ Removed
|
|
|
1704
2544
|
- ``DeviceCapabilityChecker`` → ``MqttDeviceCapabilityChecker``
|
|
1705
2545
|
- ``DeviceInfoCache`` → ``MqttDeviceInfoCache``
|
|
1706
2546
|
|
|
1707
|
-
All functionality remains identical
|
|
2547
|
+
All functionality remains identical; only the class names changed.
|
|
1708
2548
|
|
|
1709
2549
|
Added
|
|
1710
2550
|
-----
|
|
@@ -1725,7 +2565,7 @@ Added
|
|
|
1725
2565
|
- Simplified initialization for common use case (API + MQTT)
|
|
1726
2566
|
- Proper async context manager support
|
|
1727
2567
|
- Reduces boilerplate in application code
|
|
1728
|
-
-
|
|
2568
|
+
- Documentation in ``docs/guides/authentication.rst``
|
|
1729
2569
|
- Example: ``examples/intermediate/advanced_auth_patterns.py``
|
|
1730
2570
|
|
|
1731
2571
|
- **VolumeCode Enum**: Tank capacity identification with gallon values
|
|
@@ -1760,7 +2600,7 @@ Added
|
|
|
1760
2600
|
- ``tou_override_to_python()``: TOU override status conversion
|
|
1761
2601
|
- ``div_10()``: Divide by 10.0 utility
|
|
1762
2602
|
- ``enum_validator()``: Generic enum factory
|
|
1763
|
-
-
|
|
2603
|
+
- Documentation explaining device protocol quirks
|
|
1764
2604
|
- New ``converters.py`` module replacing scattered validators
|
|
1765
2605
|
|
|
1766
2606
|
- **MQTT Event System**: Structured event handling for MQTT operations
|
|
@@ -1786,7 +2626,7 @@ Added
|
|
|
1786
2626
|
- Eliminates hardcoded units in output formatter
|
|
1787
2627
|
- Single source of truth: model metadata drives CLI display
|
|
1788
2628
|
|
|
1789
|
-
- **
|
|
2629
|
+
- **Protocol Documentation**: Complete protocol reference documentation
|
|
1790
2630
|
|
|
1791
2631
|
- New ``docs/protocol/quick_reference.rst`` with command codes, field formats, and conversions
|
|
1792
2632
|
- Converted protocol documentation to RST format for Sphinx integration
|
|
@@ -1852,7 +2692,7 @@ Changed
|
|
|
1852
2692
|
- Added factory function patterns and examples
|
|
1853
2693
|
- Improved context manager documentation
|
|
1854
2694
|
- Added best practices and common patterns
|
|
1855
|
-
- More
|
|
2695
|
+
- More code examples
|
|
1856
2696
|
|
|
1857
2697
|
- **Model Refactoring**: Updated to use new converter modules
|
|
1858
2698
|
|
|
@@ -1932,7 +2772,7 @@ Added
|
|
|
1932
2772
|
- ``configure_reservation_water_program()``: Water program reservation management
|
|
1933
2773
|
- ``set_recirculation_mode()`` / ``configure_recirculation_schedule()`` / ``trigger_recirculation_hot_button()``: Recirculation pump control and scheduling
|
|
1934
2774
|
|
|
1935
|
-
- **CLI Documentation Updates**:
|
|
2775
|
+
- **CLI Documentation Updates**: Documentation updates for subcommand-based CLI
|
|
1936
2776
|
- Complete rewrite of ``docs/python_api/cli.rst`` with full command reference
|
|
1937
2777
|
- Updated README.rst with new subcommand syntax and examples
|
|
1938
2778
|
- Added 8+ practical usage examples (cron jobs, automation, monitoring)
|
|
@@ -2015,14 +2855,14 @@ Removed
|
|
|
2015
2855
|
Added
|
|
2016
2856
|
-----
|
|
2017
2857
|
|
|
2018
|
-
- **Python 3.12+ Optimizations**:
|
|
2858
|
+
- **Python 3.12+ Optimizations**: Use the latest Python features
|
|
2019
2859
|
|
|
2020
2860
|
- PEP 695: New type parameter syntax (``def func[T](...)`` instead of ``TypeVar``)
|
|
2021
2861
|
- Use ``datetime.UTC`` constant instead of ``datetime.timezone.utc``
|
|
2022
2862
|
- Native union syntax (``X | Y`` instead of ``Union[X, Y]``)
|
|
2023
2863
|
- Cleaner generic type annotations throughout codebase
|
|
2024
2864
|
|
|
2025
|
-
- **Enumerations Module (``src/nwp500/enums.py``)**:
|
|
2865
|
+
- **Enumerations Module (``src/nwp500/enums.py``)**: Type-safe enums for device control and status
|
|
2026
2866
|
|
|
2027
2867
|
- Status value enums: ``OnOffFlag``, ``Operation``, ``DhwOperationSetting``, ``CurrentOperationMode``, ``HeatSource``, ``DREvent``, ``WaterLevel``, ``FilterChange``, ``RecirculationMode``
|
|
2028
2868
|
- Time of Use enums: ``TouWeekType``, ``TouRateType``
|
|
@@ -2031,7 +2871,7 @@ Added
|
|
|
2031
2871
|
- Error code enum: ``ErrorCode`` with complete error code mappings
|
|
2032
2872
|
- Human-readable text mappings for all enums (e.g., ``DHW_OPERATION_SETTING_TEXT``, ``ERROR_CODE_TEXT``)
|
|
2033
2873
|
- Exported from main package: ``from nwp500 import OnOffFlag, ErrorCode, CommandCode``
|
|
2034
|
-
-
|
|
2874
|
+
- Documentation in ``docs/enumerations.rst``
|
|
2035
2875
|
- Example usage in ``examples/error_code_demo.py``
|
|
2036
2876
|
|
|
2037
2877
|
Changed
|
|
@@ -2058,7 +2898,7 @@ Changed
|
|
|
2058
2898
|
- CLI and other consumers benefit from automatic enum name serialization
|
|
2059
2899
|
- Text mappings available for custom formatting (e.g., `DHW_OPERATION_TEXT[enum]` → "Heat Pump Only")
|
|
2060
2900
|
|
|
2061
|
-
- **Documentation**:
|
|
2901
|
+
- **Documentation**: Updates across protocol and API documentation
|
|
2062
2902
|
|
|
2063
2903
|
- ``docs/guides/time_of_use.rst``: Clarified TOU override status behavior (1=OFF/override active, 2=ON/normal operation)
|
|
2064
2904
|
- ``docs/protocol/data_conversions.rst``: Updated TOU field descriptions with correct enum values
|
|
@@ -2099,7 +2939,7 @@ Added
|
|
|
2099
2939
|
- Export diagnostics to JSON for analysis and debugging
|
|
2100
2940
|
- Home Assistant integration support for custom components
|
|
2101
2941
|
- ``examples/mqtt_diagnostics_example.py`` demonstrating usage patterns
|
|
2102
|
-
-
|
|
2942
|
+
- Documentation in ``docs/MQTT_DIAGNOSTICS.rst``
|
|
2103
2943
|
- Exported from main package: ``from nwp500 import MqttDiagnosticsCollector``
|
|
2104
2944
|
|
|
2105
2945
|
Version 6.1.0 (2025-12-03)
|
|
@@ -2187,7 +3027,7 @@ Version 6.0.6 (2025-11-24)
|
|
|
2187
3027
|
Added
|
|
2188
3028
|
-----
|
|
2189
3029
|
|
|
2190
|
-
- **Field Descriptions**: Added
|
|
3030
|
+
- **Field Descriptions**: Added Field descriptions to ``DeviceStatus`` and ``DeviceFeature`` models with full documentation details including units, ranges, and usage context
|
|
2191
3031
|
|
|
2192
3032
|
Fixed
|
|
2193
3033
|
-----
|
|
@@ -2205,7 +3045,7 @@ Version 6.0.5 (2025-11-21)
|
|
|
2205
3045
|
Fixed
|
|
2206
3046
|
-----
|
|
2207
3047
|
|
|
2208
|
-
- **
|
|
3048
|
+
- **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:
|
|
2209
3049
|
|
|
2210
3050
|
- ``tank_upper_temperature`` - Water tank upper sensor
|
|
2211
3051
|
- ``tank_lower_temperature`` - Water tank lower sensor
|
|
@@ -2432,7 +3272,7 @@ Fixed
|
|
|
2432
3272
|
- Applied consistent cancellation handling across all MQTT operations (connect, disconnect, subscribe, unsubscribe, publish)
|
|
2433
3273
|
- AWS CRT callbacks can now complete independently without raising InvalidStateError
|
|
2434
3274
|
- Added debug logging when operations are cancelled for better diagnostics
|
|
2435
|
-
-
|
|
3275
|
+
- Clean shutdown without spurious exception messages
|
|
2436
3276
|
|
|
2437
3277
|
Version 5.0.1 (2025-10-27)
|
|
2438
3278
|
==========================
|
|
@@ -2455,7 +3295,7 @@ Changed
|
|
|
2455
3295
|
Version 5.0.0 (2025-10-27)
|
|
2456
3296
|
==========================
|
|
2457
3297
|
|
|
2458
|
-
**BREAKING CHANGES**: This release introduces
|
|
3298
|
+
**BREAKING CHANGES**: This release introduces an enterprise exception architecture.
|
|
2459
3299
|
See migration guide below for details on updating your code.
|
|
2460
3300
|
|
|
2461
3301
|
Added
|
|
@@ -2463,7 +3303,7 @@ Added
|
|
|
2463
3303
|
|
|
2464
3304
|
- **Enterprise Exception Architecture**: Complete exception hierarchy for better error handling
|
|
2465
3305
|
|
|
2466
|
-
- Created ``exceptions.py`` module with
|
|
3306
|
+
- Created ``exceptions.py`` module with an exception hierarchy
|
|
2467
3307
|
- Added ``Nwp500Error`` as base exception for all library errors
|
|
2468
3308
|
- Added MQTT-specific exceptions: ``MqttError``, ``MqttConnectionError``, ``MqttNotConnectedError``,
|
|
2469
3309
|
``MqttPublishError``, ``MqttSubscriptionError``, ``MqttCredentialsError``
|
|
@@ -2472,9 +3312,9 @@ Added
|
|
|
2472
3312
|
``DeviceOperationError``
|
|
2473
3313
|
- All exceptions now include ``error_code``, ``details``, and ``retriable`` attributes
|
|
2474
3314
|
- Added ``to_dict()`` method to all exceptions for structured logging
|
|
2475
|
-
- Added
|
|
2476
|
-
- **Added
|
|
2477
|
-
- **Updated
|
|
3315
|
+
- Added a test suite in ``tests/test_exceptions.py``
|
|
3316
|
+
- **Added an exception handling example** (``examples/exception_handling_example.py``)
|
|
3317
|
+
- **Updated the main examples** to demonstrate new exception handling patterns
|
|
2478
3318
|
|
|
2479
3319
|
Changed
|
|
2480
3320
|
-------
|
|
@@ -2721,7 +3561,7 @@ Fixed
|
|
|
2721
3561
|
- Preserves AWS credential expiration timestamps during token refresh
|
|
2722
3562
|
- Prevents reconnection failures when connection interrupts after AWS credentials expire but before JWT tokens expire
|
|
2723
3563
|
- Resolves AWS_ERROR_HTTP_WEBSOCKET_UPGRADE_FAILURE errors during reconnection attempts
|
|
2724
|
-
- Improved test coverage for auth module from 31% to 60%
|
|
3564
|
+
- Improved test coverage for auth module from 31% to 60%
|
|
2725
3565
|
|
|
2726
3566
|
Version 3.1.3 (2025-10-24)
|
|
2727
3567
|
==========================
|
|
@@ -2769,7 +3609,7 @@ Fixed
|
|
|
2769
3609
|
|
|
2770
3610
|
- Updated callback to match latest AWS IoT SDK signature: ``(connection, error, **kwargs)``
|
|
2771
3611
|
- Fixed type annotations in ``MqttConnection`` for proper type checking
|
|
2772
|
-
- Resolves mypy type checking errors and
|
|
3612
|
+
- Resolves mypy type checking errors and keeps AWS SDK compatibility
|
|
2773
3613
|
- Fixed E501 line length linting issue in connection interruption handler
|
|
2774
3614
|
|
|
2775
3615
|
Version 3.0.0 (Unreleased)
|
|
@@ -2813,7 +3653,7 @@ Added
|
|
|
2813
3653
|
- Prevents accidental comparison of user preferences with real-time states
|
|
2814
3654
|
- Better IDE support with more specific enum types
|
|
2815
3655
|
|
|
2816
|
-
- **Migration Support**:
|
|
3656
|
+
- **Migration Support**: Tools for migration
|
|
2817
3657
|
|
|
2818
3658
|
- ``migrate_operation_mode_usage()`` helper function with programmatic guidance
|
|
2819
3659
|
- ``MIGRATION.md`` with step-by-step migration instructions
|
|
@@ -2865,12 +3705,12 @@ Version 0.2 (Unreleased)
|
|
|
2865
3705
|
Added
|
|
2866
3706
|
-----
|
|
2867
3707
|
|
|
2868
|
-
- **Local/CI Linting Synchronization**:
|
|
3708
|
+
- **Local/CI Linting Synchronization**: Tooling for consistent linting results
|
|
2869
3709
|
|
|
2870
3710
|
- Multiple sync methods: tox (recommended), direct scripts, pre-commit hooks, Makefile commands
|
|
2871
3711
|
- CI-identical scripts: ``scripts/lint.py`` and ``scripts/format.py`` mirror ``tox -e lint`` and ``tox -e format``
|
|
2872
3712
|
- Pre-commit hooks configuration for automatic checking
|
|
2873
|
-
-
|
|
3713
|
+
- Documentation: ``LINTING_SETUP.md``, ``DEVELOPMENT.md``, ``FIX_LINTING.md``
|
|
2874
3714
|
- Makefile commands: ``make ci-lint``, ``make ci-format``, ``make ci-check``
|
|
2875
3715
|
- Standardized ruff configuration across all environments
|
|
2876
3716
|
- Eliminates "passes locally but fails in CI" issues
|
|
@@ -2921,7 +3761,7 @@ Added
|
|
|
2921
3761
|
- Enabled by default for reliability
|
|
2922
3762
|
- ``queued_commands_count`` property for monitoring
|
|
2923
3763
|
- ``clear_command_queue()`` method for manual management
|
|
2924
|
-
-
|
|
3764
|
+
- Works with automatic reconnection
|
|
2925
3765
|
- Example: ``command_queue_demo.py``
|
|
2926
3766
|
- Documentation: ``COMMAND_QUEUE.rst``
|
|
2927
3767
|
|
|
@@ -2960,7 +3800,7 @@ Added
|
|
|
2960
3800
|
- ``subscribe_device_feature()`` - Automatic parsing of feature messages into ``DeviceFeature`` objects
|
|
2961
3801
|
- ``subscribe_energy_usage()`` - Automatic parsing of energy usage responses into ``EnergyUsageResponse`` objects
|
|
2962
3802
|
- Type-safe callbacks with IDE autocomplete support
|
|
2963
|
-
-
|
|
3803
|
+
- Error handling and logging
|
|
2964
3804
|
- Example scripts demonstrating usage patterns
|
|
2965
3805
|
|
|
2966
3806
|
- **Energy Usage API (EMS)**: Historical energy consumption data
|
|
@@ -2975,7 +3815,7 @@ Added
|
|
|
2975
3815
|
- Energy consumption data (Watt-hours)
|
|
2976
3816
|
- Efficiency percentage calculations
|
|
2977
3817
|
|
|
2978
|
-
- **Data Models**:
|
|
3818
|
+
- **Data Models**: Type-safe models
|
|
2979
3819
|
|
|
2980
3820
|
- ``DeviceStatus`` dataclass with 125 sensor and operational fields
|
|
2981
3821
|
- ``DeviceFeature`` dataclass with 46 capability and configuration fields
|
|
@@ -3014,7 +3854,7 @@ Added
|
|
|
3014
3854
|
- Typed callbacks implementation guide
|
|
3015
3855
|
- Control command reference with verified command codes
|
|
3016
3856
|
- Example scripts for common use cases
|
|
3017
|
-
-
|
|
3857
|
+
- Troubleshooting guides
|
|
3018
3858
|
- Complete energy data reference (ENERGY_DATA_SUMMARY.md)
|
|
3019
3859
|
|
|
3020
3860
|
- **Examples**: Production-ready example scripts
|
|
@@ -3056,14 +3896,14 @@ Changed
|
|
|
3056
3896
|
- Removed "Optional Feature" and "not required for basic operation" phrases
|
|
3057
3897
|
- Fixed table rendering in DEVICE_STATUS_FIELDS.rst
|
|
3058
3898
|
- Fixed JSON syntax in code examples
|
|
3059
|
-
- Added
|
|
3060
|
-
- Added
|
|
3899
|
+
- Added reconnection documentation
|
|
3900
|
+
- Added command queue documentation
|
|
3061
3901
|
- Cleaned up backward compatibility references (new library)
|
|
3062
3902
|
|
|
3063
3903
|
Fixed
|
|
3064
3904
|
-----
|
|
3065
3905
|
|
|
3066
|
-
- **
|
|
3906
|
+
- **Bug**: Thread-safe reconnection task creation from MQTT callbacks
|
|
3067
3907
|
|
|
3068
3908
|
- Fixed ``RuntimeError: no running event loop`` when connection is interrupted
|
|
3069
3909
|
- Fixed ``RuntimeWarning: coroutine '_reconnect_with_backoff' was never awaited``
|
|
@@ -3071,9 +3911,9 @@ Fixed
|
|
|
3071
3911
|
- Implemented ``_start_reconnect_task()`` helper method to properly create reconnection tasks
|
|
3072
3912
|
- Uses existing ``_schedule_coroutine()`` method for thread-safe task scheduling
|
|
3073
3913
|
- Prevents crashes during automatic reconnection after connection interruptions
|
|
3074
|
-
-
|
|
3914
|
+
- Reconnection tasks are awaited and executed
|
|
3075
3915
|
|
|
3076
|
-
- **
|
|
3916
|
+
- **Bug**: Thread-safe event emission from MQTT callbacks
|
|
3077
3917
|
|
|
3078
3918
|
- Fixed ``RuntimeError: no running event loop in thread 'Dummy-1'``
|
|
3079
3919
|
- MQTT callbacks run in separate threads created by AWS IoT SDK
|
|
@@ -3098,7 +3938,7 @@ Fixed
|
|
|
3098
3938
|
- Updated built HTML documentation files
|
|
3099
3939
|
- Protects privacy in public documentation
|
|
3100
3940
|
|
|
3101
|
-
- **
|
|
3941
|
+
- **Bug**: Device control command codes
|
|
3102
3942
|
|
|
3103
3943
|
- Fixed incorrect command code usage causing unintended power-off
|
|
3104
3944
|
- Power-off now uses command code ``33554433``
|
|
@@ -3106,7 +3946,7 @@ Fixed
|
|
|
3106
3946
|
- DHW mode control now uses command code ``33554437``
|
|
3107
3947
|
- Discovered through network traffic analysis of official app
|
|
3108
3948
|
|
|
3109
|
-
- **
|
|
3949
|
+
- **Bug**: MQTT topic pattern matching with wildcards
|
|
3110
3950
|
|
|
3111
3951
|
- Fixed ``_topic_matches_pattern()`` to correctly handle ``#`` wildcard
|
|
3112
3952
|
- Topics now match when message arrives on base topic (e.g., ``cmd/52/device/res``)
|
|
@@ -3126,9 +3966,9 @@ Fixed
|
|
|
3126
3966
|
- Examples now subscribe to broader topic patterns
|
|
3127
3967
|
- Subscribe to ``cmd/{device_type}/{device_topic}/#`` to catch all command messages
|
|
3128
3968
|
- Subscribe to ``evt/{device_type}/{device_topic}/#`` to catch all event messages
|
|
3129
|
-
-
|
|
3969
|
+
- All device responses are received
|
|
3130
3970
|
|
|
3131
|
-
- **Enhancement**:
|
|
3971
|
+
- **Enhancement**: Enum conversion with fallbacks
|
|
3132
3972
|
|
|
3133
3973
|
- Added try/except blocks for all enum conversions in ``DeviceStatus.from_dict()``
|
|
3134
3974
|
- Added try/except blocks for all enum conversions in ``DeviceFeature.from_dict()``
|