nwp500-python 9.11.0__tar.gz → 9.12.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/CHANGELOG.rst +165 -0
- {nwp500_python-9.11.0/src/nwp500_python.egg-info → nwp500_python-9.12.0}/PKG-INFO +1 -1
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/how-to/calibrate-simulator.rst +45 -23
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/how-to/simulate-heater.rst +154 -18
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/reference/python_api/sim.rst +6 -3
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/reference/simulator-inputs.rst +44 -17
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/cli/calibration.py +2 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/cli/simulate.py +1 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/sim/api.py +50 -13
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/sim/book.py +38 -5
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/sim/calibration.py +140 -33
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/sim/circulation.py +96 -7
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/sim/defaults.py +26 -9
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/sim/delivery.py +30 -12
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/sim/inputs.py +3 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/sim/params.py +17 -3
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/sim/ready.py +18 -7
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/sim_adapters.py +443 -51
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/sim_calibrate.py +250 -51
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/sim_home_assistant.py +964 -203
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/sim_live.py +30 -2
- {nwp500_python-9.11.0 → nwp500_python-9.12.0/src/nwp500_python.egg-info}/PKG-INFO +1 -1
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500_python.egg-info/scm_version.json +2 -2
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_sim_adapters.py +8 -3
- nwp500_python-9.12.0/tests/test_sim_book_history.py +954 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_sim_bottom_loss.py +11 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_sim_calibrate.py +136 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_sim_calibration.py +9 -0
- nwp500_python-9.12.0/tests/test_sim_change_history.py +1373 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_sim_circulation.py +122 -6
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_sim_defaults.py +16 -4
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_sim_fit_book.py +139 -72
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_sim_home_assistant.py +436 -10
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_sim_hot_water.py +10 -2
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_sim_live.py +23 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_sim_overlay.py +51 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_sim_ready_api.py +165 -0
- nwp500_python-9.11.0/tests/test_sim_book_history.py +0 -435
- nwp500_python-9.11.0/tests/test_sim_change_history.py +0 -572
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/.coveragerc +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/.github/RESOLVING_PR_COMMENTS.md +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/.github/copilot-instructions.md +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/.github/workflows/ci.yml +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/.github/workflows/release.yml +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/.gitignore +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/.pre-commit-config.yaml +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/.readthedocs.yml +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/AUTHORS.rst +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/CONTRIBUTING.rst +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/LICENSE.txt +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/Makefile +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/README.rst +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/RELEASE.md +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/Makefile +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/_static/.gitignore +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/conf.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/explanation/architecture.rst +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/explanation/draws.rst +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/explanation/heating-elements.rst +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/explanation/index.rst +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/explanation/recovery-speed.rst +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/explanation/simulator.rst +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/explanation/standby-loss.rst +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/explanation/tank-energy.rst +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/explanation/tou-recovery-cap.rst +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/explanation/unknown-values.rst +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/explanation/what-starts-a-recovery.rst +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/how-to/authenticate.rst +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/how-to/auto-recovery.rst +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/how-to/bluetooth.rst +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/how-to/diagnose-mqtt.rst +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/how-to/home-assistant.rst +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/how-to/index.rst +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/how-to/maintenance.rst +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/how-to/manage-units.rst +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/how-to/monitor-status.rst +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/how-to/optimize-tou.rst +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/how-to/queue-commands.rst +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/how-to/schedule-operation.rst +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/how-to/track-energy.rst +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/index.rst +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/openapi.yaml +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/project/authors.rst +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/project/changelog.rst +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/project/contributing.rst +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/project/history.rst +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/project/license.rst +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/reference/configuration.rst +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/reference/enumerations.rst +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/reference/index.rst +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/reference/installation.rst +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/reference/protocol/data_conversions.rst +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/reference/protocol/device_features.rst +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/reference/protocol/device_status.rst +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/reference/protocol/error_codes.rst +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/reference/protocol/mqtt_protocol.rst +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/reference/protocol/quick_reference.rst +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/reference/protocol/rest_api.rst +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/reference/python_api/api_client.rst +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/reference/python_api/auth_client.rst +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/reference/python_api/cli.rst +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/reference/python_api/events.rst +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/reference/python_api/exceptions.rst +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/reference/python_api/models.rst +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/reference/python_api/mqtt_client.rst +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/requirements.txt +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/docs/tutorials/getting-started.rst +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/examples/.ruff.toml +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/examples/README.md +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/examples/advanced/air_filter_reset.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/examples/advanced/anti_legionella.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/examples/advanced/auto_recovery.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/examples/advanced/combined_callbacks.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/examples/advanced/demand_response.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/examples/advanced/device_capabilities.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/examples/advanced/device_status_debug.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/examples/advanced/energy_analytics.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/examples/advanced/error_code_demo.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/examples/advanced/firmware_payload_capture.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/examples/advanced/mqtt_diagnostics.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/examples/advanced/power_control.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/examples/advanced/recirculation_control.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/examples/advanced/reconnection_demo.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/examples/advanced/reservation_schedule.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/examples/advanced/simple_auto_recovery.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/examples/advanced/token_restoration.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/examples/advanced/tou_openei.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/examples/advanced/tou_schedule.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/examples/advanced/water_reservation.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/examples/beginner/01_authentication.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/examples/beginner/02_list_devices.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/examples/beginner/03_get_status.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/examples/beginner/04_set_temperature.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/examples/intermediate/advanced_auth_patterns.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/examples/intermediate/command_queue.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/examples/intermediate/device_status_callback.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/examples/intermediate/error_handling.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/examples/intermediate/event_driven_control.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/examples/intermediate/improved_auth.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/examples/intermediate/legacy_auth_constructor.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/examples/intermediate/mqtt_realtime_monitoring.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/examples/intermediate/periodic_requests.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/examples/intermediate/set_mode.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/examples/intermediate/vacation_mode.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/examples/mask.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/examples/simulator/lower-setpoint.json +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/examples/simulator/plan.json +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/examples/simulator/scenario.json +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/examples/testing/periodic_device_info.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/examples/testing/simple_periodic_info.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/examples/testing/test_api_client.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/examples/testing/test_mqtt_connection.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/examples/testing/test_mqtt_messaging.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/examples/testing/test_periodic_minimal.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/pyproject.toml +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/scripts/README.md +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/scripts/bump_version.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/scripts/diagnose_mqtt_connection.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/scripts/extract_changelog.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/scripts/format.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/scripts/lint.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/scripts/setup-dev.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/scripts/validate_version.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/setup.cfg +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/setup.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/__init__.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/_base.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/api_client.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/auth.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/ble/__init__.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/ble/__main__.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/ble/client.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/ble/mqtt_adapter.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/ble/protocol.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/cli/__init__.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/cli/__main__.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/cli/ble_backend.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/cli/handlers.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/cli/monitoring.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/cli/output_formatters.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/cli/presentation.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/cli/recording.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/cli/rich_output.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/cli/token_storage.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/command_decorators.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/config.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/converters.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/device_capabilities.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/device_info_cache.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/encoding.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/enums.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/events.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/exceptions.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/factory.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/field_factory.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/models/__init__.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/models/_converters.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/models/account.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/models/device.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/models/diagnostics.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/models/energy.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/models/feature.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/models/firmware.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/models/maintenance.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/models/mqtt_models.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/models/schedule.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/models/status.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/models/tou.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/mqtt/__init__.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/mqtt/_control_commands.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/mqtt/_device_subscriptions.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/mqtt/client.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/mqtt/command_queue.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/mqtt/connection.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/mqtt/control.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/mqtt/diagnostics.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/mqtt/periodic.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/mqtt/reconnection.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/mqtt/state_tracker.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/mqtt/subscriptions.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/mqtt/types.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/mqtt/utils.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/mqtt_events.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/openei.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/py.typed +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/reservations.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/sim/__init__.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/sim/attribution.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/sim/codec.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/sim/cold_supply.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/sim/controller.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/sim/decay.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/sim/draw_forecast.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/sim/engine.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/sim/extensions.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/sim/heat_pump_recovery.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/sim/heating_trajectory.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/sim/location_air.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/sim/observed.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/sim/physics/__init__.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/sim/physics/compressor.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/sim/physics/draw.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/sim/physics/element.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/sim/physics/refrigerant.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/sim/physics/tank.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/sim/profile.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/sim/record.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/sim/result.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/sim/schedules.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/sim/tank_physics.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/sim/tou_triggers.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/sim/units.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/status_history.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/temperature.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/topic_builder.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/tou_schedule.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/unit_system.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500/utils.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500_python.egg-info/SOURCES.txt +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500_python.egg-info/dependency_links.txt +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500_python.egg-info/entry_points.txt +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500_python.egg-info/not-zip-safe +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500_python.egg-info/requires.txt +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500_python.egg-info/scm_file_list.json +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/src/nwp500_python.egg-info/top_level.txt +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/_sim_site.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/conftest.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/fixtures/sim/engine_golden.json +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/fixtures/sim/heater_params.json +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/fixtures/sim/heater_params_fitted.json +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_api_endpoints_201.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_api_helpers.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_app_queries.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_auth.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_auth_session_lifecycle.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_auth_session_refresh.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_ble_mqtt_adapter.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_ble_protocol.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_bug_fixes.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_canonical_schedule.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_cli_basic.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_cli_calibrate.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_cli_commands.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_cli_simulate.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_cli_tristate_presentation.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_command_decorators.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_command_queue.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_device_capabilities.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_device_info_cache.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_device_rest_models.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_events.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_exceptions.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_model_converters.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_models.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_mqtt_clean_session_resume.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_mqtt_client_init.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_mqtt_control_fail.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_mqtt_events.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_mqtt_hypothesis.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_mqtt_last_will.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_mqtt_reconnection.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_mqtt_reconnection_storm.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_mqtt_reliability.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_multi_device.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_navilink_201_enums.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_openei.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_protocol_correctness.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_public_api.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_reservation_validation.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_reservations.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_sim_api.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_sim_book.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_sim_codec.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_sim_cold_supply.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_sim_controller.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_sim_documents.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_sim_draw_forecast.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_sim_draw_loss.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_sim_element.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_sim_element_fit.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_sim_forecast.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_sim_golden.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_sim_import_contract.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_sim_location_air.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_sim_no_clock_no_state.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_sim_observed.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_sim_profile.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_sim_readiness.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_sim_readiness_fit.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_sim_ready.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_sim_replay.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_sim_schedules.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_sim_tank_physics.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_status_history.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_temperature_converters.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_threading_model.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_token_storage.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_tou_api.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_tou_schedule.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_unit_switching.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_unit_system_process_wide.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_utility_modules.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tests/test_utils.py +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/tox.ini +0 -0
- {nwp500_python-9.11.0 → nwp500_python-9.12.0}/uv.lock +0 -0
|
@@ -2,6 +2,171 @@
|
|
|
2
2
|
Changelog
|
|
3
3
|
=========
|
|
4
4
|
|
|
5
|
+
Version 9.12.0 (2026-10-23)
|
|
6
|
+
===========================
|
|
7
|
+
|
|
8
|
+
Added
|
|
9
|
+
-----
|
|
10
|
+
|
|
11
|
+
- **The heater's own entities in the sources file** (#232).
|
|
12
|
+
``[home_assistant.heater]`` names them (``heard``, ``upper`` and
|
|
13
|
+
``lower`` required), so the CLI fits and anchors the projected air to
|
|
14
|
+
the heater's reading while its compressor runs where no air entity
|
|
15
|
+
does.
|
|
16
|
+
- **The idle cabinet air, said** (#232). ``Environment.ambient_cabinet``
|
|
17
|
+
marks air taken from the heater's own reading while the compressor
|
|
18
|
+
was idle, about 6 °F warm, and answers give the reason
|
|
19
|
+
``ambient_from_idle_cabinet``.
|
|
20
|
+
- **The heater's own air in the book** (#236). With no air reading,
|
|
21
|
+
``book_from_history``, ``book_from_changes`` and
|
|
22
|
+
``HomeAssistantBook`` take the heater's own air reading once its
|
|
23
|
+
compressor has run 5 minutes (``sim_adapters.settled_air``,
|
|
24
|
+
``HEATER_AIR_SETTLE``), held until the next run, rather than 65 °F
|
|
25
|
+
throughout. ``HomeAssistantBook`` notes it.
|
|
26
|
+
- **The standby loss from the heater's own air** (#236). With no air
|
|
27
|
+
given, calibration fits the standby loss on the heater's own reading
|
|
28
|
+
once its compressor has run 5 minutes, interpolated from one run to
|
|
29
|
+
the next, over the quiet windows with a run within
|
|
30
|
+
``HEATER_AIR_BRACKET`` (6 h) of both ends: the law's level only, on
|
|
31
|
+
the base's exponent. ``Calibrated.air`` is ``"heater"``, and a note
|
|
32
|
+
says how many windows qualified and their share of the quiet hours.
|
|
33
|
+
It was left unfitted. ``fit_ua_law``, ``fit_loss``, ``fit_standby``
|
|
34
|
+
and ``calibration.calibrate`` take a fixed exponent.
|
|
35
|
+
- **The book prices a circulation run by a circuit volume.**
|
|
36
|
+
``CirculationParams.book_circuit_gal``: with it, the heat book
|
|
37
|
+
charges a run its first gallons up to that volume, runs closer than
|
|
38
|
+
``run_gap_min`` one run, each at the tank's upper probe less the air;
|
|
39
|
+
without it, ``loss_btu_per_gal`` per gallon as before. ``fit_book``
|
|
40
|
+
fits the volume in place of the loss per gallon (``book_circuit_gal``
|
|
41
|
+
on its result), and ``fit_book_terms`` with ``circuit=True``. At one
|
|
42
|
+
installation, over 284 re-anchors, the book's error on pairs with a
|
|
43
|
+
run fell 14 % against a refitted loss per gallon (1741 against 2023
|
|
44
|
+
BTU), and on held-out months from 3184 to 2454 BTU, the volume
|
|
45
|
+
steady at 1.1 to 1.5 gal where the loss per gallon moved from 59 to
|
|
46
|
+
120. It is an effective volume, apart from ``displaced_gal``.
|
|
47
|
+
``SITE_CIRCULATION`` carries one installation's, and the book's
|
|
48
|
+
heat-pump rate and slope are refitted with it (4338 BTU/h, 66.0
|
|
49
|
+
BTU/h/°F; the UA stays 2.88).
|
|
50
|
+
|
|
51
|
+
- **The heater's heard spans, given** (#230). ``book_from_changes``
|
|
52
|
+
takes ``heard=``, the spans the heater was heard over, for a record
|
|
53
|
+
that keeps no instant for each response. A gap between spans breaks
|
|
54
|
+
the book as a silence between instants does, and the heater is heard
|
|
55
|
+
at ``at`` only inside a span.
|
|
56
|
+
- **The air fit carried across reads** (#221).
|
|
57
|
+
``sim_home_assistant.fit_location_air`` is the fit
|
|
58
|
+
``HomeAssistantEnvironment`` makes where it is given no model, with
|
|
59
|
+
its notes, as a call of its own. ``HomeAssistantEnvironment`` takes
|
|
60
|
+
it back as ``fitted_air``, projects through it, and notes its r and
|
|
61
|
+
lag on every ``read`` without fitting again. A caller that reads
|
|
62
|
+
every few minutes fits on its own cadence.
|
|
63
|
+
- **The heat book kept current from Home Assistant** (#227).
|
|
64
|
+
``sim_home_assistant.HomeAssistantBook`` holds the heater's and the
|
|
65
|
+
household flow's history, reading only what is new on each ``read``,
|
|
66
|
+
and books from it with no request (``book``, for an executor). The
|
|
67
|
+
flow is the household's draws as delivered through the mixing valve.
|
|
68
|
+
The circulation's speed, an inlet probe and the demand entity, each
|
|
69
|
+
optional, give the runs, the inlet and the conversion at the mains
|
|
70
|
+
(``BookAnswer.inlet_measured``). ``mains_plateau`` and ``known_runs``
|
|
71
|
+
are the inlet's plateau and the pump's runs from a ``History``, as
|
|
72
|
+
``HomeAssistantEnvironment`` reads them. ``book_from_changes`` and
|
|
73
|
+
``book_steps_from_changes`` take ``air_outages``: while the air is
|
|
74
|
+
unknown the book takes the default air, not the last reading.
|
|
75
|
+
- **The CPU work off the event loop** (#225). ``fit_location_air`` and
|
|
76
|
+
``HomeAssistantEnvironment`` take ``run=`` (``Run``), which runs a
|
|
77
|
+
piece of work and awaits its result, such as Home Assistant's
|
|
78
|
+
``hass.async_add_executor_job``. The fit, the samples of the heater's
|
|
79
|
+
air and the inlet's plateau go through it. Without it they run
|
|
80
|
+
inline (``inline``), as before.
|
|
81
|
+
- **A history kept current** (#223). ``sim_home_assistant.HeaterHistory``,
|
|
82
|
+
``sim_home_assistant.History`` and ``sim_adapters.ChangeRows`` take
|
|
83
|
+
``extended(newer)``, which merges a later read in by instant (the
|
|
84
|
+
later read's row winning at a shared instant) and joins the outages,
|
|
85
|
+
and ``since(start)``, which trims to ``start`` and keeps each value in
|
|
86
|
+
force there. A caller that books every few minutes reads only what is
|
|
87
|
+
new and books from what it holds. ``sim_adapters.merge_changes``,
|
|
88
|
+
``changes_since``, ``join_spans`` and ``spans_since`` do it for one
|
|
89
|
+
series.
|
|
90
|
+
|
|
91
|
+
Changed
|
|
92
|
+
-------
|
|
93
|
+
|
|
94
|
+
- ``SITE_CIRCULATION.meter_share`` is 0. The book's flow is the
|
|
95
|
+
household's draws alone, which no circulation run passes, so the
|
|
96
|
+
example terms take nothing off it. A caller whose meter carries the
|
|
97
|
+
loop's water still sets its own share.
|
|
98
|
+
|
|
99
|
+
- **Readiness with no evidence and no book** (#234). A tank whose probes
|
|
100
|
+
are within the book's ``uniform_band_f`` reads its full-tank floor
|
|
101
|
+
from its probes (``Deliverable.basis`` ``probes``), at any
|
|
102
|
+
temperature. Any other tank reads 0 with the reason
|
|
103
|
+
``no_evidence_for_state``, which says the gallons are unknown, not
|
|
104
|
+
none, in place of ``colder_than_all_evidence``.
|
|
105
|
+
- The 50 and 80 gal defaults scale the book's empty point
|
|
106
|
+
(``BookParams.empty_btu``) and the full tank's hidden cold water
|
|
107
|
+
(``ReadinessParams.full_hidden_gal``) by tank size, as they scale its
|
|
108
|
+
volume (#234). Both were the 65 gal tank's at every size.
|
|
109
|
+
- Circulation runs given with params that have no circulation terms are
|
|
110
|
+
priced at one installation's (``SITE_CIRCULATION``), and the answer
|
|
111
|
+
says so in ``defaults_used`` (#232). They raised in every walk, and a
|
|
112
|
+
book left them unpriced. ``sim_adapters.with_circulation_terms`` does
|
|
113
|
+
this for the books.
|
|
114
|
+
- A status in Standby is Power Off, as the NaviLink app counts it (#232).
|
|
115
|
+
It raised.
|
|
116
|
+
- ``book_from_changes`` books from the flow's first reading when its
|
|
117
|
+
history starts later than the window, rather than raising (#232).
|
|
118
|
+
- ``known_runs`` returns the pump's runs from its first reading and the
|
|
119
|
+
span before it as unknown, rather than nothing for a history shorter
|
|
120
|
+
than the span (#232). The inlet's plateau skips a draw in that span,
|
|
121
|
+
and ``HomeAssistantBook`` takes no anchor before it.
|
|
122
|
+
- ``read_heater_history`` leaves out a heating flag entity with no
|
|
123
|
+
history at all, with a note, rather than leaving the whole record
|
|
124
|
+
unobserved (#232).
|
|
125
|
+
|
|
126
|
+
- The simulate how-to's ``book_from_changes`` example passes the
|
|
127
|
+
circulation's runs and flow, and says to pass the household's draws,
|
|
128
|
+
separated from any circulation in the meter, with ``meter_share`` 0.
|
|
129
|
+
- With a model, given or fitted, and no fresh reading of the air beside
|
|
130
|
+
the heater, ``HomeAssistantEnvironment`` anchors the projection to
|
|
131
|
+
``heater``'s newest air reading from the last hour while its
|
|
132
|
+
compressor ran, on every ``read``. It anchored to one only on the
|
|
133
|
+
read that fitted the model (#221).
|
|
134
|
+
|
|
135
|
+
Fixed
|
|
136
|
+
-----
|
|
137
|
+
|
|
138
|
+
- ``simulate`` and ``time_to_ready`` convert a book's heat to gallons at
|
|
139
|
+
the scenario's inlet (``Environment.inlet_f``) where one is given, as
|
|
140
|
+
the walk's draws take it (#234). The gallons ignored a measured inlet,
|
|
141
|
+
and with a book a colder inlet gave warmer delivered water.
|
|
142
|
+
- ``fit_location_air`` fits to the heater's own air when the air entity
|
|
143
|
+
can't be read or gives no fit (#232).
|
|
144
|
+
- Calibration's recoveries outside a partial air record read the
|
|
145
|
+
heater's own air, rather than being dropped (#232).
|
|
146
|
+
- The simulate how-to's and ``sim_live``'s ``compose`` example falls
|
|
147
|
+
back to the 65 gal defaults when the feature info doesn't arrive
|
|
148
|
+
(#232). It passed None to ``simulate``.
|
|
149
|
+
- ``HomeAssistantBook`` says when the flow is unknown now (#232).
|
|
150
|
+
|
|
151
|
+
- **A heater read missing a heating flag is unobserved** (#227).
|
|
152
|
+
``read_heater_history`` reads a day at a time; a window with nothing
|
|
153
|
+
of the compressor, an element or the heat source (each opens with
|
|
154
|
+
the value in force) now leaves the heater unobserved over it, with a
|
|
155
|
+
note, rather than holding the flag from before.
|
|
156
|
+
- ``sim_adapters.book_from_changes`` read its flow as a sampled meter:
|
|
157
|
+
a reading held 60 seconds (``FLOW_HOLD``) and the inlet's an hour,
|
|
158
|
+
and flow rows more than an hour apart refused the book as a silent
|
|
159
|
+
meter. A history of changes writes a row only when a value changes,
|
|
160
|
+
so a steady draw counted for a minute and an hour at 0 gal/min was
|
|
161
|
+
refused. Its flows, inlet and air now hold until the next change. It
|
|
162
|
+
takes ``flow_outages``, a span with no anchor before it counting, as
|
|
163
|
+
for a heater unheard, and ``inlet_outages``. No flow reading in force
|
|
164
|
+
at the window's start is a ``ValueError`` (#219).
|
|
165
|
+
- ``sim_home_assistant.pump_runs`` ended a run 5 minutes after its last
|
|
166
|
+
speed reading. A pump at a steady speed writes no row, so a long run
|
|
167
|
+
read as 5 minutes. A speed now holds until it changes; ``stale``
|
|
168
|
+
(``PUMP_STALE``) keeps the old rule for a sampled speed (#219).
|
|
169
|
+
|
|
5
170
|
Version 9.11.0 (2026-10-22)
|
|
6
171
|
===========================
|
|
7
172
|
|
|
@@ -96,9 +96,23 @@ Idle, it reads the cabinet, about 6 °F warm. So give one of these:
|
|
|
96
96
|
the heater's own flag is used instead. ``--air``, ``--ambient`` and
|
|
97
97
|
``--humidity`` override the sources.
|
|
98
98
|
|
|
99
|
-
Without the air, the standby loss
|
|
100
|
-
|
|
101
|
-
|
|
99
|
+
Without the air, the command fits the standby loss on the heater's own
|
|
100
|
+
reading, taken once the compressor has run 5 minutes and drawn straight
|
|
101
|
+
from one run's reading to the next across the idle span between them.
|
|
102
|
+
Only quiet windows with a run within 6 hours of both ends are fitted:
|
|
103
|
+
they fall mostly overnight, and a reading held from the day's last run
|
|
104
|
+
would read the night warm. Those windows span too narrow a range of
|
|
105
|
+
tank-to-air difference to fit the loss law's shape, so only its level is
|
|
106
|
+
fitted, on the base's exponent. A note says how many windows qualified
|
|
107
|
+
and what share of the quiet hours they hold. Expect the loss to read
|
|
108
|
+
somewhat high: at one installation, against a sensor beside the heater,
|
|
109
|
+
it read about 12 % high, as the night air dips below the line between
|
|
110
|
+
an evening run and a morning one, and the running reading is itself a
|
|
111
|
+
little warm. A sensor beside the heater (``--air``) avoids both.
|
|
112
|
+
With no such reading at all, the standby loss keeps the base's value
|
|
113
|
+
and the report says so. When you give the air, the recoveries use it
|
|
114
|
+
too; without it, they use the heater's own reading while the compressor
|
|
115
|
+
runs.
|
|
102
116
|
|
|
103
117
|
``--humidity`` takes the same kind of file, in %RH, for the
|
|
104
118
|
evaporator; without it the command gives the fits 50 %.
|
|
@@ -205,7 +219,8 @@ read in order without holding every status:
|
|
|
205
219
|
print(term.name, term.value, "fitted" if term.fitted else term.why)
|
|
206
220
|
|
|
207
221
|
Give ``ambient`` instead of ``air`` for a constant, or neither: the
|
|
208
|
-
standby loss then
|
|
222
|
+
standby loss is then fitted on the heater's own reading between runs,
|
|
223
|
+
and ``done.air`` is ``"heater"``. Without ``humidity`` the fits
|
|
209
224
|
get 50 %. An input it cannot use raises ``CalibrationInputError``.
|
|
210
225
|
|
|
211
226
|
On a desktop computer, 60 days of statuses (about 540,000) took about
|
|
@@ -373,8 +388,11 @@ Fit the heat book
|
|
|
373
388
|
|
|
374
389
|
The heat book (``book_from_changes``) counts the heat the tank holds
|
|
375
390
|
from the compressor's heat rate and its slope with the air, the standby
|
|
376
|
-
UA, and, with a circulation pump,
|
|
377
|
-
|
|
391
|
+
UA, and, with a circulation pump, what each run takes: its first
|
|
392
|
+
gallons, up to the book's circuit volume
|
|
393
|
+
(``CirculationParams.book_circuit_gal``), each at the tank's upper
|
|
394
|
+
probe less the air, the loop having cooled to the air since the last
|
|
395
|
+
run. ``fit_book`` fits them
|
|
378
396
|
together from the heater's history, the household's draws, and the
|
|
379
397
|
pump's runs with the loop's flow. The draws and the loop's flow are
|
|
380
398
|
yours to give, apart from each other: the fit takes every gallon of the
|
|
@@ -402,8 +420,8 @@ draws as hot water drawn, and each run at the loop's flow.
|
|
|
402
420
|
|
|
403
421
|
``draws`` and ``loop_flow`` are (instant, gal/min) readings and ``runs``
|
|
404
422
|
the pump's (start, end) spans; ``record_inputs``' air is °F, as the fit
|
|
405
|
-
takes it. ``fit.
|
|
406
|
-
``CirculationParams`` (None without
|
|
423
|
+
takes it. ``fit.book_circuit_gal`` is the circuit volume for your
|
|
424
|
+
``CirculationParams`` (None without a loop).
|
|
407
425
|
|
|
408
426
|
The fit reads the pairs of quiet full tanks up to
|
|
409
427
|
``book.max_anchor_age_h`` apart, and fits each term for the least mean
|
|
@@ -415,18 +433,22 @@ pair:
|
|
|
415
433
|
the others (the slope, with the air never off one temperature) stays
|
|
416
434
|
at ``params.book``. The rest of the book (its draw line, the anchors'
|
|
417
435
|
age, the valve's line) is ``params``'.
|
|
418
|
-
- **The
|
|
419
|
-
|
|
420
|
-
``
|
|
421
|
-
|
|
436
|
+
- **The book's circuit volume:** on 10 pairs with a run between, with
|
|
437
|
+
the book's terms fitted at each volume tried. Runs closer than
|
|
438
|
+
``run_gap_min`` are one run. It is an effective volume, smaller than
|
|
439
|
+
the loop's own (``displaced_gal``): at one installation 1.4 gal
|
|
440
|
+
against 6.4. Fit and keep them apart. With fewer pairs, the loop is
|
|
441
|
+
priced as ``params.circulation`` prices it, and without one, the
|
|
442
|
+
pairs with a run are left out. A pair with a run that has no flow
|
|
443
|
+
reading is left out.
|
|
422
444
|
|
|
423
445
|
The fit needs 10 pairs. It holds out the latest fifth and replaces the
|
|
424
446
|
base only when it books them no worse. A term on its bound
|
|
425
447
|
(``sim.calibration.BOOK_TERM_BOUNDS``) is no fit: the book misses
|
|
426
448
|
something between those anchors that the term is standing in for. The
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
449
|
+
circuit volume on its bound (``sim.calibration.BOOK_CIRCUIT_GAL_BOUNDS``)
|
|
450
|
+
leaves the loop at the base's; any other term on its bound keeps the
|
|
451
|
+
base. ``fit.kept`` says which it
|
|
430
452
|
kept and ``fit.why`` why. A span the heater wasn't heard from, a probe
|
|
431
453
|
read nothing, or the draws were silent over an hour, breaks the book.
|
|
432
454
|
|
|
@@ -435,10 +457,10 @@ come slowly. With a pump running about once a day, one installation had
|
|
|
435
457
|
5 in 14 days and 19 in 42: fit on four weeks or more, and raise Home
|
|
436
458
|
Assistant's ``purge_keep_days`` to keep them.
|
|
437
459
|
|
|
438
|
-
Book with the same inputs. Runs are priced
|
|
460
|
+
Book with the same inputs. Runs are priced at the circuit volume, in a
|
|
439
461
|
``CirculationParams`` of your installation's with ``meter_share`` 0,
|
|
440
|
-
since the draws hold none of the loop's water; without
|
|
441
|
-
|
|
462
|
+
since the draws hold none of the loop's water; without one, they can't
|
|
463
|
+
be booked. ``sa.run_readings`` gives the book each run's flow as
|
|
442
464
|
the fit takes it: its own readings, the first from its start, and none
|
|
443
465
|
read outside the runs.
|
|
444
466
|
|
|
@@ -447,13 +469,13 @@ read outside the runs.
|
|
|
447
469
|
from nwp500.sim.params import CirculationParams
|
|
448
470
|
|
|
449
471
|
book = None
|
|
450
|
-
|
|
451
|
-
if
|
|
472
|
+
volume = fit.book_circuit_gal
|
|
473
|
+
if volume is not None or not runs:
|
|
452
474
|
loop = None
|
|
453
|
-
if
|
|
475
|
+
if volume is not None:
|
|
454
476
|
loop = CirculationParams(
|
|
455
|
-
displaced_gal=..., flow_gpm=..., loss_btu_per_gal
|
|
456
|
-
meter_share=0.0,
|
|
477
|
+
displaced_gal=..., flow_gpm=..., loss_btu_per_gal=...,
|
|
478
|
+
book_circuit_gal=volume, meter_share=0.0,
|
|
457
479
|
)
|
|
458
480
|
book = sa.book_from_changes(
|
|
459
481
|
history.rows, history.observed, replace(params, circulation=loop),
|
|
@@ -142,6 +142,13 @@ pass it with ``--sources`` (or set ``NWP500_SOURCES``):
|
|
|
142
142
|
circulation_speed = "sensor.circulation_pump_speed"
|
|
143
143
|
weather = "weather.forecast_home"
|
|
144
144
|
|
|
145
|
+
[home_assistant.heater] # optional: the heater's own entities
|
|
146
|
+
heard = "sensor.water_heater_last_status"
|
|
147
|
+
upper = "sensor.water_heater_upper_tank_temperature"
|
|
148
|
+
lower = "sensor.water_heater_lower_tank_temperature"
|
|
149
|
+
compressor = "binary_sensor.water_heater_compressor"
|
|
150
|
+
ambient = "sensor.water_heater_ambient_temperature"
|
|
151
|
+
|
|
145
152
|
[location_air] # optional: air = damping x outdoor(t - lag) + offset
|
|
146
153
|
damping = 0.638
|
|
147
154
|
lag_hours = 2.0
|
|
@@ -162,7 +169,11 @@ give:
|
|
|
162
169
|
heater that is at most an hour old; without a fresh one, the
|
|
163
170
|
projection is unshifted. Without a ``[location_air]`` model, one is
|
|
164
171
|
fitted on the last 3 days of the ``air`` entity's readings against
|
|
165
|
-
the ``weather`` entity's outdoor temperature.
|
|
172
|
+
the ``weather`` entity's outdoor temperature. Without an ``air``
|
|
173
|
+
entity, or one that gives no fit, the ``[home_assistant.heater]``
|
|
174
|
+
table's ``ambient`` reading while its ``compressor`` runs is fitted
|
|
175
|
+
instead, and the projection is shifted to pass through its newest
|
|
176
|
+
such reading. It is used when the
|
|
166
177
|
readings span a day and follow the outdoor air at r 0.5 or more; a
|
|
167
178
|
note gives the fit. Otherwise, and without a ``weather`` entity, the
|
|
168
179
|
reading beside the heater is held over the span;
|
|
@@ -171,7 +182,8 @@ give:
|
|
|
171
182
|
circulation off) in the last 72 hours, from the inlet probe and the
|
|
172
183
|
``demand`` entity: a binary sensor that is on while the household
|
|
173
184
|
draws hot water, with the circulation's water not among its draws. A
|
|
174
|
-
circulation pump with no speed reading leaves no estimate
|
|
185
|
+
circulation pump with no speed reading leaves no estimate, and a draw
|
|
186
|
+
before its first reading is skipped.
|
|
175
187
|
|
|
176
188
|
A reading's age is from when Home Assistant last heard the sensor
|
|
177
189
|
report (``last_reported``). Over the REST API, Home Assistant serves
|
|
@@ -191,8 +203,34 @@ same parts. Given ``heater=``, a ``HeaterEntities`` with ``ambient`` and
|
|
|
191
203
|
heater's own air reading every 10 minutes while the compressor runs,
|
|
192
204
|
where the reading was taken 2 minutes or more into the run (earlier, or
|
|
193
205
|
idle, it reads the cabinet), and not while the sensor was unavailable.
|
|
194
|
-
|
|
195
|
-
``
|
|
206
|
+
With a model, given or fitted, and no fresh reading beside the heater,
|
|
207
|
+
each ``read`` anchors the projection to the heater's newest such
|
|
208
|
+
reading from the last hour. ``fitted_air`` holds the fit once ``read``
|
|
209
|
+
has run.
|
|
210
|
+
|
|
211
|
+
A caller that builds a new environment every few minutes needn't refit
|
|
212
|
+
each time. Fit on your own cadence, and pass the fit on:
|
|
213
|
+
|
|
214
|
+
.. code-block:: python
|
|
215
|
+
|
|
216
|
+
fitted, notes = await ha.fit_location_air(
|
|
217
|
+
client, entities, now=now, heater=heater_entities
|
|
218
|
+
) # hourly, say; None (and notes why) without enough history
|
|
219
|
+
|
|
220
|
+
environment = ha.HomeAssistantEnvironment(
|
|
221
|
+
client, entities, start=now, until=now + horizon, now=now,
|
|
222
|
+
heater=heater_entities, fitted_air=fitted,
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
Given ``fitted_air``, the environment projects through it, notes its r
|
|
226
|
+
and lag on every ``read``, and doesn't fit again.
|
|
227
|
+
|
|
228
|
+
The fit, the samples of the heater's air and the inlet's plateau are
|
|
229
|
+
CPU work: the fit alone takes about 100 ms on 3 days of 30-second
|
|
230
|
+
readings. To keep it off an event loop, pass ``run=``, a callable that
|
|
231
|
+
runs a function and awaits its result, to ``fit_location_air`` and
|
|
232
|
+
``HomeAssistantEnvironment``. In Home Assistant that is
|
|
233
|
+
``hass.async_add_executor_job``. Without it, the work runs inline.
|
|
196
234
|
|
|
197
235
|
|
|
198
236
|
Fetch what the heater reports
|
|
@@ -226,7 +264,10 @@ A scenario starts from four things the library already reads:
|
|
|
226
264
|
heater=sim_live.LiveHeaterSource(mqtt, device),
|
|
227
265
|
schedules=sim_live.LiveScheduleSource(mqtt, api, device),
|
|
228
266
|
)
|
|
229
|
-
|
|
267
|
+
# Without the feature info the tank size is unknown: the 65 gal
|
|
268
|
+
# defaults stand in.
|
|
269
|
+
params = composed.params or sim.default_params()
|
|
270
|
+
return sim.simulate(composed.scenario, params)
|
|
230
271
|
|
|
231
272
|
``environment=`` and ``loads=`` take any source with an ``async
|
|
232
273
|
read()``; ``sim_live.Given(value)`` is one for a value in hand. Without
|
|
@@ -234,7 +275,7 @@ read()``; ``sim_live.Given(value)`` is one for a value in hand. Without
|
|
|
234
275
|
gets no reply raises ``sim_live.LiveReadError``; so does the schedule
|
|
235
276
|
source when TOU is on and its periods cannot be read. A missing
|
|
236
277
|
feature info leaves ``composed.params`` None (the tank size is
|
|
237
|
-
unknown). Change a part with ``composed.scenario.model_copy(update=...)``.
|
|
278
|
+
unknown), so the example falls back to ``sim.default_params()``. Change a part with ``composed.scenario.model_copy(update=...)``.
|
|
238
279
|
|
|
239
280
|
With the status, the feature info, the reservations and the TOU
|
|
240
281
|
periods already read some other way, build the same parts from them:
|
|
@@ -887,14 +928,106 @@ with the instants the heater was heard from:
|
|
|
887
928
|
params,
|
|
888
929
|
at=now,
|
|
889
930
|
flow=draws_gpm, # gal/min, whatever the preferred units
|
|
931
|
+
flow_outages=flow_down, # when the flow entity was unavailable
|
|
932
|
+
circulation=runs, # [(start, end), ...], if you circulate
|
|
933
|
+
circulation_flow=pump_flow, # the loop's gal/min while it runs
|
|
934
|
+
)
|
|
935
|
+
|
|
936
|
+
``flow`` is the household's draws. If your meter also carries the
|
|
937
|
+
circulation's water, separate the household's draws from it yourself
|
|
938
|
+
and pass those, with ``CirculationParams.meter_share`` 0, so the book
|
|
939
|
+
takes nothing off them again. Clean up the loop's flow before passing it
|
|
940
|
+
too (drop readings below the pump's floor, or one taken mid-ramp): while
|
|
941
|
+
a run is on, each step takes the last reading in force (a run before
|
|
942
|
+
any reading takes ``CirculationParams.flow_gpm``).
|
|
943
|
+
|
|
944
|
+
Like the history's rows, its ``flow``, ``circulation_flow``, ``inlet``
|
|
945
|
+
and ``air`` are changes: each value holds until the next, so a steady
|
|
946
|
+
draw is one row and an hour at 0 gal/min none. They are gal/min and °F,
|
|
947
|
+
whatever the preferred units. A span the heater wasn't heard from (an
|
|
948
|
+
outage, or a silence much longer than its usual reporting), or the flow
|
|
949
|
+
was unknown (``flow_outages``, from the history's ``outages``), breaks
|
|
950
|
+
the book: it is walked only from an anchor after the last one, and is
|
|
951
|
+
None while the heater isn't heard now. A record that keeps no instant
|
|
952
|
+
for each of the heater's responses passes ``heard=``, the spans the
|
|
953
|
+
heater was heard over, and an empty ``observed``: a gap between two
|
|
954
|
+
spans breaks the book the same way, and the heater is heard now only
|
|
955
|
+
inside a span. With no flow reading in force
|
|
956
|
+
at the start of the window it books over, it raises ``ValueError``.
|
|
957
|
+
An inlet reading holds except through ``inlet_outages``.
|
|
958
|
+
|
|
959
|
+
To keep a book current as you walk every few minutes, keep the history
|
|
960
|
+
rather than the book. Read only what is new since your last read,
|
|
961
|
+
extend what you hold with it, trim it to the book's window, and book
|
|
962
|
+
again from memory:
|
|
963
|
+
|
|
964
|
+
.. code-block:: python
|
|
965
|
+
|
|
966
|
+
newer = await ha.read_heater_history(client, entities, last_read, now)
|
|
967
|
+
history = history.extended(newer).since(now - timedelta(hours=72))
|
|
968
|
+
flows = flows.extended(
|
|
969
|
+
await client.read_history([FLOW], last_read, now)
|
|
970
|
+
).since(now - timedelta(hours=72))
|
|
971
|
+
last_read = now
|
|
972
|
+
|
|
973
|
+
``HeaterHistory`` and ``History`` both take ``extended`` and ``since``.
|
|
974
|
+
A read opens with the value in force at its start; as each value holds
|
|
975
|
+
until the next change, that row changes nothing. Where both reads have
|
|
976
|
+
a row at one instant, the later read's is kept. ``since`` keeps each
|
|
977
|
+
value in force at the cut. 72 hours is ``BookParams.history_h``, the
|
|
978
|
+
oldest anchor a book reads.
|
|
979
|
+
|
|
980
|
+
``sim_home_assistant.HomeAssistantBook`` does all of that from Home
|
|
981
|
+
Assistant's entities:
|
|
982
|
+
|
|
983
|
+
.. code-block:: python
|
|
984
|
+
|
|
985
|
+
keeper = ha.HomeAssistantBook(
|
|
986
|
+
client,
|
|
987
|
+
heater_entities, # ha.HeaterEntities
|
|
988
|
+
"sensor.hot_water_draw_flow", # the household's draws
|
|
989
|
+
ha.Entities(
|
|
990
|
+
circulation_speed="sensor.circulation_pump_speed",
|
|
991
|
+
inlet="sensor.cold_supply_temperature",
|
|
992
|
+
demand="binary_sensor.hot_water_demand",
|
|
993
|
+
),
|
|
890
994
|
)
|
|
891
995
|
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
996
|
+
await keeper.read(now) # all 72 h the first time, then what's new
|
|
997
|
+
answer = await hass.async_add_executor_job(keeper.book, now, params)
|
|
998
|
+
readiness = sim.time_to_ready(
|
|
999
|
+
scenario, params, book=answer.booked,
|
|
1000
|
+
inlet_measured=answer.inlet_measured,
|
|
1001
|
+
)
|
|
1002
|
+
|
|
1003
|
+
``read`` reads the history since the last read and holds it; a read
|
|
1004
|
+
that fails is noted, and the next one reads from where it would have
|
|
1005
|
+
started. Until then, ``book`` takes the flow as unknown since the last
|
|
1006
|
+
read that worked, rather than holding its last value, and after a
|
|
1007
|
+
failed read of the heater it gives no book. An optional
|
|
1008
|
+
entity whose unit can't be read is left out of the book. With no air
|
|
1009
|
+
entity, or one that gives no reading, the book takes the heater's own
|
|
1010
|
+
air reading (``HeaterEntities.ambient``) once its compressor has run 5
|
|
1011
|
+
minutes, and holds it until the next run, and a note says so. Idle, that
|
|
1012
|
+
sensor reads the cabinet, about 6 °F warm, so an idle reading is never
|
|
1013
|
+
taken. Before the first run, and while an air entity that gives readings
|
|
1014
|
+
is unavailable or reads in a unit the book doesn't read, the book takes
|
|
1015
|
+
the default air (65 °F). ``book`` books from what it holds, with no
|
|
1016
|
+
request, so it can run in an executor. ``answer.notes`` say what the book rests on, or why
|
|
1017
|
+
there is none.
|
|
1018
|
+
|
|
1019
|
+
The flow is the household's hot water draws, without the circulation's
|
|
1020
|
+
water, in gal/min or any unit ``to_gpm`` reads. It is the water
|
|
1021
|
+
delivered at the taps, through the mixing valve: the book prices a
|
|
1022
|
+
delivered gallon at the valve's delivery line
|
|
1023
|
+
(``DrawParams.delivery_intercept_f`` and ``delivery_slope``), not as
|
|
1024
|
+
water withdrawn from the tank. As none of the flow is circulation,
|
|
1025
|
+
``book`` takes ``CirculationParams.meter_share`` as 0. A flow entity that reports a draw late
|
|
1026
|
+
reaches the book late: the book holds what the history holds at
|
|
1027
|
+
``read``. Each of the other entities is optional:
|
|
1028
|
+
``circulation_speed`` gives the circulation's runs, ``inlet`` the inlet
|
|
1029
|
+
the book converts at, ``inlet`` with ``demand`` the mains, and ``air``
|
|
1030
|
+
the air beside the heater.
|
|
898
1031
|
|
|
899
1032
|
Without a meter, start one only when you know the tank is full and
|
|
900
1033
|
quiet (``sa.full_tank_book``).
|
|
@@ -1035,15 +1168,18 @@ to a day apart. Each pair's booked heat is checked against the probes':
|
|
|
1035
1168
|
circulation=runs, circulation_flow=pump_flow, params=params,
|
|
1036
1169
|
)
|
|
1037
1170
|
book = calibration.RecordedBook(steps, p, params.circulation, start=since)
|
|
1038
|
-
fit = calibration.fit_book_terms(book, book.reanchors(), p)
|
|
1171
|
+
fit = calibration.fit_book_terms(book, book.reanchors(), p, circuit=True)
|
|
1039
1172
|
p = replace(p, hp_btu_per_h=fit.hp_btu_per_h,
|
|
1040
1173
|
hp_btu_per_h_per_f=fit.hp_btu_per_h_per_f,
|
|
1041
1174
|
ua_btu_per_h_f=fit.ua_btu_per_h_f)
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1175
|
+
v = fit.book_circuit_gal
|
|
1176
|
+
bands = calibration.book_error_bands(book, p, 0.0, v)
|
|
1177
|
+
empty, crossings = calibration.empty_point(book, p, 0.0, circuit_gal=v)
|
|
1178
|
+
zone = calibration.zone_empty_point(book, p, 0.0, circuit_gal=v)
|
|
1179
|
+
|
|
1180
|
+
With ``circuit=True`` the runs are priced at a circuit volume
|
|
1181
|
+
(``CirculationParams.book_circuit_gal``) fitted in place of a loss per
|
|
1182
|
+
gallon. ``book_error_btu(book, p, 0.0, v)`` gives the book's
|
|
1047
1183
|
error bands: the 90th percentile of its error pooled over re-anchors an
|
|
1048
1184
|
hour to ``max_anchor_age_h`` apart, and the 18 to 24 h band's past it,
|
|
1049
1185
|
each rounded up to 100 BTU. ``cold_lower_cap(draws,
|
|
@@ -154,7 +154,7 @@ Hot water and the heat book
|
|
|
154
154
|
clean_draws, CleanDraw, crossing,
|
|
155
155
|
demonstrated, frontier, capacity, held_out, fit_evidence,
|
|
156
156
|
EvidenceFit, cold_lower_cap, RecordedBook, fit_book_terms, BookFit,
|
|
157
|
-
fit_terms_l1, BOOK_TERMS, BOOK_TERM_BOUNDS,
|
|
157
|
+
fit_terms_l1, BOOK_TERMS, BOOK_TERM_BOUNDS, BOOK_CIRCUIT_GAL_BOUNDS,
|
|
158
158
|
book_error_bands, book_error_btu, empty_point,
|
|
159
159
|
zone_empty_point, withheld_btu, book_walk, long_draws,
|
|
160
160
|
front_frame, fit_front, FrontFit, settle_tails, fit_front_settle,
|
|
@@ -305,14 +305,17 @@ Recorded history
|
|
|
305
305
|
fit_book, BookTermsFit, MIN_BOOK_PAIRS, MIN_RUN_PAIRS,
|
|
306
306
|
score, Judged, HeldOut, GROUPS, air_unread_note,
|
|
307
307
|
CalibrationInputError, DEFAULT_HUMIDITY_PCT,
|
|
308
|
-
DEFAULT_BASE_NOTE, NO_AIR_NOTE
|
|
308
|
+
DEFAULT_BASE_NOTE, NO_AIR_NOTE, HEATER_AIR_BRACKET
|
|
309
309
|
|
|
310
310
|
|
|
311
311
|
Home Assistant sources
|
|
312
312
|
======================
|
|
313
313
|
|
|
314
314
|
.. automodule:: nwp500.sim_home_assistant
|
|
315
|
-
:members: HomeAssistantEnvironment, HomeAssistantClient,
|
|
315
|
+
:members: HomeAssistantEnvironment, HomeAssistantClient, fit_location_air,
|
|
316
|
+
Run, inline, HomeAssistantBook, BookAnswer, BOOK_WINDOW,
|
|
317
|
+
mains_plateau, known_runs,
|
|
318
|
+
record_inputs,
|
|
316
319
|
RecordInputs, INLET_LOOKBACK, PROBE_STEP, held, LOCATION_AIR_WINDOW,
|
|
317
320
|
LOCATION_AIR_MIN_SPAN, LOCATION_AIR_MIN_R, RUN_SETTLE, Sources,
|
|
318
321
|
Entities, sources_from, State, parse_state,
|