nwp500-python 9.12.1__tar.gz → 9.12.2__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.12.1 → nwp500_python-9.12.2}/CHANGELOG.rst +561 -133
- {nwp500_python-9.12.1/src/nwp500_python.egg-info → nwp500_python-9.12.2}/PKG-INFO +1 -1
- nwp500_python-9.12.2/docs/_static/light-page.js +1 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/docs/conf.py +5 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/docs/explanation/draws.rst +115 -48
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/docs/explanation/heating-elements.rst +2 -2
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/docs/explanation/index.rst +1 -0
- nwp500_python-9.12.2/docs/explanation/simulator-methodology.rst +726 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/docs/explanation/simulator.rst +79 -62
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/docs/explanation/standby-loss.rst +109 -19
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/docs/explanation/tank-energy.rst +10 -67
- nwp500_python-9.12.2/docs/explanation/unknown-values.rst +198 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/docs/explanation/what-starts-a-recovery.rst +17 -12
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/docs/how-to/authenticate.rst +1 -1
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/docs/how-to/bluetooth.rst +18 -24
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/docs/how-to/calibrate-simulator.rst +24 -19
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/docs/how-to/diagnose-mqtt.rst +2 -3
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/docs/how-to/maintenance.rst +13 -17
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/docs/how-to/optimize-tou.rst +8 -9
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/docs/how-to/schedule-operation.rst +15 -9
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/docs/how-to/simulate-heater.rst +108 -73
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/docs/how-to/track-energy.rst +1 -1
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/docs/project/history.rst +0 -1
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/docs/reference/configuration.rst +1 -1
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/docs/reference/enumerations.rst +11 -14
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/docs/reference/protocol/data_conversions.rst +8 -8
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/docs/reference/protocol/device_features.rst +4 -4
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/docs/reference/protocol/device_status.rst +7 -7
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/docs/reference/protocol/mqtt_protocol.rst +76 -98
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/docs/reference/protocol/rest_api.rst +49 -54
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/docs/reference/python_api/api_client.rst +24 -28
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/docs/reference/python_api/auth_client.rst +1 -1
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/docs/reference/python_api/cli.rst +7 -8
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/docs/reference/python_api/events.rst +2 -2
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/docs/reference/python_api/models.rst +26 -30
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/docs/reference/python_api/mqtt_client.rst +30 -36
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/docs/reference/python_api/sim.rst +48 -20
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/docs/reference/simulator-inputs.rst +349 -140
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/docs/requirements.txt +1 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/docs/tutorials/getting-started.rst +5 -5
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/api_client.py +39 -43
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/auth.py +9 -9
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/ble/client.py +5 -5
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/ble/protocol.py +55 -68
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/cli/__main__.py +4 -5
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/cli/calibration.py +2 -2
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/cli/presentation.py +4 -4
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/cli/simulate.py +5 -1
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/converters.py +10 -12
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/encoding.py +1 -1
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/enums.py +21 -27
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/models/account.py +3 -3
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/models/device.py +20 -25
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/models/diagnostics.py +8 -9
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/models/energy.py +6 -6
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/models/feature.py +4 -8
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/models/maintenance.py +5 -6
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/models/mqtt_models.py +16 -19
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/models/schedule.py +10 -14
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/models/status.py +12 -19
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/models/tou.py +1 -2
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/mqtt/_control_commands.py +2 -2
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/mqtt/_device_subscriptions.py +1 -1
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/mqtt/client.py +12 -15
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/mqtt/control.py +36 -48
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/mqtt/subscriptions.py +3 -3
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/mqtt/types.py +1 -1
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/mqtt/utils.py +7 -11
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/sim/api.py +31 -50
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/sim/attribution.py +5 -2
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/sim/book.py +322 -454
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/sim/calibration.py +328 -653
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/sim/circulation.py +50 -15
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/sim/cold_supply.py +3 -4
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/sim/controller.py +140 -59
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/sim/decay.py +72 -101
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/sim/defaults.py +50 -36
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/sim/delivery.py +74 -164
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/sim/draw_forecast.py +11 -3
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/sim/engine.py +118 -472
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/sim/extensions.py +13 -38
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/sim/heat_pump_recovery.py +3 -1
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/sim/heating_trajectory.py +3 -1
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/sim/inputs.py +3 -2
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/sim/params.py +184 -118
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/sim/physics/compressor.py +42 -17
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/sim/physics/draw.py +171 -139
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/sim/physics/refrigerant.py +43 -34
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/sim/physics/tank.py +2 -2
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/sim/ready.py +35 -89
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/sim/tank_physics.py +9 -12
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/sim/tou_triggers.py +116 -15
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/sim_adapters.py +86 -25
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/sim_calibrate.py +64 -93
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/sim_home_assistant.py +62 -21
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/temperature.py +2 -2
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/topic_builder.py +4 -5
- {nwp500_python-9.12.1 → nwp500_python-9.12.2/src/nwp500_python.egg-info}/PKG-INFO +1 -1
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500_python.egg-info/SOURCES.txt +3 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500_python.egg-info/scm_file_list.json +3 -0
- nwp500_python-9.12.2/src/nwp500_python.egg-info/scm_version.json +8 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/tests/_sim_site.py +0 -44
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/tests/fixtures/sim/engine_golden.json +45 -55
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/tests/fixtures/sim/heater_params.json +7 -4
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/tests/fixtures/sim/heater_params_fitted.json +17 -11
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/tests/test_cli_calibrate.py +3 -5
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/tests/test_cli_simulate.py +45 -5
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/tests/test_sim_adapters.py +5 -6
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/tests/test_sim_api.py +37 -47
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/tests/test_sim_book.py +182 -72
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/tests/test_sim_book_history.py +382 -47
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/tests/test_sim_bottom_loss.py +10 -8
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/tests/test_sim_calibrate.py +71 -68
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/tests/test_sim_calibration.py +49 -33
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/tests/test_sim_change_history.py +123 -9
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/tests/test_sim_circulation.py +54 -15
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/tests/test_sim_codec.py +0 -2
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/tests/test_sim_controller.py +155 -28
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/tests/test_sim_defaults.py +9 -16
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/tests/test_sim_documents.py +0 -4
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/tests/test_sim_draw_forecast.py +0 -2
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/tests/test_sim_draw_loss.py +170 -58
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/tests/test_sim_element_fit.py +0 -2
- nwp500_python-9.12.2/tests/test_sim_evaporator.py +59 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/tests/test_sim_fit_book.py +1 -6
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/tests/test_sim_forecast.py +0 -2
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/tests/test_sim_golden.py +4 -6
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/tests/test_sim_home_assistant.py +26 -14
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/tests/test_sim_hot_water.py +328 -38
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/tests/test_sim_live.py +21 -5
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/tests/test_sim_location_air.py +0 -2
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/tests/test_sim_no_clock_no_state.py +3 -4
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/tests/test_sim_overlay.py +0 -2
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/tests/test_sim_readiness.py +147 -334
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/tests/test_sim_readiness_fit.py +102 -64
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/tests/test_sim_ready.py +52 -34
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/tests/test_sim_ready_api.py +41 -20
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/tests/test_sim_replay.py +0 -2
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/tests/test_sim_tank_physics.py +13 -13
- nwp500_python-9.12.1/docs/explanation/unknown-values.rst +0 -264
- nwp500_python-9.12.1/src/nwp500_python.egg-info/scm_version.json +0 -8
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/.coveragerc +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/.github/RESOLVING_PR_COMMENTS.md +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/.github/copilot-instructions.md +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/.github/workflows/ci.yml +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/.github/workflows/release.yml +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/.gitignore +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/.pre-commit-config.yaml +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/.readthedocs.yml +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/AUTHORS.rst +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/CONTRIBUTING.rst +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/LICENSE.txt +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/Makefile +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/README.rst +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/RELEASE.md +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/docs/Makefile +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/docs/_static/.gitignore +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/docs/explanation/architecture.rst +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/docs/explanation/recovery-speed.rst +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/docs/explanation/tou-recovery-cap.rst +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/docs/how-to/auto-recovery.rst +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/docs/how-to/home-assistant.rst +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/docs/how-to/index.rst +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/docs/how-to/manage-units.rst +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/docs/how-to/monitor-status.rst +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/docs/how-to/queue-commands.rst +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/docs/index.rst +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/docs/openapi.yaml +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/docs/project/authors.rst +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/docs/project/changelog.rst +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/docs/project/contributing.rst +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/docs/project/license.rst +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/docs/reference/index.rst +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/docs/reference/installation.rst +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/docs/reference/protocol/error_codes.rst +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/docs/reference/protocol/quick_reference.rst +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/docs/reference/python_api/exceptions.rst +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/examples/.ruff.toml +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/examples/README.md +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/examples/advanced/air_filter_reset.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/examples/advanced/anti_legionella.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/examples/advanced/auto_recovery.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/examples/advanced/combined_callbacks.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/examples/advanced/demand_response.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/examples/advanced/device_capabilities.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/examples/advanced/device_status_debug.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/examples/advanced/energy_analytics.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/examples/advanced/error_code_demo.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/examples/advanced/firmware_payload_capture.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/examples/advanced/mqtt_diagnostics.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/examples/advanced/power_control.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/examples/advanced/recirculation_control.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/examples/advanced/reconnection_demo.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/examples/advanced/reservation_schedule.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/examples/advanced/simple_auto_recovery.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/examples/advanced/token_restoration.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/examples/advanced/tou_openei.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/examples/advanced/tou_schedule.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/examples/advanced/water_reservation.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/examples/beginner/01_authentication.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/examples/beginner/02_list_devices.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/examples/beginner/03_get_status.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/examples/beginner/04_set_temperature.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/examples/intermediate/advanced_auth_patterns.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/examples/intermediate/command_queue.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/examples/intermediate/device_status_callback.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/examples/intermediate/error_handling.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/examples/intermediate/event_driven_control.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/examples/intermediate/improved_auth.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/examples/intermediate/legacy_auth_constructor.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/examples/intermediate/mqtt_realtime_monitoring.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/examples/intermediate/periodic_requests.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/examples/intermediate/set_mode.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/examples/intermediate/vacation_mode.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/examples/mask.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/examples/simulator/lower-setpoint.json +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/examples/simulator/plan.json +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/examples/simulator/scenario.json +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/examples/testing/periodic_device_info.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/examples/testing/simple_periodic_info.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/examples/testing/test_api_client.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/examples/testing/test_mqtt_connection.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/examples/testing/test_mqtt_messaging.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/examples/testing/test_periodic_minimal.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/pyproject.toml +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/scripts/README.md +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/scripts/bump_version.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/scripts/diagnose_mqtt_connection.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/scripts/extract_changelog.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/scripts/format.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/scripts/lint.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/scripts/setup-dev.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/scripts/validate_version.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/setup.cfg +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/__init__.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/_base.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/ble/__init__.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/ble/__main__.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/ble/mqtt_adapter.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/cli/__init__.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/cli/ble_backend.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/cli/handlers.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/cli/monitoring.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/cli/output_formatters.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/cli/recording.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/cli/rich_output.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/cli/token_storage.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/command_decorators.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/config.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/device_capabilities.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/device_info_cache.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/events.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/exceptions.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/factory.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/field_factory.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/models/__init__.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/models/_converters.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/models/firmware.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/mqtt/__init__.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/mqtt/command_queue.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/mqtt/connection.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/mqtt/diagnostics.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/mqtt/periodic.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/mqtt/reconnection.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/mqtt/state_tracker.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/mqtt_events.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/openei.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/py.typed +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/reservations.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/sim/__init__.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/sim/codec.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/sim/location_air.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/sim/observed.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/sim/physics/__init__.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/sim/physics/element.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/sim/profile.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/sim/record.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/sim/result.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/sim/schedules.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/sim/units.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/sim_live.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/status_history.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/tou_schedule.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/unit_system.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500/utils.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500_python.egg-info/dependency_links.txt +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500_python.egg-info/entry_points.txt +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500_python.egg-info/not-zip-safe +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500_python.egg-info/requires.txt +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/src/nwp500_python.egg-info/top_level.txt +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/tests/conftest.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/tests/test_api_endpoints_201.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/tests/test_api_helpers.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/tests/test_app_queries.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/tests/test_auth.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/tests/test_auth_session_lifecycle.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/tests/test_auth_session_refresh.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/tests/test_ble_mqtt_adapter.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/tests/test_ble_protocol.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/tests/test_bug_fixes.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/tests/test_canonical_schedule.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/tests/test_cli_basic.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/tests/test_cli_commands.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/tests/test_cli_tristate_presentation.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/tests/test_command_decorators.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/tests/test_command_queue.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/tests/test_device_capabilities.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/tests/test_device_info_cache.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/tests/test_device_rest_models.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/tests/test_events.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/tests/test_exceptions.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/tests/test_model_converters.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/tests/test_models.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/tests/test_mqtt_clean_session_resume.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/tests/test_mqtt_client_init.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/tests/test_mqtt_control_fail.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/tests/test_mqtt_events.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/tests/test_mqtt_hypothesis.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/tests/test_mqtt_last_will.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/tests/test_mqtt_reconnection.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/tests/test_mqtt_reconnection_storm.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/tests/test_mqtt_reliability.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/tests/test_multi_device.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/tests/test_navilink_201_enums.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/tests/test_openei.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/tests/test_protocol_correctness.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/tests/test_public_api.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/tests/test_reservation_validation.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/tests/test_reservations.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/tests/test_sim_cold_supply.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/tests/test_sim_element.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/tests/test_sim_import_contract.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/tests/test_sim_observed.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/tests/test_sim_profile.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/tests/test_sim_schedules.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/tests/test_status_history.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/tests/test_temperature_converters.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/tests/test_threading_model.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/tests/test_token_storage.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/tests/test_tou_api.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/tests/test_tou_schedule.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/tests/test_unit_switching.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/tests/test_unit_system_process_wide.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/tests/test_utility_modules.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/tests/test_utils.py +0 -0
- {nwp500_python-9.12.1 → nwp500_python-9.12.2}/tox.ini +0 -0
|
@@ -2,6 +2,460 @@
|
|
|
2
2
|
Changelog
|
|
3
3
|
=========
|
|
4
4
|
|
|
5
|
+
Version 9.12.2 (2026-10-25)
|
|
6
|
+
===========================
|
|
7
|
+
|
|
8
|
+
Changed
|
|
9
|
+
-------
|
|
10
|
+
|
|
11
|
+
- **The heat book prices its runs as cooled to the air, on the
|
|
12
|
+
record.** Pricing each run at the loop's return instead, the pipe
|
|
13
|
+
resting 7.84 °F over the air and cooling on 0.464 h, books the fitted
|
|
14
|
+
pairs no better (17,018 BTU mean absolute error against 16,991 on 87
|
|
15
|
+
pairs). The book is unchanged. See :doc:`/how-to/calibrate-simulator`.
|
|
16
|
+
|
|
17
|
+
- **The recovery fit's held-out error is draws inside recoveries.**
|
|
18
|
+
The latest recoveries come out about 25 min short at every
|
|
19
|
+
compressor fit tried. Those with under 2 gal of hot water and 5
|
|
20
|
+
minutes of circulation inside them come out 0.1 min short (20 of
|
|
21
|
+
35); each hot gallon inside a recovery adds about 4.7 min. The fit is
|
|
22
|
+
unchanged: fitted on the draw-light recoveries alone, the constant
|
|
23
|
+
scores worse on the held-out draw-light ones (14.2 min against 11.6).
|
|
24
|
+
See :doc:`/explanation/simulator`.
|
|
25
|
+
|
|
26
|
+
- **The compressor's engineering values are not fittable on the
|
|
27
|
+
record.** The condenser approach (10 °F), clearance ratio (0.05) and
|
|
28
|
+
polytropic exponent (1.17) stay. The compressor constant absorbs
|
|
29
|
+
each: refitting it with each moved over a wide range changed the
|
|
30
|
+
held-out recovery error by under 0.7 min. See
|
|
31
|
+
:doc:`/reference/simulator-inputs`.
|
|
32
|
+
|
|
33
|
+
- **Hand-set values checked against the record.** None changes.
|
|
34
|
+
``TankParams.lower_zone_share`` 0.5 stays: refitting the compressor
|
|
35
|
+
constant at 0.435-0.5 moved the held-out recovery error by under
|
|
36
|
+
0.7 min. ``ControllerParams.tou_cap_f`` 3.6 °F stays at the device's
|
|
37
|
+
threshold: the probe's 3.42 °F at a capped stop is one probe step
|
|
38
|
+
past it, as a full recovery ends past its setpoint.
|
|
39
|
+
``ReadinessParams.probe_resolution_f`` is a round 1 °F and is now
|
|
40
|
+
tagged as a default, not a measurement. The unused
|
|
41
|
+
``engine.RESOLUTION_F`` is gone. The humidity default of 50 % stays:
|
|
42
|
+
holding every recovery there instead of its own reading moved the
|
|
43
|
+
mean duration error by 0.4 min. ``calibration.SETTLE_MINUTES`` 45
|
|
44
|
+
stays: ``ua_ref`` was 4.44-4.48 at 30 to 90 min. The recovery screen
|
|
45
|
+
(``MIN_MINUTES_PER_F``, ``MAX_MINUTES_PER_F``) stays: fitting with
|
|
46
|
+
the 12 it drops moved the compressor constant 0.2 %.
|
|
47
|
+
``sim_adapters.FLOW_HOLD``, ``sim_home_assistant.PUMP_ON_RPM`` and
|
|
48
|
+
``PUMP_STALE`` say what the record shows of them. See
|
|
49
|
+
:doc:`/reference/simulator-inputs` and
|
|
50
|
+
:doc:`/explanation/standby-loss`.
|
|
51
|
+
|
|
52
|
+
- **A window opening drops a running element mode: from the record.**
|
|
53
|
+
The simulator drops an element mode to the heat pump at a window's
|
|
54
|
+
start, which was assumed from one device run at a TOU switch-on. The
|
|
55
|
+
record has 18 openings with an element mode in force (2026-01-01 to
|
|
56
|
+
10-02): every one dropped it within 2 s, 15 at the window's scheduled
|
|
57
|
+
start and 3 at TOU switched back on inside a window. The model is
|
|
58
|
+
unchanged. See :doc:`/how-to/schedule-operation`.
|
|
59
|
+
|
|
60
|
+
Fixed
|
|
61
|
+
-----
|
|
62
|
+
|
|
63
|
+
- **Without an air sensor, the standby loss is fitted on the heater's
|
|
64
|
+
idle reading.** It was fitted on the heater's running reading,
|
|
65
|
+
interpolated from one run to the next, on the quiet windows with a
|
|
66
|
+
run within ``HEATER_AIR_BRACKET`` (6 h, now removed) of both ends.
|
|
67
|
+
The night air dips below that line: at one installation, against a
|
|
68
|
+
sensor beside the heater, those windows read 3.9 °F warm on average
|
|
69
|
+
(7.8 °F on windows over 12 h), and the loss 5 % high on 150 of 378
|
|
70
|
+
windows, 21 % high on 54 from 2026-06-25. The idle reading, the
|
|
71
|
+
cabinet's less ``sim_adapters.IDLE_CABINET_WARM_F``, reads every
|
|
72
|
+
window: the loss comes out 0.7 % high on all 378, and 0.3 % low on
|
|
73
|
+
the 188 from 2026-06-25 (2.2 % low with the cabinet's warmth measured
|
|
74
|
+
before then). The frame has a new column, ``heater_cabinet_f``. See
|
|
75
|
+
:doc:`/how-to/calibrate-simulator`.
|
|
76
|
+
|
|
77
|
+
- **The site's standby loss and gap constant are the refits that
|
|
78
|
+
score better held out.** ``site_example_params`` kept the earlier
|
|
79
|
+
standby law (``UA`` 4.605, exponent 0.186, no bottom loss) and gap
|
|
80
|
+
constant (60.06 h), though the calibration with ``keep_better`` on
|
|
81
|
+
the record replaces both. Fitted on 378 quiet windows to 2026-10-24,
|
|
82
|
+
the law is a flat ``UA`` of 4.48 BTU/hr per °F with a 90.8 BTU/hr
|
|
83
|
+
bottom loss (gate 1.83 °F). The gap constant, fitted on 127 windows
|
|
84
|
+
with that bottom loss opening the gaps it relaxes, is 26.7 h. Held
|
|
85
|
+
out, the pair tracks the probes' fall within 0.066 °F/h against
|
|
86
|
+
0.096 (56 windows) and the gap at 0.69 °F² against 1.78 (32
|
|
87
|
+
windows). Recalibrated with them as the base, every group keeps
|
|
88
|
+
them. The compressor, power and COP terms keep theirs (the refits
|
|
89
|
+
scored no better). See :doc:`/explanation/standby-loss`.
|
|
90
|
+
|
|
91
|
+
- **The ungated circulation share is the library's own fit.**
|
|
92
|
+
``site_example_params``' ``DrawParams.ungated_observability`` was
|
|
93
|
+
0.398, measured outside the library at the run model's earlier
|
|
94
|
+
terms, and the library's ``fit_ungated_observability`` never
|
|
95
|
+
reproduced it (0.200 at those terms on the same record). It is now
|
|
96
|
+
0.263, that fit on 165 windows from April to October at the current
|
|
97
|
+
run model; 0.117 on the windows since September. It prices the runs
|
|
98
|
+
inside a metered COP cycle: held out, the latest 45 cycles' median
|
|
99
|
+
error is 6.1 % against 6.3 % with 0.398. See
|
|
100
|
+
:doc:`/explanation/standby-loss`.
|
|
101
|
+
|
|
102
|
+
- **High Demand's element restarts later in a stint.** After the entry
|
|
103
|
+
run the simulator never ran High Demand's element again. On the
|
|
104
|
+
record it restarts the upper element with the upper zone about 4 °F
|
|
105
|
+
short of the setpoint (the four restarts of 2026-04-26/27 at 3.78 to
|
|
106
|
+
4.14 °F, each about 3 minutes to the setpoint), the measured
|
|
107
|
+
``ElementParams.upper_reengages_below_setpoint_f`` (4.1 °F) that
|
|
108
|
+
nothing read. The controller now starts it there
|
|
109
|
+
(``controller.later_upper_on``, trigger ``high_demand_upper_short``,
|
|
110
|
+
also in ``attribution``). Electric's later starts are still not
|
|
111
|
+
modelled and still flagged: no Electric stint on record ran half an
|
|
112
|
+
hour. See :doc:`/explanation/simulator`.
|
|
113
|
+
|
|
114
|
+
- **A circulation run's return is not at the air.** The run model took
|
|
115
|
+
the returning water at the air beside the heater and the pipe as
|
|
116
|
+
cooled between runs. On 123 runs with the compressor off (April to
|
|
117
|
+
October), the return rested about 7 °F above the heater's air, and a
|
|
118
|
+
run within an hour of another took about half the heat of one after
|
|
119
|
+
a long rest. ``CirculationParams`` gains ``return_rest_offset_f`` and
|
|
120
|
+
``return_cool_tau_h`` (defaults 0, the old form): the inflow is the
|
|
121
|
+
air plus the offset, and a run's gallons are scaled by the share of
|
|
122
|
+
the pipe cooled since the last run (``circulation.cooled_share``).
|
|
123
|
+
``calibration.fit_return`` fits the volume and both terms together;
|
|
124
|
+
``measure_runs`` records each run's rest, and ``run_heat_btu``,
|
|
125
|
+
``metered_withdrawn`` and ``fit_ungated_observability`` take the
|
|
126
|
+
terms. The site's terms are 19.36 gal, 7.84 °F and 0.464 h, a
|
|
127
|
+
cross-validated error of 268 BTU a run against 767 for the old form.
|
|
128
|
+
The volume is effective: the long runs on the record never brought
|
|
129
|
+
hot water back. Snapshot and window runs are now ``(minutes, upper,
|
|
130
|
+
rest hours, minutes run before)``, and a run a snapshot takes up
|
|
131
|
+
partway is priced from where it was. See :doc:`/reference/simulator-inputs` and
|
|
132
|
+
:doc:`/explanation/standby-loss`.
|
|
133
|
+
|
|
134
|
+
- **The R-134a properties are CoolProp's.** The refrigerant table,
|
|
135
|
+
cited as "ASHRAE Standard 34" (a safety classification, not a property
|
|
136
|
+
table), had the saturated vapour density about 4x low and the liquid
|
|
137
|
+
enthalpy 14 to 40 BTU/lb high, so the condenser's latent heat fell to
|
|
138
|
+
9.7 BTU/lb at 160 °F against a true 52.7. It is now CoolProp 8.0.0's
|
|
139
|
+
every 5 °F. On the record's mixed-tank heating, the model's rate now
|
|
140
|
+
falls 24 BTU/hr per °F of tank and rises 48 per °F of air, against
|
|
141
|
+
the record's 28 and 53 (the old table: 12 and 34). The site's
|
|
142
|
+
compressor constant is refit to 0.0160 ft³/s, a compressor of about
|
|
143
|
+
8 cc at 3,500 rpm (the old 0.0499 was four times that, to make up the
|
|
144
|
+
density). The power scale is carried to 1.640 and the COP terms to
|
|
145
|
+
0.688 and 0.002. The durations score the same: 27.0 min mean error
|
|
146
|
+
on 177 recoveries, against 27.2.
|
|
147
|
+
- **The compressor fit holds the evaporator approach.** With the true
|
|
148
|
+
properties the durations no longer place the approach: K and the
|
|
149
|
+
approach together reached the site's error from 6.9 °F, where the
|
|
150
|
+
evaporator coil reads 19.6 °F below the air. ``fit_compressor`` now
|
|
151
|
+
fits ``compressor_constant`` alone, at the parameters'
|
|
152
|
+
``evap_approach_f``. ``CompressorParams.evap_approach_f`` defaults to
|
|
153
|
+
the coil's 19.56 °F and is labelled measured. ``K_BOUNDS`` is
|
|
154
|
+
0.004-0.08, and ``INITIAL_K``, ``INITIAL_APPROACH_F`` and
|
|
155
|
+
``APPROACH_BOUNDS`` are gone. See :doc:`/reference/simulator-inputs`.
|
|
156
|
+
|
|
157
|
+
- **The circulation share's story.** ``DrawParams.ungated_observability``
|
|
158
|
+
was described as the probes' share of a run's heat. The loop heat it
|
|
159
|
+
divides counts all the loop's flow as the tank's, but the return
|
|
160
|
+
feeds the mixing valve's cold port as well as the tank's inlet, and
|
|
161
|
+
that split is not measured. The share is the split and any probe
|
|
162
|
+
shortfall together (its refit to 0.263 is above). See
|
|
163
|
+
:doc:`/explanation/standby-loss`.
|
|
164
|
+
|
|
165
|
+
- **The upper zone's hold through a recovery rests on the record.** The
|
|
166
|
+
two-zone model warms the lower zone alone while the tank is
|
|
167
|
+
stratified and holds the upper where it started. On 30 compressor-only
|
|
168
|
+
recoveries the upper probe fell a median 0.54 °F while the gap was
|
|
169
|
+
over 3 °F and rose to 1.26 °F above its start over the gap's last 2 to
|
|
170
|
+
3 °F, so the hold stays. See :doc:`/explanation/simulator`.
|
|
171
|
+
|
|
172
|
+
- **The outlet lag is the tank's.** ``ReadinessParams.delivered_lag_gal``
|
|
173
|
+
(5 gal) was described as mostly the pipe to the fixture. Both bath
|
|
174
|
+
figures behind it, 7.3 gal at the tub spout and 5.3 at the valve
|
|
175
|
+
outlet, were taken net of the pipe to each thermometer, and they
|
|
176
|
+
differ by calibration, not place. The lag is the tank water above the
|
|
177
|
+
upper probe, and the field is now labelled the heater's. The value is
|
|
178
|
+
unchanged. See :doc:`/reference/simulator-inputs`.
|
|
179
|
+
|
|
180
|
+
- **The draw windows' reach rests on the record.** ``HEATER_DRAW_REACH``
|
|
181
|
+
stays 60 minutes. Its reason was that a draw "can come that much
|
|
182
|
+
earlier than any the forecast has seen", which nothing measured. On 44
|
|
183
|
+
complete forecast issues (98 long draws), 60 minutes covered 94.9% of
|
|
184
|
+
long draws and 30 minutes 89.8%; the five covered only at 60 fell
|
|
185
|
+
either side of a 30-minute window. See :doc:`/how-to/simulate-heater`.
|
|
186
|
+
|
|
187
|
+
- **The heater's idle air reading is corrected.** Idle, the heater's
|
|
188
|
+
own air sensor reads its cabinet: on the record (February to October)
|
|
189
|
+
6.2 °F above a sensor beside the heater on average, 9.3 in February to
|
|
190
|
+
4.8 in October. ``sim_adapters.environment(status)``, and so the live
|
|
191
|
+
scenario and a replay without recorded air, now take
|
|
192
|
+
``IDLE_CABINET_WARM_F`` (6.2 °F) off an idle reading. That cuts the
|
|
193
|
+
average error from 6.3 to 2.2 °F. The ``ambient_from_idle_cabinet``
|
|
194
|
+
reason still names it. See :doc:`/reference/simulator-inputs`.
|
|
195
|
+
|
|
196
|
+
- **The Home Assistant air fit settles 5 minutes into a run.**
|
|
197
|
+
``sim_home_assistant.RUN_SETTLE`` was 2 minutes, against the book's 5
|
|
198
|
+
(``sim_adapters.HEATER_AIR_SETTLE``). It is now that constant. On the
|
|
199
|
+
record the reading runs a median 2.1 °F warm in a run's third minute
|
|
200
|
+
and 1.8 in its fifth.
|
|
201
|
+
|
|
202
|
+
- **The fan airflow's source.** ``CompressorParams.fan_airflow_cfm``
|
|
203
|
+
(229) was cited as the NWP500's specification. No airflow is
|
|
204
|
+
published; the manual's 229 is the 50 gal model's shipping weight in
|
|
205
|
+
pounds. The value stays: on 178 recorded recoveries the latent term it
|
|
206
|
+
sizes is at most about 150 BTU/hr, humidity's own effect is not
|
|
207
|
+
resolved, and the term off scored 0.6 min worse held out.
|
|
208
|
+
|
|
209
|
+
- **The 50 and 80 gal models are scaled by rated storage.** Their tank
|
|
210
|
+
volume, the book's count of it and its empty point were scaled from
|
|
211
|
+
the 65's by nominal size (48.6 and 77.8 gal). The manual's
|
|
212
|
+
specification table rates them at 45.2 and 76.1 gal, against the
|
|
213
|
+
65's 63.2. ``defaults.NOMINAL_GAL`` is now ``RATED_GAL``.
|
|
214
|
+
|
|
215
|
+
- **A held mode landing over the setpoint starts nothing.** On
|
|
216
|
+
2026-09-21 Energy Saver, held through a TOU window, landed at its
|
|
217
|
+
close with the upper tank 18.7 °F above a 107.6 °F setpoint and the
|
|
218
|
+
lower tank 20.9 °F under it. Nothing started for 9 h 56 min, and the
|
|
219
|
+
start came 2 min 17 s into a circulation run. The simulation started
|
|
220
|
+
the compressor at the landing, and again on the lower trigger, since
|
|
221
|
+
it treated Energy Saver's trigger as a plain level test. Three rules
|
|
222
|
+
change:
|
|
223
|
+
|
|
224
|
+
- A mode write starts the compressor on a short lower tank
|
|
225
|
+
(``mode_write_lower_starts_f``) out of an element mode whatever the
|
|
226
|
+
upper tank, as before (19 of 19), and otherwise only with the upper
|
|
227
|
+
tank short too.
|
|
228
|
+
- Energy Saver holds the lower trigger while the upper tank is at or
|
|
229
|
+
above the setpoint, as Heat Pump does, and a draw's drop under the
|
|
230
|
+
setting starts it. ``tou_triggers.LOWER_HOLD_MODE`` is now
|
|
231
|
+
``LOWER_HOLD_MODES``, and ``autonomous_start`` takes
|
|
232
|
+
``drop_starts``.
|
|
233
|
+
- A mode write held in a window is judged as a write in the window is
|
|
234
|
+
(``window_write_starts_f``). Before, it always charged a start. Two
|
|
235
|
+
held Energy Saver writes with the compressor off, 2.4 °F short and
|
|
236
|
+
27.5 °F over, started nothing for hours.
|
|
237
|
+
|
|
238
|
+
Documented in :doc:`/explanation/what-starts-a-recovery`.
|
|
239
|
+
|
|
240
|
+
- **A recovery that ends during a draw books only what follows.** The
|
|
241
|
+
book anchored at such a recovery's end and then booked the movement
|
|
242
|
+
again from its start, on the premise that the probes at the anchor
|
|
243
|
+
showed none of it, the cold water lying below the lower probe. The
|
|
244
|
+
lower probe falls at once with a draw. On the record from 2026-02-14
|
|
245
|
+
to 10-24 the rule fired 5 times, each a circulation run with at most
|
|
246
|
+
0.03 gal of household draw, and walked to the next clean anchor it
|
|
247
|
+
scored a mean absolute error of 9.92 gal against 9.55 without it.
|
|
248
|
+
``book_steps`` and ``calibration.book_walk`` now book the movement
|
|
249
|
+
from the anchor's own step on, its probes reading the step's start.
|
|
250
|
+
|
|
251
|
+
- **A draw window runs cold once its delivery falls below the line.**
|
|
252
|
+
A window counted as depleted only when the delivery fell more than
|
|
253
|
+
3.4 °F below the line or stayed below it more than 3 minutes, an
|
|
254
|
+
allowance for the upper probe dipping during a draw. That dip was
|
|
255
|
+
measured during an Electric recovery on 2026-09-20, with no draw, and
|
|
256
|
+
no draw on the record shows one: on 535 clean draws the upper probe's
|
|
257
|
+
fall by the draw's end was a median 0.00-0.72 °F by size, and none
|
|
258
|
+
came back 1 °F or more within 10 minutes. The simulated delivery has
|
|
259
|
+
no transient either, so the allowance only hid real crossings, up to
|
|
260
|
+
about 1.7 gal past the line. Removed: ``ReadinessParams.dip_f`` and
|
|
261
|
+
``dip_min``. A parameters document that still carries them loads, and
|
|
262
|
+
``unknown_fields`` doesn't name them.
|
|
263
|
+
|
|
264
|
+
- **The evaporator moves 0.6 °F per °F of air, not one for one.** The
|
|
265
|
+
compressor's physics held the evaporator a fixed approach below the
|
|
266
|
+
air, so its capacity followed the air too closely. Over 377
|
|
267
|
+
compressor runs at one installation, the coil read 6.7 + 0.596 × the
|
|
268
|
+
air beside the heater (air 48-86 °F), and a free fit on 142 recorded
|
|
269
|
+
recoveries' durations chose a slope of 0.62. With the coil's slope and
|
|
270
|
+
the compressor constant and approach refit (0.0499 and 18.69 °F at
|
|
271
|
+
65 °F air, on the old refrigerant table; both are replaced above),
|
|
272
|
+
the 35 recoveries held out came out 32.0 min off on
|
|
273
|
+
average, against 34.2 for the fixed approach refit on the same split
|
|
274
|
+
and 36.5 as shipped. The bias below 60 °F air went from +14.3 to
|
|
275
|
+
+4.4 min, and above 75 °F from -7.6 to +3.5. The coil's live reading
|
|
276
|
+
(``active_cycle``'s ``evaporator_f``) now sits in the same frame: as
|
|
277
|
+
the evaporator it fits with an offset of 0.0 °F. Added:
|
|
278
|
+
``CompressorParams.evap_air_slope`` (1, a fixed approach, for a
|
|
279
|
+
document without it) and ``physics.compressor.evaporator_f`` and
|
|
280
|
+
``EVAP_REF_AIR_F``. ``evap_approach_f`` is now the approach at 65 °F
|
|
281
|
+
air.
|
|
282
|
+
|
|
283
|
+
- **The heat book converts gallons at the mains, not the pipe at
|
|
284
|
+
rest.** A book built from a record converted its heat to gallons at
|
|
285
|
+
the inlet probe's median over the last 3 hours. At rest the probe
|
|
286
|
+
tracks the air around the pipe. Now the book reads the mains from the
|
|
287
|
+
long draws in its own record, as ``cold_supply`` does: the probe's
|
|
288
|
+
median over the last 2 minutes of each household draw of 11 minutes
|
|
289
|
+
or more with circulation off. Without one it takes
|
|
290
|
+
``BookParams.inlet_default_f``. Over 2026-09-21 to 10-24, against the
|
|
291
|
+
plateaus, the inlet's mean error went from 3.85 °F (up to 14.2) to
|
|
292
|
+
0.97 °F (up to 3.3). ``Booked.inlet_measured_share`` is now 1 when the
|
|
293
|
+
inlet is read from a long draw and 0 when it is the default. Added:
|
|
294
|
+
``book.mains_f``. Removed: ``BookParams.inlet_window_s``. A parameters
|
|
295
|
+
document that still carries it loads, and ``unknown_fields`` doesn't
|
|
296
|
+
name it. The docs now give the mains by season
|
|
297
|
+
(:doc:`/explanation/draws`): ``draw.inlet_temp_f``, 72.4 °F, is a
|
|
298
|
+
September value.
|
|
299
|
+
|
|
300
|
+
- **A draw's probe response follows the tank's gap.** A delivered draw
|
|
301
|
+
moved the probes the same way whatever the tank's state: the bulk
|
|
302
|
+
fell by its apparent heat and the gap opened 1.235 °F per withdrawn
|
|
303
|
+
gallon. The recorded lower probe falls further the wider the gap at
|
|
304
|
+
the draw. Now the water let in reaches the lower probe by a share
|
|
305
|
+
that grows with the gap, and the upper probe falls 0.05 °F per
|
|
306
|
+
delivered gallon (``physics.draw.probes_after_draw``). On 535 clean
|
|
307
|
+
draws from 2026-02-21 to 10-23, the lower probe's mean absolute error
|
|
308
|
+
10 minutes after the draw went from 0.48 to 0.35 °F under 1 gal, 1.68
|
|
309
|
+
to 0.91 at 1-3 gal, 3.70 to 2.12 at 3-8 gal, 6.72 to 3.52 at 8-20 gal
|
|
310
|
+
and 8.78 to 6.48 over 20 gal; its bias went from -1.75 to -0.03 °F
|
|
311
|
+
with the probes within 3 °F and from +1.62 to -0.10 with them apart.
|
|
312
|
+
On the 136 draws from 08-01, held out of the fit, the lower probe's
|
|
313
|
+
error went from 2.79 to 1.91 °F. Added: ``DrawParams.reach_gate_f``,
|
|
314
|
+
``reach_offset_f``, ``reach_gal`` and ``upper_f_per_gal``;
|
|
315
|
+
``physics.draw.probes_after_draw``, ``DEFAULT_REACH_GATE_F``,
|
|
316
|
+
``DEFAULT_REACH_OFFSET_F``, ``DEFAULT_REACH_GAL`` and
|
|
317
|
+
``DEFAULT_UPPER_F_PER_GAL``; and the ``draw_reach_gate_f``,
|
|
318
|
+
``draw_reach_offset_f``, ``draw_reach_gal`` and
|
|
319
|
+
``draw_upper_f_per_gal`` arguments of ``decay.project_decay`` and
|
|
320
|
+
``decay.draw_step``. ``draw_step``'s ``draw_observability`` takes
|
|
321
|
+
None, which leaves the tank as it was. Removed:
|
|
322
|
+
``DrawParams.gap_open_f_per_gal``; ``physics.draw.gap_opened_f`` and
|
|
323
|
+
``DEFAULT_GAP_OPEN_F_PER_GAL``; the ``draw_gap_open_f_per_gal``
|
|
324
|
+
arguments of ``decay.project_decay`` and ``decay.draw_step``; and
|
|
325
|
+
``draw_step``'s ``tank``. ``DrawParams.observability`` still prices a
|
|
326
|
+
draw's heat and turns delivered draws on. A parameters document that
|
|
327
|
+
still carries ``gap_open_f_per_gal`` loads, and ``unknown_fields``
|
|
328
|
+
doesn't name it.
|
|
329
|
+
|
|
330
|
+
- **The heat book counts all its heat.** The book held back the heat
|
|
331
|
+
that landed in a draw's cold water, the heat pump's and the lower
|
|
332
|
+
element's, until that water passed 100 °F, or until the lower probe,
|
|
333
|
+
having read it below 100 °F, read 100 °F again. On the two recorded drawdowns where it held
|
|
334
|
+
heat back, its answer at the start under-read the gallons delivered
|
|
335
|
+
to the line by about 9 gal; counting all of it read within about 4.
|
|
336
|
+
Now all booked heat is drawable, the deliverable gallons, readiness
|
|
337
|
+
and the upper zone through a draw alike, and a spent tank's delivery
|
|
338
|
+
falls to the inlet. Removed: ``BookParams.cold_release_f``;
|
|
339
|
+
``Booked.cold_gal``, ``cold_f``, ``cold_seen``, ``withheld_btu`` and
|
|
340
|
+
``observed``; ``book.projected_release``; ``book.Step.lower_element``;
|
|
341
|
+
``WalkCarry.drawn`` and ``bottom``; the ``withheld_btu`` arguments of
|
|
342
|
+
``book.deliverable_from_heat``, ``probe_line_gal`` and
|
|
343
|
+
``delivery.deliverable``; ``calibration.withheld_btu`` and
|
|
344
|
+
``zone_empty_point`` (with nothing held back it is ``empty_point``'s
|
|
345
|
+
median); and ``book_walk``'s ``withheld``, ``cold_gal``, ``cold_f``,
|
|
346
|
+
``cold_gal_after`` and ``cold_f_after`` columns. ``Booked.heated``
|
|
347
|
+
takes one heat, ``ready.Added`` has one ``heat_btu`` in place of
|
|
348
|
+
``bottom_btu`` and ``top_btu``, and ``book.draw_out`` returns the
|
|
349
|
+
heat alone. ``zone_empty_btu`` keeps its value. A parameters document
|
|
350
|
+
that still carries ``cold_release_f`` loads, and ``unknown_fields``
|
|
351
|
+
doesn't name it.
|
|
352
|
+
|
|
353
|
+
- **With a heat book, a draw moves the lower probe at once.** With a
|
|
354
|
+
book, the simulated lower probe held where a draw found it until the
|
|
355
|
+
book's cold region reached about 9 gal, then settled over 5 hours
|
|
356
|
+
toward the draw's calibrated response, which left it 3 to 10 °F warm
|
|
357
|
+
after draws of 3 to 20 gal. The recorded lower probe falls on every
|
|
358
|
+
draw at once and neither rebounds nor keeps falling. Now a draw moves
|
|
359
|
+
the lower probe by its calibrated draw response at once, as it does
|
|
360
|
+
without a book; the book still takes the upper probe down. Removed
|
|
361
|
+
with the old behavior: ``BookParams.lower_probe_gal``,
|
|
362
|
+
``front_width_gal`` and ``front_settle_min``; their fits
|
|
363
|
+
``calibration.front_frame``, ``fit_front``, ``FrontFit``,
|
|
364
|
+
``settle_tails``, ``fit_front_settle`` and ``SettleFit``;
|
|
365
|
+
``book.front_share``, ``front_start_gal``, ``lower_after_draw_f`` and
|
|
366
|
+
``front_cannot_move``; ``physics.draw.settled_lower_f`` and
|
|
367
|
+
``unsettled_after_f``; ``HeatLedger.front_moves_at``,
|
|
368
|
+
``LedgerZones.settle_min`` and ``inlet_f``, ``AfterDraw.lower_f``,
|
|
369
|
+
``spent`` and ``inlet_f``, ``UpperAhead.inlet_f`` and
|
|
370
|
+
``DrawTaken.heating``; and ``draw_lower`` of ``TankPhysics.coast``
|
|
371
|
+
and ``decay.project_decay``. ``simulate``'s ``draw_hold_factor`` is
|
|
372
|
+
gone too: it held a recovery for a share of each draw the probes were
|
|
373
|
+
taken to miss, which nothing measured. A recovery model now reads
|
|
374
|
+
``lead_draws`` without it. A parameters document that still carries
|
|
375
|
+
the removed ``book`` terms loads, and ``unknown_fields`` doesn't name
|
|
376
|
+
them.
|
|
377
|
+
|
|
378
|
+
- **A Home Assistant restart no longer loses the heat book.** A restart
|
|
379
|
+
leaves the heater and the flow unknown for a few minutes, and the book
|
|
380
|
+
broke there: there was no book until the next anchor, often hours
|
|
381
|
+
later. ``book_from_changes`` and ``HomeAssistantBook`` now walk across
|
|
382
|
+
a span the heater wasn't heard or an entity was unavailable, holding
|
|
383
|
+
each value from before it, as if it were seen. The flow holds a
|
|
384
|
+
minute and the pump's speed five, so a draw or a run going in doesn't
|
|
385
|
+
run on for the whole outage.
|
|
386
|
+
|
|
387
|
+
- **Readiness reads the tank from its probes.** Without a heat book,
|
|
388
|
+
the deliverable gallons are now the tank's heat as its two probes
|
|
389
|
+
place it, as the book's anchor reads it, plus ``delivered_lag_gal``,
|
|
390
|
+
for any state with the upper probe at or above the line. A tank whose
|
|
391
|
+
probes read full no longer has 13.5 gal taken off; a tank with its
|
|
392
|
+
probes apart reads its probes' heat instead of 0; a cold lower probe
|
|
393
|
+
no longer caps the answer at 6.5 gal; and a running heater no longer
|
|
394
|
+
holds the answer back, now or along a projected recovery. With
|
|
395
|
+
evidence, the answer is the larger of the evidence's and the probes'.
|
|
396
|
+
On one installation, a drawdown from 136.4/111.4 °F delivered 31.1 gal
|
|
397
|
+
where this says 34.5, and two from uniform tanks matched it.
|
|
398
|
+
``sim.delivery.probes_gal`` replaces ``full_tank_floor_gal`` and
|
|
399
|
+
``hot_to_the_lower_probe``. Removed: ``ReadinessParams.full_hidden_gal``,
|
|
400
|
+
``cold_lower_cutoff_f``, ``cold_lower_cap_gal`` and
|
|
401
|
+
``full_setpoint_band_f``; ``calibration.cold_lower_cap``;
|
|
402
|
+
``Deliverable.thermocline_unlocated``, ``colder_than_evidence``,
|
|
403
|
+
``no_evidence`` and ``from_probes``; ``TankNow.heating``; the
|
|
404
|
+
``setpoint_f`` and ``heating`` arguments of ``delivery.deliverable``;
|
|
405
|
+
and the reasons ``thermocline_unlocated``, ``colder_than_all_evidence``,
|
|
406
|
+
``no_evidence_for_state`` and ``estimated_from_probes``
|
|
407
|
+
(``ready.NO_EVIDENCE_FOR_STATE``, ``ESTIMATED_FROM_PROBES``). A
|
|
408
|
+
parameters document that still carries the removed parameters loads
|
|
409
|
+
without them (``params.RETIRED_FIELDS``).
|
|
410
|
+
|
|
411
|
+
- **The heat book anchors while the heat pump runs.** The book anchored
|
|
412
|
+
only when a recovery ended, so a book lost to a restart, or an old one
|
|
413
|
+
drifting, waited for the recovery's end, often hours. Now a step with
|
|
414
|
+
the heat pump running alone, both probes within ``uniform_band_f`` and
|
|
415
|
+
nothing drawn or circulated in it anchors too, once the heat pump has
|
|
416
|
+
run the new ``BookParams.anchor_run_min`` (15 minutes of its running
|
|
417
|
+
time) since a draw after the last anchor. The book walked along a
|
|
418
|
+
simulation anchors the same way. A quiet tank after a draw still
|
|
419
|
+
doesn't anchor: in a mixed tank the lower probe shows only about a
|
|
420
|
+
third of a small draw's heat.
|
|
421
|
+
|
|
422
|
+
- **A draw flow can count tank water.** The heat book read every
|
|
423
|
+
gallon of its flow as delivered through the mixing valve, debiting
|
|
424
|
+
only its hot share of tank water. A flow from a meter or a detector
|
|
425
|
+
on the heater's hot outlet counts tank gallons, so the book debited
|
|
426
|
+
about a quarter of each draw too little (a 141 °F tank behind a valve
|
|
427
|
+
delivering 122 °F) and promised hot water that wasn't there. ``book_from_history``,
|
|
428
|
+
``book_from_changes``, ``fit_book`` and ``HomeAssistantBook`` take a
|
|
429
|
+
new ``flow_volume``: ``"delivered"``, the default and the reading
|
|
430
|
+
until now, or ``"tank"``, each gallon priced at the upper probe less
|
|
431
|
+
the inlet. ``simulate run`` reads it from ``[home_assistant]``'s
|
|
432
|
+
``draw_flow_volume``. The shipped empty points
|
|
433
|
+
(``BookParams.empty_btu``, ``zone_empty_btu``) were booked as
|
|
434
|
+
delivered gallons, so a tank-water book takes them refit on its own
|
|
435
|
+
record (``calibration.empty_point``, keeping ``zone_empty_btu`` as far
|
|
436
|
+
below it): booked as tank water, a drawdown leaves less heat at the
|
|
437
|
+
line.
|
|
438
|
+
|
|
439
|
+
Removed
|
|
440
|
+
-------
|
|
441
|
+
|
|
442
|
+
- **The COP fit's lead share.** With a meter, the COP fit charged a
|
|
443
|
+
share of the draws in the 30 minutes before each cycle, because the
|
|
444
|
+
start reading was taken to miss their cold water. The probes see a
|
|
445
|
+
draw's cold water at once, and on the record the term did not earn
|
|
446
|
+
its place: trained on 221 metered cycles (2026-02-14 to 2026-10-24)
|
|
447
|
+
and scored on the latest 55, it fitted a share of 0.19 and scored a
|
|
448
|
+
median relative error of 0.077 against 0.082 without it, and a mean
|
|
449
|
+
of 0.116 against 0.112. ``calibration.metered_lead``,
|
|
450
|
+
``PowerFit.lead_share``, ``CopCycle.lead_wh``, the snapshots'
|
|
451
|
+
``draw_lead_gal`` and the ``lead`` arguments of ``calibrate``,
|
|
452
|
+
``fit_power``, ``fit_cop_scale`` and ``cop_cycles`` are gone;
|
|
453
|
+
``solve_cop_scale`` returns ``(scale, startup_wh)`` and
|
|
454
|
+
``solve_cop_fit`` ``(scale, startup_wh, lift_coeff)``. A draw in the
|
|
455
|
+
30 minutes before a cycle still keeps it out of the draw-free fit
|
|
456
|
+
(``DRAW_LEAD``). The site's COP terms were fitted without a meter
|
|
457
|
+
and are unchanged.
|
|
458
|
+
|
|
5
459
|
Version 9.12.1 (2026-10-23)
|
|
6
460
|
===========================
|
|
7
461
|
|
|
@@ -140,7 +594,7 @@ Changed
|
|
|
140
594
|
says so in ``defaults_used`` (#232). They raised in every walk, and a
|
|
141
595
|
book left them unpriced. ``sim_adapters.with_circulation_terms`` does
|
|
142
596
|
this for the books.
|
|
143
|
-
- A status in Standby
|
|
597
|
+
- A status in Standby counts as Power Off (#232).
|
|
144
598
|
It raised.
|
|
145
599
|
- ``book_from_changes`` books from the flow's first reading when its
|
|
146
600
|
history starts later than the window, rather than raising (#232).
|
|
@@ -516,7 +970,7 @@ Added
|
|
|
516
970
|
``TOUPeriod``, the cloud's model, reading the controller's 40-slot table
|
|
517
971
|
whole (a ``TOU_PLAN`` read's payload byte is the last slot, not a
|
|
518
972
|
length; ``build_tou_read()``). ``write_tou_records()`` stores a plan of
|
|
519
|
-
up to twelve intervals (``build_tou_write()``:
|
|
973
|
+
up to twelve intervals (``build_tou_write()``: an 8-byte clock
|
|
520
974
|
header, then the records in the read layout) and reads it back. It
|
|
521
975
|
refuses to write over a longer stored plan, whose extra intervals no
|
|
522
976
|
Bluetooth write can clear, and raises ``TouWriteError`` when the
|
|
@@ -527,8 +981,8 @@ Added
|
|
|
527
981
|
(``MSCD``) and ``SoftwareInfoBlock`` (``MSWD``) use the cloud models'
|
|
528
982
|
field names for the fields matched against them.
|
|
529
983
|
``read_sub_status()`` decodes the ``SSD`` block into two DIP-switch
|
|
530
|
-
words (``SubStatus``), using the layout
|
|
531
|
-
|
|
984
|
+
words (``SubStatus``), using the boiler layout; it is unconfirmed on
|
|
985
|
+
the NWP500.
|
|
532
986
|
``read_status()`` returns live status: the class-50 session command 200
|
|
533
987
|
makes the heater push the ``RTMD`` block about every 300 ms until the
|
|
534
988
|
connection closes, and ``RealTimeStatus`` decodes the fields checked
|
|
@@ -553,10 +1007,10 @@ Added
|
|
|
553
1007
|
(``/user/get-push-settings``, ``/user/set-push-settings``),
|
|
554
1008
|
``record_filter_change()`` / ``record_cip_change()`` and
|
|
555
1009
|
``get_filter_history()`` / ``get_cip_history()`` (the cloud's maintenance
|
|
556
|
-
log; dates are sent ``YYYY-M-D`` without zero padding
|
|
1010
|
+
log; dates are sent ``YYYY-M-D`` without zero padding),
|
|
557
1011
|
``get_display_preferences()`` / ``set_display_preferences()``
|
|
558
|
-
(``/device/preferences/format``; the cloud rejects it for the NWP500
|
|
559
|
-
|
|
1012
|
+
(``/device/preferences/format``; the cloud rejects it for the NWP500;
|
|
1013
|
+
it serves the IoT-module hydro unit), ``get_user_profile()`` (``/user/info``),
|
|
560
1014
|
``get_firmware_detail()`` (``/firmware/info``; ``run_time`` is the
|
|
561
1015
|
estimated update time in minutes), ``delete_tou()``
|
|
562
1016
|
(``DELETE /device/tou``), ``get_manufacture_id()``,
|
|
@@ -566,24 +1020,24 @@ Added
|
|
|
566
1020
|
``get_app_version()`` (unauthenticated; a 403 maintenance notice is
|
|
567
1021
|
returned as ``server_check_text``; ``nwp500.get_app_version()`` asks
|
|
568
1022
|
before sign-in, without a client). ``set_push_settings()`` keeps the
|
|
569
|
-
current value of a flag left ``None``, since the
|
|
570
|
-
|
|
1023
|
+
current value of a flag left ``None``, since the cloud expects every
|
|
1024
|
+
flag. ``get_error_history()`` entries carry the list's
|
|
571
1025
|
``timeZone`` (``time_zone``, ``occurred_at_local``). New models: ``PushSettings``,
|
|
572
1026
|
``DeviceInstaller``, ``FirmwareDeviceGroup``, ``FirmwareDetail``,
|
|
573
1027
|
``DisplayPreferences``, ``SimpleDevice``, ``DeviceErrorHistoryEntry``,
|
|
574
1028
|
``MaintenanceHistoryEntry``, ``UserProfile``, ``AppVersionInfo``. Push
|
|
575
1029
|
flags are 1 = on, 2 = off, the reverse of the device's on/off flags, and
|
|
576
1030
|
decode to ``True``/``False``/``None``. ``Device.installer`` and
|
|
577
|
-
``Device.alarm_info`` are parsed from ``/device/info
|
|
1031
|
+
``Device.alarm_info`` are parsed from ``/device/info``;
|
|
578
1032
|
``TOUSchedule.nickname`` is modelled.
|
|
579
1033
|
- **AWS credentials renew without the password.**
|
|
580
1034
|
``NavienAuthClient.refresh_session()`` posts to ``/auth/session/refresh``
|
|
581
1035
|
with the access token and updates only the AWS credential fields the
|
|
582
|
-
server returns
|
|
583
|
-
auth error. ``secured_sign_in()`` posts to ``/user/secured-sign-in`` with
|
|
1036
|
+
server returns; it is the step used when the MQTT connection fails with
|
|
1037
|
+
an auth error. ``secured_sign_in()`` posts to ``/user/secured-sign-in`` with
|
|
584
1038
|
the access token and overlays whatever token fields come back (observed
|
|
585
|
-
live: the AWS credentials only; the JWTs are kept),
|
|
586
|
-
|
|
1039
|
+
live: the AWS credentials only; the JWTs are kept), the auto-login
|
|
1040
|
+
step. When the AWS credentials expire, ``ensure_valid_token()``
|
|
587
1041
|
and the context manager's stored-token check now try session refresh,
|
|
588
1042
|
then secured sign-in, and only then the password sign-in; both token
|
|
589
1043
|
steps are skipped when the access token has expired and cannot be
|
|
@@ -598,14 +1052,14 @@ Added
|
|
|
598
1052
|
control command it publishes on ``cmd/52/navilink-{mac}/res/ctrl-fail``
|
|
599
1053
|
instead of acknowledging it; the library silently dropped that message.
|
|
600
1054
|
It is now parsed into ``ControlFailResponse``
|
|
601
|
-
(``ControlFailCode.DHW_CONTROL_INTERVAL_EXCEEDED`` = 2 is the only
|
|
602
|
-
|
|
1055
|
+
(``ControlFailCode.DHW_CONTROL_INTERVAL_EXCEEDED`` = 2 is the only known
|
|
1056
|
+
code: the previous DHW control was too recent) and emitted
|
|
603
1057
|
as the ``control_failed`` client event (``ControlFailedEvent`` with
|
|
604
1058
|
``own_command`` and ``reason``) for every device subscription, with a
|
|
605
1059
|
WARNING log. ``subscribe_control_fail_response()`` /
|
|
606
1060
|
``unsubscribe_control_fail_response()`` add a plain callback.
|
|
607
1061
|
- **MQTT Last-Will and app-connection presence.** Set
|
|
608
|
-
``MqttConnectionConfig.last_will_device`` to register
|
|
1062
|
+
``MqttConnectionConfig.last_will_device`` to register a Last-Will
|
|
609
1063
|
on ``evt/52/navilink-{mac}/app-connection`` (``event.connection.status``
|
|
610
1064
|
0, QoS 1) so other clients learn when this one drops; every rebuilt
|
|
611
1065
|
connection carries it. ``subscribe_app_connection()`` /
|
|
@@ -613,24 +1067,22 @@ Added
|
|
|
613
1067
|
``AppConnectionEvent`` and emit ``app_connection_received``
|
|
614
1068
|
(``AppConnectionReceivedEvent``). New ``MqttTopicBuilder`` helpers
|
|
615
1069
|
``control_fail_response_topic()`` and ``app_connection_topic()``.
|
|
616
|
-
- **``DeviceStatus.air_filter_remaining_hours``**: the
|
|
1070
|
+
- **``DeviceStatus.air_filter_remaining_hours``**: the remaining
|
|
617
1071
|
filter life (``None`` when the reminder period is 0, else clamped at 0).
|
|
618
|
-
- **``DeviceStatus.minor_code``** (``minorCode``): the secondary alarm code
|
|
619
|
-
|
|
1072
|
+
- **``DeviceStatus.minor_code``** (``minorCode``): the secondary alarm code,
|
|
1073
|
+
formatted as ``R%03d``; 101 is the one a user can reset.
|
|
620
1074
|
Defaults to 0 for payloads that omit it.
|
|
621
|
-
- **``DhwOperationSetting.STANDBY`` (0)**, the
|
|
622
|
-
``
|
|
1075
|
+
- **``DhwOperationSetting.STANDBY`` (0)**, the device's Standby mode.
|
|
1076
|
+
``DeviceStatus.dhw_operation_setting``
|
|
623
1077
|
previously rejected a 0. It is status-only: ``set_dhw_mode()`` refuses
|
|
624
1078
|
it, as it now refuses any id outside 1-6. ``sim_adapters.settings()``
|
|
625
1079
|
refuses a Standby status, which the simulator does not model.
|
|
626
|
-
- **``DREvent.GRID_EMERGENCY`` (6).**
|
|
627
|
-
Emergency both as id 5 but decodes ``drEventStatus`` by array position,
|
|
628
|
-
so raw 6 is Grid Emergency; ``CPE`` (5) stays "Customer Peak Event".
|
|
1080
|
+
- **``DREvent.GRID_EMERGENCY`` (6).** Raw 6 is Grid Emergency; ``CPE`` (5) stays "Customer Peak Event".
|
|
629
1081
|
- **``UnitType``** NCB700 (22), NFB700_H (24), CAS_NFB700_H (25),
|
|
630
1082
|
NPF_SYSTEM (514); **``DeviceType``** IOT_MODULE (8), NFB700_MAIN (50),
|
|
631
|
-
NFB700_SUB (51) (
|
|
632
|
-
|
|
633
|
-
|
|
1083
|
+
NFB700_SUB (51) (``NPF700_MAIN``/``NPF700_SUB`` remain as aliases) and
|
|
1084
|
+
``HPWH`` as an alias of 52, which is the NWP500, not an NPF700 WiFi
|
|
1085
|
+
module. A dumped device
|
|
634
1086
|
of type 50 or 51 now names it ``NFB700_MAIN`` or ``NFB700_SUB``.
|
|
635
1087
|
- **A recovery model's inputs, through ``nwp500.sim.simulate``** (#165).
|
|
636
1088
|
Beside ``recovery=``, ``simulate`` takes what a learned model was
|
|
@@ -984,11 +1436,10 @@ Added
|
|
|
984
1436
|
|
|
985
1437
|
Changed
|
|
986
1438
|
-------
|
|
987
|
-
- **Recirculation mode values
|
|
988
|
-
had 2 and 3 swapped and 4 wrong: the
|
|
989
|
-
``
|
|
990
|
-
|
|
991
|
-
4 No recirculation. The members are now ``ALWAYS_ON``, ``WEEKLY``,
|
|
1439
|
+
- **Recirculation mode values corrected.** ``RecirculationMode``
|
|
1440
|
+
had 2 and 3 swapped and 4 wrong: the recirculation mode (sent by
|
|
1441
|
+
``recirc-mode`` and decoded from ``recircOperationMode``) is 1 Always
|
|
1442
|
+
on, 2 Weekly, 3 HotButton only, 4 No recirculation. The members are now ``ALWAYS_ON``, ``WEEKLY``,
|
|
992
1443
|
``HOT_BUTTON_ONLY`` and ``OFF``; ``ALWAYS`` remains as an alias of
|
|
993
1444
|
``ALWAYS_ON``. **Behaviour change:** ``BUTTON``, ``SCHEDULE`` and
|
|
994
1445
|
``TEMPERATURE`` are removed, since their meanings were wrong; code that
|
|
@@ -999,7 +1450,7 @@ Changed
|
|
|
999
1450
|
``"ALWAYS"``) and ``"WEEKLY"`` for 2 (was ``"BUTTON"``); code keyed on
|
|
1000
1451
|
the old strings must follow.
|
|
1001
1452
|
- **Reservation mode 5 is Power Off, not Vacation.** Weekly-schedule
|
|
1002
|
-
entries carry
|
|
1453
|
+
entries carry a reservation mode (0 not reserved, 1 Heat
|
|
1003
1454
|
Pump, 2 Electric, 3 Energy Saver, 4 High Demand, 5 Power Off; there is
|
|
1004
1455
|
no vacation entry), now the ``ReservationMode`` enum.
|
|
1005
1456
|
``ReservationEntry.mode_name`` resolves through it, so a mode-5 entry
|
|
@@ -1013,20 +1464,19 @@ Changed
|
|
|
1013
1464
|
(``update_reservations(kept=)``). Changing such an entry needs a mode
|
|
1014
1465
|
of 1-5. The simulator's reservation entries take the same numbering
|
|
1015
1466
|
(``sim.inputs.EntryMode``); only 1-4 are modelled, as before.
|
|
1016
|
-
- **``dr_override_status`` is an on/off flag, not hours.**
|
|
1017
|
-
|
|
1467
|
+
- **``dr_override_status`` is an on/off flag, not hours.** It is a 0/1/2
|
|
1468
|
+
on-off value, and 2 means DR applied. The
|
|
1018
1469
|
field stays an ``int`` but no longer carries an ``hours`` unit; the new
|
|
1019
1470
|
``DeviceStatus.dr_override_active`` is True when it is 2.
|
|
1020
|
-
- **Air-filter life comes from ``cumulated_op_time_eva_fan``.**
|
|
1021
|
-
|
|
1022
|
-
|
|
1471
|
+
- **Air-filter life comes from ``cumulated_op_time_eva_fan``.** Remaining
|
|
1472
|
+
filter life is ``air_filter_alarm_period - cumulated_op_time_eva_fan``;
|
|
1473
|
+
``air_filter_alarm_elapsed`` is not part of it.
|
|
1023
1474
|
The two descriptions were the wrong way round; ``cumulated_op_time_eva_fan``
|
|
1024
1475
|
is fan hours since the last filter reset and ``air_filter_alarm_elapsed``
|
|
1025
1476
|
is unconfirmed.
|
|
1026
|
-
- **``signal_app_connection()`` sends the
|
|
1477
|
+
- **``signal_app_connection()`` sends the Last-Will message shape.** It
|
|
1027
1478
|
published ``{clientID, timestamp}``, a shape nothing reads. It now
|
|
1028
|
-
publishes the
|
|
1029
|
-
``event.connection.status`` 1.
|
|
1479
|
+
publishes the Last-Will envelope with ``event.connection.status`` 1.
|
|
1030
1480
|
- Sign-in and ``/auth/refresh`` requests now send the library's
|
|
1031
1481
|
``User-Agent`` and ``Content-Type`` headers, as every other request does.
|
|
1032
1482
|
- **The heater's own air reading is not as close as documented in
|
|
@@ -1058,15 +1508,14 @@ Changed
|
|
|
1058
1508
|
describe both fields as differences.
|
|
1059
1509
|
- **Vacation accepts 1-99 days** (#155). ``set_dhw_mode()`` (vacation),
|
|
1060
1510
|
``set_vacation_days()`` and ``set_vacation_duration()`` rejected anything
|
|
1061
|
-
over 30 days. The
|
|
1062
|
-
|
|
1063
|
-
library now accepts the same. The ``vacation`` and
|
|
1511
|
+
over 30 days. The front panel (User's Information Manual, section
|
|
1512
|
+
3.4.5) offers 1-99, so the library now accepts the same. The ``vacation`` and
|
|
1064
1513
|
``vacation-duration`` CLI commands follow.
|
|
1065
1514
|
|
|
1066
1515
|
Fixed
|
|
1067
1516
|
-----
|
|
1068
|
-
- ``FirmwareInfo.device_group`` was typed as a string; the
|
|
1069
|
-
object ``{groupSeq, unitNum}`` (plus a top-level ``deviceGroupUnitNo``).
|
|
1517
|
+
- ``FirmwareInfo.device_group`` was typed as a string; the cloud returns
|
|
1518
|
+
an object ``{groupSeq, unitNum}`` (plus a top-level ``deviceGroupUnitNo``).
|
|
1070
1519
|
It is now ``FirmwareDeviceGroup``, with a bare string still accepted.
|
|
1071
1520
|
- ``convert_tou()`` hard-coded ``userType: "O"``; it now sends the
|
|
1072
1521
|
signed-in user's type from the sign-in response, defaulting to ``"O"``.
|
|
@@ -1101,8 +1550,6 @@ Documentation
|
|
|
1101
1550
|
physics with a learned correction, and are removed. The "cold starts
|
|
1102
1551
|
about 55 min short" figure is replaced by the recorded errors by time
|
|
1103
1552
|
left. The lower trigger's in-window wait is given with its full count.
|
|
1104
|
-
- ``unknown-values``, ``tank-energy`` and the MQTT protocol reference now
|
|
1105
|
-
cite the current NaviLink app release they were checked against.
|
|
1106
1553
|
- ``docs/openapi.yaml`` and ``rest_api.rst`` describe every endpoint above
|
|
1107
1554
|
plus ``POST /auth/session/refresh`` and ``POST /user/secured-sign-in``;
|
|
1108
1555
|
``mqtt_protocol.rst`` documents ``res/ctrl-fail`` and the
|
|
@@ -1140,13 +1587,12 @@ Documentation
|
|
|
1140
1587
|
- The ambient effect on the compressor is about 1.18x, not 1.7x; the
|
|
1141
1588
|
standby soak's figures, the 60 h gap prior and the circulation runs
|
|
1142
1589
|
behind the lower-zone drop are stated as measured.
|
|
1143
|
-
- **Docs that contradicted the code or the
|
|
1590
|
+
- **Docs that contradicted the code or the protocol** (#155). Each
|
|
1144
1591
|
now has one value with its source:
|
|
1145
1592
|
|
|
1146
1593
|
- The mode-ID table in ``device_features.rst`` numbered Energy Saver 2,
|
|
1147
1594
|
High Demand 3 and Electric 4. The IDs are Electric 2, Energy Saver 3,
|
|
1148
|
-
High Demand 4, as in ``DhwOperationSetting
|
|
1149
|
-
``MgppOperationMode``.
|
|
1595
|
+
High Demand 4, as in ``DhwOperationSetting``.
|
|
1150
1596
|
- ``track-energy.rst`` used ``comp_running_minute_total`` and
|
|
1151
1597
|
``heater1/2_running_minute_total``, which do not exist. Lifetime run
|
|
1152
1598
|
times come from the diagnostics query, in hours
|
|
@@ -1177,12 +1623,11 @@ Documentation
|
|
|
1177
1623
|
- **Flow-rate units corrected** (#154). The protocol reference said
|
|
1178
1624
|
``currentDhwFlowRate`` and ``recircDhwFlowRate`` were tenths of a GPM and
|
|
1179
1625
|
``cumulatedDhwFlowRate`` was gallons, while the properties treated them as
|
|
1180
|
-
tenths of an LPM and as litres. The properties are right.
|
|
1181
|
-
|
|
1182
|
-
``raw /
|
|
1626
|
+
tenths of an LPM and as litres. The properties are right.
|
|
1627
|
+
``currentDhwFlowRate`` is ``raw / 10`` LPM in Celsius and
|
|
1628
|
+
``raw / 3.785 / 10`` GPM in Fahrenheit.
|
|
1183
1629
|
The NWP500 manual lists the panel's water-flow reading in LPM even though
|
|
1184
|
-
the panel defaults to GPM.
|
|
1185
|
-
hot-water volume totals in whole litres in either setting. Flow rates are
|
|
1630
|
+
the panel defaults to GPM. Flow rates are
|
|
1186
1631
|
0.1 LPM and the cumulative counter is whole litres in both unit systems.
|
|
1187
1632
|
``device_status.rst``, ``data_conversions.rst`` and ``mqtt_protocol.rst``
|
|
1188
1633
|
now say so, and ``DiagnosticsDhwUsage`` documents its units (litres, hours,
|
|
@@ -1193,7 +1638,7 @@ Documentation
|
|
|
1193
1638
|
fixed ``"GPM"`` or ``"gal"`` ``unit_of_measurement``, which contradicted
|
|
1194
1639
|
their raw units. ``get_field_unit()``, which was already driven by
|
|
1195
1640
|
``device_class``, returns the same units as before. New tests pin both
|
|
1196
|
-
flow properties to
|
|
1641
|
+
flow properties to these formulas in each unit system.
|
|
1197
1642
|
|
|
1198
1643
|
Version 9.4.3 (2026-10-01)
|
|
1199
1644
|
==========================
|
|
@@ -1364,31 +1809,30 @@ Fixed
|
|
|
1364
1809
|
Version 9.4.0 (2026-09-12)
|
|
1365
1810
|
==========================
|
|
1366
1811
|
|
|
1367
|
-
This release realigns the MQTT command set with
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
schedule write was corrected. Breaking changes are listed under
|
|
1812
|
+
This release realigns the MQTT command set with the NaviLink protocol.
|
|
1813
|
+
Every query in the protocol now has a method, six commands that are not
|
|
1814
|
+
part of it were removed, and the recirculation schedule write was
|
|
1815
|
+
corrected. Breaking changes are listed under
|
|
1372
1816
|
*Removed* and *Changed*.
|
|
1373
1817
|
|
|
1374
1818
|
Added
|
|
1375
1819
|
-----
|
|
1376
1820
|
- **Installer diagnostics query.** ``request_diagnostics()`` /
|
|
1377
1821
|
``subscribe_diagnostics()`` and the ``nwp-cli diagnostics`` command
|
|
1378
|
-
return the
|
|
1822
|
+
return the device's lifetime installer counters: heat
|
|
1379
1823
|
pump and element energy, days since installation, fault event counts,
|
|
1380
1824
|
demand-response operation times, hot-water draw statistics and
|
|
1381
1825
|
component run times and start counts, as
|
|
1382
1826
|
:class:`~nwp500.models.DeviceDiagnostics`. The device answers the
|
|
1383
1827
|
``st/td/rd`` query with packed hex; the NaviLink cloud decodes it into
|
|
1384
|
-
JSON only when the reply topic has the
|
|
1828
|
+
JSON only when the reply topic has the five-segment form, so the
|
|
1385
1829
|
library requests that form. Verified live: the energies and compressor
|
|
1386
1830
|
and element run times match the energy query's lifetime totals.
|
|
1387
1831
|
- **Monthly energy query.** ``request_energy_usage_monthly()`` /
|
|
1388
1832
|
``subscribe_energy_usage_monthly()`` and ``nwp-cli energy --years``
|
|
1389
1833
|
return one entry per year with twelve per-month items. Verified live.
|
|
1390
1834
|
- **Hourly energy query.** ``request_energy_usage_hourly()`` /
|
|
1391
|
-
``subscribe_energy_usage_hourly()`` send the
|
|
1835
|
+
``subscribe_energy_usage_hourly()`` send the per-hour query. The
|
|
1392
1836
|
NWP500 tested never answered it in four attempts, so it has no CLI
|
|
1393
1837
|
command.
|
|
1394
1838
|
- **Recirculation schedule read.** ``request_recirculation_schedule()``
|
|
@@ -1405,46 +1849,43 @@ Added
|
|
|
1405
1849
|
``subscribe_firmware_download_info()`` and ``nwp-cli firmware info``
|
|
1406
1850
|
return :class:`~nwp500.models.FirmwareDownloadInfo`. The device replies
|
|
1407
1851
|
on its own topic rather than a client-keyed one. Verified live.
|
|
1408
|
-
- **Session end.** ``end_session()`` sends the ``st/end`` query
|
|
1409
|
-
|
|
1852
|
+
- **Session end.** ``end_session()`` sends the ``st/end`` query that
|
|
1853
|
+
ends a session with a device. No reply to it has been observed.
|
|
1410
1854
|
Unlike other commands it is never queued while disconnected (it raises
|
|
1411
1855
|
``MqttNotConnectedError``), since a session end replayed after a
|
|
1412
1856
|
reconnect would end the new session.
|
|
1413
1857
|
- ``MqttConnectionConfig.send_session_end_on_disconnect`` (default
|
|
1414
1858
|
``True``) controls whether ``disconnect()`` sends it.
|
|
1415
1859
|
- ``subscribe_firmware_commit_response()`` receives the reply to
|
|
1416
|
-
``commit_firmware_update()``, which is requested on the
|
|
1860
|
+
``commit_firmware_update()``, which is requested on the five-segment
|
|
1417
1861
|
``res/commit-ota`` topic that no other subscription covers.
|
|
1418
|
-
- **Controls
|
|
1419
|
-
``set_vacation_duration(days)`` (``goout-day``;
|
|
1420
|
-
|
|
1421
|
-
without changing the operation mode),
|
|
1862
|
+
- **Controls that had no method:**
|
|
1863
|
+
``set_vacation_duration(days)`` (``goout-day``; verified live to change
|
|
1864
|
+
the day count without changing the operation mode),
|
|
1422
1865
|
``set_air_filter_life(hours)`` (``air-filter-life``; the wire value is
|
|
1423
|
-
hours divided by 500,
|
|
1866
|
+
hours divided by 500, for 0 or 1000-10000 hours;
|
|
1424
1867
|
verified live) and ``reset_condenser_fault()`` (``cond-fault-reset``,
|
|
1425
|
-
installer-level
|
|
1868
|
+
an installer-level control; acknowledged live, clearing effect not
|
|
1426
1869
|
observable without a fault). CLI: ``vacation-duration``,
|
|
1427
1870
|
``filter-life``, ``reset-condenser-fault``.
|
|
1428
|
-
- **Command codes** for every
|
|
1429
|
-
|
|
1871
|
+
- **Command codes** for every known control command, with the
|
|
1872
|
+
declared-only ones annotated.
|
|
1430
1873
|
- ``EnergyUsageResponse.type_of_usage``, ``get_year_data()``,
|
|
1431
1874
|
``MonthlyEnergyData.day`` and ``EnergyUsageDay.ep_usage`` /
|
|
1432
|
-
``water_usage`` (
|
|
1875
|
+
``water_usage`` (part of the protocol; not reported by the NWP500).
|
|
1433
1876
|
|
|
1434
1877
|
Changed
|
|
1435
1878
|
-------
|
|
1436
|
-
- **Recirculation schedule models take the
|
|
1879
|
+
- **Recirculation schedule models take the reservation entry shape** (breaking).
|
|
1437
1880
|
:class:`~nwp500.models.RecirculationScheduleEntry` is now
|
|
1438
1881
|
``enable``/``week``/``hour``/``min``/``mode``/``param`` like a
|
|
1439
|
-
reservation entry (``mode`` is the entry's pump on/off toggle
|
|
1440
|
-
app's editor) and :class:`~nwp500.models.RecirculationSchedule` carries
|
|
1882
|
+
reservation entry (``mode`` is the entry's pump on/off toggle) and :class:`~nwp500.models.RecirculationSchedule` carries
|
|
1441
1883
|
``reservation_use`` plus ``reservation`` instead of ``schedule``. The
|
|
1442
1884
|
previous start/end window fields were not what the device accepts.
|
|
1443
1885
|
- ``MonthlyEnergyData.month`` is optional: monthly-query entries have no
|
|
1444
1886
|
month.
|
|
1445
1887
|
- **``NavienMqttClient.disconnect()`` now sends ``st/end``** to every
|
|
1446
|
-
device that was passed to any subscription
|
|
1447
|
-
when it leaves a device. The publishes go straight to the connection,
|
|
1888
|
+
device that was passed to any subscription. The publishes go straight to the connection,
|
|
1448
1889
|
never into the offline command queue, stop if the connection drops,
|
|
1449
1890
|
are bounded by a short timeout and never fail the disconnect. What it
|
|
1450
1891
|
does to other clients connected to the same device (for example a Home
|
|
@@ -1468,12 +1909,12 @@ Fixed
|
|
|
1468
1909
|
-----
|
|
1469
1910
|
- **``configure_recirculation_schedule()`` published to the wrong topic
|
|
1470
1911
|
with the wrong keys.** It sent ``{"schedule": [...]}`` on the bare
|
|
1471
|
-
``ctrl`` topic;
|
|
1472
|
-
|
|
1473
|
-
|
|
1912
|
+
``ctrl`` topic; RECIR_RESERVATION goes on ``ctrl/recirc-rsv/rd``
|
|
1913
|
+
with ``reservationUse`` and ``reservation`` and asks for the echo on
|
|
1914
|
+
``recirc-rsv/rd``.
|
|
1474
1915
|
- **``commit_firmware_update()`` published to the bare ``ctrl`` topic.**
|
|
1475
|
-
|
|
1476
|
-
|
|
1916
|
+
OTA_COMMIT goes on ``ctrl/commit-ota`` and asks for the reply on
|
|
1917
|
+
``res/commit-ota``; the library now does the same. Not
|
|
1477
1918
|
exercised against a device.
|
|
1478
1919
|
- **Unsubscribing could remove the wrong handler.** When one callback was
|
|
1479
1920
|
registered for more than one device-wildcard subscription (status,
|
|
@@ -1487,8 +1928,8 @@ Fixed
|
|
|
1487
1928
|
- Recirculation read-backs in hex carry the unused ``param`` as ``0xFF``
|
|
1488
1929
|
and in JSON as ``-1``; the model normalizes to ``-1`` so the two compare
|
|
1489
1930
|
equal.
|
|
1490
|
-
- **Protocol documentation** rewritten from the
|
|
1491
|
-
|
|
1931
|
+
- **Protocol documentation** rewritten from the protocol and live
|
|
1932
|
+
captures: real topic structure, the anti-legionella
|
|
1492
1933
|
modes (``anti-leg-on [days]`` / ``anti-leg-off``, not
|
|
1493
1934
|
``anti-legionella-setting``), the message example that paired the
|
|
1494
1935
|
weekly-reservation code with ``dhw-temperature``, the undocumented
|
|
@@ -1505,17 +1946,16 @@ Fixed
|
|
|
1505
1946
|
|
|
1506
1947
|
Removed
|
|
1507
1948
|
-------
|
|
1508
|
-
- **Six MQTT commands
|
|
1949
|
+
- **Six MQTT commands that are not part of the protocol** (breaking):
|
|
1509
1950
|
``check_firmware_update()``, ``reconnect_wifi()``, ``reset_wifi()``,
|
|
1510
1951
|
``set_freeze_protection_temperature()``, ``run_smart_diagnostic()`` and
|
|
1511
1952
|
``update_weekly_reservation()`` with
|
|
1512
1953
|
``subscribe_weekly_reservation_response()`` and the
|
|
1513
1954
|
``WeeklyReservationEntry`` / ``WeeklyReservationSchedule`` models. The
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
``
|
|
1517
|
-
|
|
1518
|
-
program: it writes with 16777226 on ``ctrl/rsv/rd``
|
|
1955
|
+
first five codes are declared only, so the library's ``ota-check``,
|
|
1956
|
+
``wifi-reconnect``, ``wifi-reset``, ``frez-temp`` and
|
|
1957
|
+
``smart-diagnostic`` mode strings were invented. The weekly schedule is
|
|
1958
|
+
the ordinary reservation program: it is written with 16777226 on ``ctrl/rsv/rd``
|
|
1519
1959
|
(``update_reservations()``), never with 33554438 or a ``rsv-weekly/rd``
|
|
1520
1960
|
topic. The ``CommandCode`` members stay, annotated as declared only.
|
|
1521
1961
|
|
|
@@ -1581,12 +2021,10 @@ Removed
|
|
|
1581
2021
|
waited for a reply on ``res/tou/rd``. No reply ever comes: a live device
|
|
1582
2022
|
with TOU provisioned (``program_reservation_use`` true, a valid controller
|
|
1583
2023
|
serial, and a plan the REST API returns in full) stayed silent for 45
|
|
1584
|
-
seconds. ``ctrl/tou/rd`` with that command code is the *write
|
|
1585
|
-
:meth:`~nwp500.NavienMqttClient.configure_tou_schedule` publishes and
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
on ``res/tou/rd`` only to confirm such a write. The vendor app reads TOU
|
|
1589
|
-
over REST, which is the only TOU read the protocol has.
|
|
2024
|
+
seconds. ``ctrl/tou/rd`` with that command code is the *write*: it is what
|
|
2025
|
+
:meth:`~nwp500.NavienMqttClient.configure_tou_schedule` publishes, and the
|
|
2026
|
+
device answers on ``res/tou/rd`` only to confirm such a write. REST is the
|
|
2027
|
+
only TOU read the protocol has.
|
|
1590
2028
|
|
|
1591
2029
|
Beyond returning nothing, the call published a write-shaped command with no
|
|
1592
2030
|
schedule attached. This device ignored it (``touStatus`` was unchanged
|
|
@@ -1627,10 +2065,9 @@ Fixed
|
|
|
1627
2065
|
unknown keys, so the field silently reported its ``False`` default on
|
|
1628
2066
|
every device regardless of whether a thermostatic mixing valve was
|
|
1629
2067
|
supported. The CLI's "Mixing Valve" capability row was wrong for the
|
|
1630
|
-
same reason. The misspelling is Navien's own:
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
wire spelling correctly all along.
|
|
2068
|
+
same reason. The misspelling is Navien's own:
|
|
2069
|
+
``docs/reference/protocol/device_features.rst`` documented the wire
|
|
2070
|
+
spelling correctly all along.
|
|
1634
2071
|
|
|
1635
2072
|
- **``firmware_payload_capture.py`` captured almost nothing.** Its wildcards
|
|
1636
2073
|
covered ``cmd/{type}/{client_id}/res/#`` and the event topic, but the device
|
|
@@ -1706,10 +2143,6 @@ Changed
|
|
|
1706
2143
|
- **Status flags now preserve the device's unknown state.** The protocol
|
|
1707
2144
|
encodes these flags as ``0 = unknown, 1 = OFF, 2 = ON``, and the library
|
|
1708
2145
|
was collapsing 0 to ``False``, inventing an OFF the device never claimed.
|
|
1709
|
-
Confirmed against Navien's own NaviLink app (2.03.00, versionCode 141),
|
|
1710
|
-
which decodes exactly this set of fields through an enum declared
|
|
1711
|
-
``UNKNOWN(0), OFF(1), ON(2)``; two sibling enums render their zero as
|
|
1712
|
-
``"-"`` and ``"Not Applied"`` rather than as an off state.
|
|
1713
2146
|
|
|
1714
2147
|
Affected: ``operation_busy``, ``comp_use``, ``anti_legionella_use``,
|
|
1715
2148
|
``anti_legionella_operation_busy``, ``heat_upper_use``, ``heat_lower_use``,
|
|
@@ -1721,7 +2154,7 @@ Changed
|
|
|
1721
2154
|
this is the wanted shape: ``None`` renders as "Unknown" instead of writing
|
|
1722
2155
|
a fabricated OFF into the recorder database.
|
|
1723
2156
|
|
|
1724
|
-
- ``OnOffFlag`` gains
|
|
2157
|
+
- ``OnOffFlag`` gains an ``UNKNOWN = 0`` member. It previously
|
|
1725
2158
|
started at ``OFF = 1``, leaving the device's reserved value unrepresented.
|
|
1726
2159
|
|
|
1727
2160
|
- The CLI renders these flags as ``Unknown`` rather than ``No``. Both
|
|
@@ -1746,7 +2179,7 @@ Added
|
|
|
1746
2179
|
report. ``converters.device_bool_to_python`` is unchanged and remains
|
|
1747
2180
|
correct for capability flags.
|
|
1748
2181
|
- New ``docs/explanation/unknown-values.rst`` recording which field families
|
|
1749
|
-
use 0 as a sentinel and which do not, with the
|
|
2182
|
+
use 0 as a sentinel and which do not, with the evidence for each.
|
|
1750
2183
|
|
|
1751
2184
|
Removed
|
|
1752
2185
|
-------
|
|
@@ -1775,8 +2208,6 @@ Fixed
|
|
|
1775
2208
|
- ``dhwTemperature`` is documented as an outlet temperature but is
|
|
1776
2209
|
measured inside the tank: it tracks ``tankUpperTemperature`` to within
|
|
1777
2210
|
one 0.5 degC step, and the device has no sensor downstream of itself.
|
|
1778
|
-
Navien's own app agrees, labelling it "DHW Temp." beside the tank
|
|
1779
|
-
thermistors and showing ``dischargeTemperature`` separately.
|
|
1780
2211
|
- ``docs/explanation/tank-energy.rst`` tabulated ``totalEnergyCapacity``
|
|
1781
2212
|
and ``availableEnergyCapacity`` at ten times their raw wire values,
|
|
1782
2213
|
under column headings naming the raw protocol fields. The series had
|
|
@@ -1794,19 +2225,16 @@ Fixed
|
|
|
1794
2225
|
in the changelog, the model field description and the protocol
|
|
1795
2226
|
reference.
|
|
1796
2227
|
- New ``docs/explanation/tank-energy.rst`` records what the two fields
|
|
1797
|
-
measure and the calibration evidence behind the scale change
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
has no out-of-band constant (no ``0xFFFF``/``-999``/``-1``), no zero-guard
|
|
1803
|
-
in any display path, and formats whatever arrives, so a temperature of
|
|
1804
|
-
zero means zero. This closes a recurring source of bugs where zero-as-none
|
|
2228
|
+
measure and the calibration evidence behind the scale change.
|
|
2229
|
+
|
|
2230
|
+
- Documented that **temperature fields carry no sentinel at all**. There
|
|
2231
|
+
is no out-of-band constant (no ``0xFFFF``/``-999``/``-1``), so a
|
|
2232
|
+
temperature of zero means zero. This closes a recurring source of bugs where zero-as-none
|
|
1805
2233
|
was applied to temperature converters and reported working sensors as
|
|
1806
2234
|
missing during cold-weather operation.
|
|
1807
|
-
- Documented that capability flags are a distinct case:
|
|
1808
|
-
|
|
1809
|
-
|
|
2235
|
+
- Documented that capability flags are a distinct case: a DID ``Use``
|
|
2236
|
+
flag of 0 means "not fitted", so the existing ``bool`` mapping is
|
|
2237
|
+
correct.
|
|
1810
2238
|
|
|
1811
2239
|
Version 9.2.1 (2026-07-30)
|
|
1812
2240
|
==========================
|
|
@@ -2100,8 +2528,8 @@ Bug Fixes
|
|
|
2100
2528
|
(default 300 s, ``None`` to disable) are now discarded at send time.
|
|
2101
2529
|
- **Fix Fahrenheit conversion for sub-zero temperatures** (ASYMMETRIC
|
|
2102
2530
|
formula): the rounding used Python's floored ``%``, which is always
|
|
2103
|
-
non-negative, while the firmware
|
|
2104
|
-
``-11`` (-5.5 °C) decoded to 22 °F instead of
|
|
2531
|
+
non-negative, while the firmware uses a truncated remainder. Raw
|
|
2532
|
+
``-11`` (-5.5 °C) decoded to 22 °F instead of 23 °F. Now
|
|
2105
2533
|
uses ``math.fmod`` semantics.
|
|
2106
2534
|
- **Fix freeze protection default limits**: the defaults (43/65) were
|
|
2107
2535
|
Fahrenheit display values stored in raw half-Celsius fields, decoding
|
|
@@ -2530,7 +2958,7 @@ Fixed
|
|
|
2530
2958
|
- **Vacation mode sent wrong MQTT command**: ``set_vacation_days()`` used
|
|
2531
2959
|
``CommandCode.GOOUT_DAY`` (33554466), which the device silently accepted
|
|
2532
2960
|
but did not activate vacation mode; the operating mode remained unchanged.
|
|
2533
|
-
HAR capture of
|
|
2961
|
+
A HAR capture of NaviLink traffic confirms the correct command is
|
|
2534
2962
|
``DHW_MODE`` (33554437) with ``param=[5, days]``
|
|
2535
2963
|
(``DhwOperationSetting.VACATION``). The valid range has also been corrected
|
|
2536
2964
|
from 1-365 to 1-30 to match the device's actual constraint.
|
|
@@ -2603,7 +3031,7 @@ Added
|
|
|
2603
3031
|
-----
|
|
2604
3032
|
- **OpenEI Client Module**: New ``OpenEIClient`` async client (``nwp500.openei``) for browsing utility rate plans from the OpenEI API by zip code. Supports listing utilities, filtering rate plans, and fetching plan details. API key read from ``OPENEI_API_KEY`` environment variable.
|
|
2605
3033
|
- **Convert TOU API**: ``NavienAPIClient.convert_tou()`` sends raw OpenEI rate data to the Navien backend for server-side conversion into device-ready TOU schedules with season/week bitfields and scaled pricing.
|
|
2606
|
-
- **Update TOU API**: ``NavienAPIClient.update_tou()`` applies a converted TOU rate plan to a device
|
|
3034
|
+
- **Update TOU API**: ``NavienAPIClient.update_tou()`` applies a converted TOU rate plan to a device through the cloud's ``PUT /device/tou`` endpoint.
|
|
2607
3035
|
- **ConvertedTOUPlan Model**: New Pydantic model for parsed ``convert_tou()`` results (utility, name, schedule).
|
|
2608
3036
|
- **CLI ``tou rates``**: Browse utilities and rate plans for a zip code (``nwp500 tou rates 94903``).
|
|
2609
3037
|
- **CLI ``tou plan``**: View converted rate plan details with decoded pricing (``nwp500 tou plan 94903 "EV Rate A"``).
|
|
@@ -2617,8 +3045,8 @@ Changed
|
|
|
2617
3045
|
|
|
2618
3046
|
Fixed
|
|
2619
3047
|
-----
|
|
2620
|
-
- **Week Bitfield Encoding**: Fixed MGPP week bitfield encoding to match NaviLink
|
|
2621
|
-
- **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
|
|
3048
|
+
- **Week Bitfield Encoding**: Fixed MGPP week bitfield encoding to match the NaviLink 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.
|
|
3049
|
+
- **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. Global reservation status now correctly shows DISABLED when ``reservationUse=1``.
|
|
2622
3050
|
- **Reservation Set Command Timeout**: Fixed ``reservations set`` subscription pattern that had extra wildcards preventing response matching. Command now receives confirmations correctly.
|
|
2623
3051
|
- **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.
|
|
2624
3052
|
|
|
@@ -3561,7 +3989,7 @@ Version 6.0.4 (2025-11-21)
|
|
|
3561
3989
|
Fixed
|
|
3562
3990
|
-----
|
|
3563
3991
|
|
|
3564
|
-
- **Temperature Conversion Accuracy**: Corrected temperature conversion logic
|
|
3992
|
+
- **Temperature Conversion Accuracy**: Corrected temperature conversion logic. Previous conversions used approximations; new logic uses the exact formulas the protocol uses:
|
|
3565
3993
|
|
|
3566
3994
|
- Replaced ``Add20`` validator with ``HalfCelsiusToF`` for fields transmitted as half-degrees Celsius
|
|
3567
3995
|
- Replaced ``DeciCelsiusToF`` with ``PentaCelsiusToF`` for fields scaled by factor of 5
|
|
@@ -4430,7 +4858,7 @@ Fixed
|
|
|
4430
4858
|
- Power-off now uses command code ``33554433``
|
|
4431
4859
|
- Power-on now uses command code ``33554434``
|
|
4432
4860
|
- DHW mode control now uses command code ``33554437``
|
|
4433
|
-
- Discovered through network traffic analysis
|
|
4861
|
+
- Discovered through network traffic analysis
|
|
4434
4862
|
|
|
4435
4863
|
- **Bug**: MQTT topic pattern matching with wildcards
|
|
4436
4864
|
|