nwp500-python 9.12.0__tar.gz → 9.12.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (343) hide show
  1. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/CHANGELOG.rst +29 -0
  2. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/CONTRIBUTING.rst +1 -1
  3. {nwp500_python-9.12.0/src/nwp500_python.egg-info → nwp500_python-9.12.1}/PKG-INFO +6 -17
  4. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/docs/conf.py +1 -2
  5. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/docs/how-to/simulate-heater.rst +20 -1
  6. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/docs/requirements.txt +1 -1
  7. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/pyproject.toml +72 -2
  8. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/scripts/README.md +1 -1
  9. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/scripts/validate_version.py +21 -13
  10. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/setup.cfg +0 -65
  11. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/cli/__main__.py +16 -3
  12. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/cli/simulate.py +121 -7
  13. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/sim_home_assistant.py +5 -0
  14. {nwp500_python-9.12.0 → nwp500_python-9.12.1/src/nwp500_python.egg-info}/PKG-INFO +6 -17
  15. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500_python.egg-info/SOURCES.txt +0 -2
  16. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500_python.egg-info/requires.txt +2 -11
  17. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500_python.egg-info/scm_file_list.json +1 -3
  18. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500_python.egg-info/scm_version.json +2 -2
  19. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/tests/test_cli_simulate.py +136 -1
  20. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/tests/test_sim_home_assistant.py +4 -0
  21. nwp500_python-9.12.0/setup.py +0 -22
  22. nwp500_python-9.12.0/uv.lock +0 -3
  23. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/.coveragerc +0 -0
  24. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/.github/RESOLVING_PR_COMMENTS.md +0 -0
  25. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/.github/copilot-instructions.md +0 -0
  26. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/.github/workflows/ci.yml +0 -0
  27. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/.github/workflows/release.yml +0 -0
  28. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/.gitignore +0 -0
  29. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/.pre-commit-config.yaml +0 -0
  30. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/.readthedocs.yml +0 -0
  31. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/AUTHORS.rst +0 -0
  32. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/LICENSE.txt +0 -0
  33. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/Makefile +0 -0
  34. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/README.rst +0 -0
  35. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/RELEASE.md +0 -0
  36. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/docs/Makefile +0 -0
  37. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/docs/_static/.gitignore +0 -0
  38. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/docs/explanation/architecture.rst +0 -0
  39. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/docs/explanation/draws.rst +0 -0
  40. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/docs/explanation/heating-elements.rst +0 -0
  41. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/docs/explanation/index.rst +0 -0
  42. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/docs/explanation/recovery-speed.rst +0 -0
  43. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/docs/explanation/simulator.rst +0 -0
  44. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/docs/explanation/standby-loss.rst +0 -0
  45. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/docs/explanation/tank-energy.rst +0 -0
  46. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/docs/explanation/tou-recovery-cap.rst +0 -0
  47. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/docs/explanation/unknown-values.rst +0 -0
  48. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/docs/explanation/what-starts-a-recovery.rst +0 -0
  49. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/docs/how-to/authenticate.rst +0 -0
  50. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/docs/how-to/auto-recovery.rst +0 -0
  51. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/docs/how-to/bluetooth.rst +0 -0
  52. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/docs/how-to/calibrate-simulator.rst +0 -0
  53. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/docs/how-to/diagnose-mqtt.rst +0 -0
  54. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/docs/how-to/home-assistant.rst +0 -0
  55. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/docs/how-to/index.rst +0 -0
  56. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/docs/how-to/maintenance.rst +0 -0
  57. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/docs/how-to/manage-units.rst +0 -0
  58. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/docs/how-to/monitor-status.rst +0 -0
  59. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/docs/how-to/optimize-tou.rst +0 -0
  60. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/docs/how-to/queue-commands.rst +0 -0
  61. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/docs/how-to/schedule-operation.rst +0 -0
  62. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/docs/how-to/track-energy.rst +0 -0
  63. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/docs/index.rst +0 -0
  64. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/docs/openapi.yaml +0 -0
  65. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/docs/project/authors.rst +0 -0
  66. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/docs/project/changelog.rst +0 -0
  67. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/docs/project/contributing.rst +0 -0
  68. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/docs/project/history.rst +0 -0
  69. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/docs/project/license.rst +0 -0
  70. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/docs/reference/configuration.rst +0 -0
  71. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/docs/reference/enumerations.rst +0 -0
  72. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/docs/reference/index.rst +0 -0
  73. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/docs/reference/installation.rst +0 -0
  74. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/docs/reference/protocol/data_conversions.rst +0 -0
  75. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/docs/reference/protocol/device_features.rst +0 -0
  76. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/docs/reference/protocol/device_status.rst +0 -0
  77. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/docs/reference/protocol/error_codes.rst +0 -0
  78. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/docs/reference/protocol/mqtt_protocol.rst +0 -0
  79. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/docs/reference/protocol/quick_reference.rst +0 -0
  80. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/docs/reference/protocol/rest_api.rst +0 -0
  81. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/docs/reference/python_api/api_client.rst +0 -0
  82. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/docs/reference/python_api/auth_client.rst +0 -0
  83. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/docs/reference/python_api/cli.rst +0 -0
  84. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/docs/reference/python_api/events.rst +0 -0
  85. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/docs/reference/python_api/exceptions.rst +0 -0
  86. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/docs/reference/python_api/models.rst +0 -0
  87. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/docs/reference/python_api/mqtt_client.rst +0 -0
  88. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/docs/reference/python_api/sim.rst +0 -0
  89. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/docs/reference/simulator-inputs.rst +0 -0
  90. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/docs/tutorials/getting-started.rst +0 -0
  91. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/examples/.ruff.toml +0 -0
  92. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/examples/README.md +0 -0
  93. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/examples/advanced/air_filter_reset.py +0 -0
  94. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/examples/advanced/anti_legionella.py +0 -0
  95. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/examples/advanced/auto_recovery.py +0 -0
  96. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/examples/advanced/combined_callbacks.py +0 -0
  97. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/examples/advanced/demand_response.py +0 -0
  98. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/examples/advanced/device_capabilities.py +0 -0
  99. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/examples/advanced/device_status_debug.py +0 -0
  100. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/examples/advanced/energy_analytics.py +0 -0
  101. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/examples/advanced/error_code_demo.py +0 -0
  102. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/examples/advanced/firmware_payload_capture.py +0 -0
  103. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/examples/advanced/mqtt_diagnostics.py +0 -0
  104. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/examples/advanced/power_control.py +0 -0
  105. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/examples/advanced/recirculation_control.py +0 -0
  106. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/examples/advanced/reconnection_demo.py +0 -0
  107. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/examples/advanced/reservation_schedule.py +0 -0
  108. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/examples/advanced/simple_auto_recovery.py +0 -0
  109. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/examples/advanced/token_restoration.py +0 -0
  110. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/examples/advanced/tou_openei.py +0 -0
  111. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/examples/advanced/tou_schedule.py +0 -0
  112. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/examples/advanced/water_reservation.py +0 -0
  113. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/examples/beginner/01_authentication.py +0 -0
  114. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/examples/beginner/02_list_devices.py +0 -0
  115. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/examples/beginner/03_get_status.py +0 -0
  116. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/examples/beginner/04_set_temperature.py +0 -0
  117. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/examples/intermediate/advanced_auth_patterns.py +0 -0
  118. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/examples/intermediate/command_queue.py +0 -0
  119. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/examples/intermediate/device_status_callback.py +0 -0
  120. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/examples/intermediate/error_handling.py +0 -0
  121. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/examples/intermediate/event_driven_control.py +0 -0
  122. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/examples/intermediate/improved_auth.py +0 -0
  123. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/examples/intermediate/legacy_auth_constructor.py +0 -0
  124. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/examples/intermediate/mqtt_realtime_monitoring.py +0 -0
  125. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/examples/intermediate/periodic_requests.py +0 -0
  126. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/examples/intermediate/set_mode.py +0 -0
  127. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/examples/intermediate/vacation_mode.py +0 -0
  128. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/examples/mask.py +0 -0
  129. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/examples/simulator/lower-setpoint.json +0 -0
  130. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/examples/simulator/plan.json +0 -0
  131. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/examples/simulator/scenario.json +0 -0
  132. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/examples/testing/periodic_device_info.py +0 -0
  133. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/examples/testing/simple_periodic_info.py +0 -0
  134. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/examples/testing/test_api_client.py +0 -0
  135. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/examples/testing/test_mqtt_connection.py +0 -0
  136. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/examples/testing/test_mqtt_messaging.py +0 -0
  137. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/examples/testing/test_periodic_minimal.py +0 -0
  138. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/scripts/bump_version.py +0 -0
  139. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/scripts/diagnose_mqtt_connection.py +0 -0
  140. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/scripts/extract_changelog.py +0 -0
  141. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/scripts/format.py +0 -0
  142. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/scripts/lint.py +0 -0
  143. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/scripts/setup-dev.py +0 -0
  144. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/__init__.py +0 -0
  145. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/_base.py +0 -0
  146. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/api_client.py +0 -0
  147. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/auth.py +0 -0
  148. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/ble/__init__.py +0 -0
  149. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/ble/__main__.py +0 -0
  150. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/ble/client.py +0 -0
  151. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/ble/mqtt_adapter.py +0 -0
  152. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/ble/protocol.py +0 -0
  153. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/cli/__init__.py +0 -0
  154. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/cli/ble_backend.py +0 -0
  155. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/cli/calibration.py +0 -0
  156. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/cli/handlers.py +0 -0
  157. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/cli/monitoring.py +0 -0
  158. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/cli/output_formatters.py +0 -0
  159. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/cli/presentation.py +0 -0
  160. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/cli/recording.py +0 -0
  161. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/cli/rich_output.py +0 -0
  162. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/cli/token_storage.py +0 -0
  163. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/command_decorators.py +0 -0
  164. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/config.py +0 -0
  165. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/converters.py +0 -0
  166. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/device_capabilities.py +0 -0
  167. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/device_info_cache.py +0 -0
  168. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/encoding.py +0 -0
  169. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/enums.py +0 -0
  170. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/events.py +0 -0
  171. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/exceptions.py +0 -0
  172. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/factory.py +0 -0
  173. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/field_factory.py +0 -0
  174. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/models/__init__.py +0 -0
  175. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/models/_converters.py +0 -0
  176. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/models/account.py +0 -0
  177. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/models/device.py +0 -0
  178. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/models/diagnostics.py +0 -0
  179. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/models/energy.py +0 -0
  180. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/models/feature.py +0 -0
  181. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/models/firmware.py +0 -0
  182. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/models/maintenance.py +0 -0
  183. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/models/mqtt_models.py +0 -0
  184. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/models/schedule.py +0 -0
  185. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/models/status.py +0 -0
  186. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/models/tou.py +0 -0
  187. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/mqtt/__init__.py +0 -0
  188. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/mqtt/_control_commands.py +0 -0
  189. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/mqtt/_device_subscriptions.py +0 -0
  190. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/mqtt/client.py +0 -0
  191. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/mqtt/command_queue.py +0 -0
  192. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/mqtt/connection.py +0 -0
  193. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/mqtt/control.py +0 -0
  194. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/mqtt/diagnostics.py +0 -0
  195. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/mqtt/periodic.py +0 -0
  196. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/mqtt/reconnection.py +0 -0
  197. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/mqtt/state_tracker.py +0 -0
  198. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/mqtt/subscriptions.py +0 -0
  199. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/mqtt/types.py +0 -0
  200. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/mqtt/utils.py +0 -0
  201. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/mqtt_events.py +0 -0
  202. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/openei.py +0 -0
  203. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/py.typed +0 -0
  204. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/reservations.py +0 -0
  205. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/sim/__init__.py +0 -0
  206. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/sim/api.py +0 -0
  207. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/sim/attribution.py +0 -0
  208. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/sim/book.py +0 -0
  209. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/sim/calibration.py +0 -0
  210. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/sim/circulation.py +0 -0
  211. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/sim/codec.py +0 -0
  212. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/sim/cold_supply.py +0 -0
  213. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/sim/controller.py +0 -0
  214. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/sim/decay.py +0 -0
  215. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/sim/defaults.py +0 -0
  216. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/sim/delivery.py +0 -0
  217. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/sim/draw_forecast.py +0 -0
  218. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/sim/engine.py +0 -0
  219. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/sim/extensions.py +0 -0
  220. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/sim/heat_pump_recovery.py +0 -0
  221. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/sim/heating_trajectory.py +0 -0
  222. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/sim/inputs.py +0 -0
  223. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/sim/location_air.py +0 -0
  224. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/sim/observed.py +0 -0
  225. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/sim/params.py +0 -0
  226. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/sim/physics/__init__.py +0 -0
  227. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/sim/physics/compressor.py +0 -0
  228. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/sim/physics/draw.py +0 -0
  229. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/sim/physics/element.py +0 -0
  230. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/sim/physics/refrigerant.py +0 -0
  231. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/sim/physics/tank.py +0 -0
  232. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/sim/profile.py +0 -0
  233. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/sim/ready.py +0 -0
  234. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/sim/record.py +0 -0
  235. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/sim/result.py +0 -0
  236. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/sim/schedules.py +0 -0
  237. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/sim/tank_physics.py +0 -0
  238. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/sim/tou_triggers.py +0 -0
  239. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/sim/units.py +0 -0
  240. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/sim_adapters.py +0 -0
  241. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/sim_calibrate.py +0 -0
  242. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/sim_live.py +0 -0
  243. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/status_history.py +0 -0
  244. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/temperature.py +0 -0
  245. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/topic_builder.py +0 -0
  246. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/tou_schedule.py +0 -0
  247. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/unit_system.py +0 -0
  248. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500/utils.py +0 -0
  249. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500_python.egg-info/dependency_links.txt +0 -0
  250. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500_python.egg-info/entry_points.txt +0 -0
  251. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500_python.egg-info/not-zip-safe +0 -0
  252. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/src/nwp500_python.egg-info/top_level.txt +0 -0
  253. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/tests/_sim_site.py +0 -0
  254. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/tests/conftest.py +0 -0
  255. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/tests/fixtures/sim/engine_golden.json +0 -0
  256. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/tests/fixtures/sim/heater_params.json +0 -0
  257. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/tests/fixtures/sim/heater_params_fitted.json +0 -0
  258. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/tests/test_api_endpoints_201.py +0 -0
  259. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/tests/test_api_helpers.py +0 -0
  260. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/tests/test_app_queries.py +0 -0
  261. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/tests/test_auth.py +0 -0
  262. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/tests/test_auth_session_lifecycle.py +0 -0
  263. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/tests/test_auth_session_refresh.py +0 -0
  264. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/tests/test_ble_mqtt_adapter.py +0 -0
  265. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/tests/test_ble_protocol.py +0 -0
  266. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/tests/test_bug_fixes.py +0 -0
  267. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/tests/test_canonical_schedule.py +0 -0
  268. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/tests/test_cli_basic.py +0 -0
  269. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/tests/test_cli_calibrate.py +0 -0
  270. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/tests/test_cli_commands.py +0 -0
  271. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/tests/test_cli_tristate_presentation.py +0 -0
  272. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/tests/test_command_decorators.py +0 -0
  273. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/tests/test_command_queue.py +0 -0
  274. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/tests/test_device_capabilities.py +0 -0
  275. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/tests/test_device_info_cache.py +0 -0
  276. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/tests/test_device_rest_models.py +0 -0
  277. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/tests/test_events.py +0 -0
  278. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/tests/test_exceptions.py +0 -0
  279. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/tests/test_model_converters.py +0 -0
  280. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/tests/test_models.py +0 -0
  281. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/tests/test_mqtt_clean_session_resume.py +0 -0
  282. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/tests/test_mqtt_client_init.py +0 -0
  283. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/tests/test_mqtt_control_fail.py +0 -0
  284. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/tests/test_mqtt_events.py +0 -0
  285. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/tests/test_mqtt_hypothesis.py +0 -0
  286. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/tests/test_mqtt_last_will.py +0 -0
  287. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/tests/test_mqtt_reconnection.py +0 -0
  288. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/tests/test_mqtt_reconnection_storm.py +0 -0
  289. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/tests/test_mqtt_reliability.py +0 -0
  290. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/tests/test_multi_device.py +0 -0
  291. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/tests/test_navilink_201_enums.py +0 -0
  292. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/tests/test_openei.py +0 -0
  293. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/tests/test_protocol_correctness.py +0 -0
  294. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/tests/test_public_api.py +0 -0
  295. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/tests/test_reservation_validation.py +0 -0
  296. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/tests/test_reservations.py +0 -0
  297. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/tests/test_sim_adapters.py +0 -0
  298. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/tests/test_sim_api.py +0 -0
  299. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/tests/test_sim_book.py +0 -0
  300. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/tests/test_sim_book_history.py +0 -0
  301. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/tests/test_sim_bottom_loss.py +0 -0
  302. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/tests/test_sim_calibrate.py +0 -0
  303. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/tests/test_sim_calibration.py +0 -0
  304. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/tests/test_sim_change_history.py +0 -0
  305. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/tests/test_sim_circulation.py +0 -0
  306. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/tests/test_sim_codec.py +0 -0
  307. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/tests/test_sim_cold_supply.py +0 -0
  308. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/tests/test_sim_controller.py +0 -0
  309. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/tests/test_sim_defaults.py +0 -0
  310. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/tests/test_sim_documents.py +0 -0
  311. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/tests/test_sim_draw_forecast.py +0 -0
  312. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/tests/test_sim_draw_loss.py +0 -0
  313. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/tests/test_sim_element.py +0 -0
  314. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/tests/test_sim_element_fit.py +0 -0
  315. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/tests/test_sim_fit_book.py +0 -0
  316. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/tests/test_sim_forecast.py +0 -0
  317. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/tests/test_sim_golden.py +0 -0
  318. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/tests/test_sim_hot_water.py +0 -0
  319. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/tests/test_sim_import_contract.py +0 -0
  320. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/tests/test_sim_live.py +0 -0
  321. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/tests/test_sim_location_air.py +0 -0
  322. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/tests/test_sim_no_clock_no_state.py +0 -0
  323. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/tests/test_sim_observed.py +0 -0
  324. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/tests/test_sim_overlay.py +0 -0
  325. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/tests/test_sim_profile.py +0 -0
  326. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/tests/test_sim_readiness.py +0 -0
  327. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/tests/test_sim_readiness_fit.py +0 -0
  328. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/tests/test_sim_ready.py +0 -0
  329. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/tests/test_sim_ready_api.py +0 -0
  330. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/tests/test_sim_replay.py +0 -0
  331. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/tests/test_sim_schedules.py +0 -0
  332. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/tests/test_sim_tank_physics.py +0 -0
  333. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/tests/test_status_history.py +0 -0
  334. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/tests/test_temperature_converters.py +0 -0
  335. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/tests/test_threading_model.py +0 -0
  336. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/tests/test_token_storage.py +0 -0
  337. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/tests/test_tou_api.py +0 -0
  338. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/tests/test_tou_schedule.py +0 -0
  339. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/tests/test_unit_switching.py +0 -0
  340. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/tests/test_unit_system_process_wide.py +0 -0
  341. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/tests/test_utility_modules.py +0 -0
  342. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/tests/test_utils.py +0 -0
  343. {nwp500_python-9.12.0 → nwp500_python-9.12.1}/tox.ini +0 -0
@@ -2,6 +2,35 @@
2
2
  Changelog
3
3
  =========
4
4
 
5
+ Version 9.12.1 (2026-10-23)
6
+ ===========================
7
+
8
+ Fixed
9
+ -----
10
+
11
+ - **simulate run books the tank from Home Assistant.** Given the
12
+ sources' ``[home_assistant.heater]`` table and the new
13
+ ``draw_flow`` entity, the run walks from the tank's booked heat
14
+ (``HomeAssistantBook``), and "deliverable now" reads it: a tank with
15
+ its probes apart read 0 gal from the probes alone, the history
16
+ unused.
17
+
18
+ Added
19
+ -----
20
+
21
+ - **simulate run's bath and showers.** The table gives the bath and
22
+ the whole showers the tank is ready for (``sim.time_to_ready``), and
23
+ when the heat pump has the bath ready; ``--bath`` and ``--shower``
24
+ give the volumes.
25
+
26
+ Changed
27
+ -------
28
+
29
+ - **The package's metadata is in pyproject.toml.** Name, dependencies,
30
+ extras, classifiers and the ``nwp-cli`` entry point moved from
31
+ ``setup.cfg`` to the standard ``[project]`` table, and ``setup.py`` is
32
+ gone. The built package installs the same files and requirements.
33
+
5
34
  Version 9.12.0 (2026-10-23)
6
35
  ===========================
7
36
 
@@ -265,7 +265,7 @@ package:
265
265
  potentially in the root of your project.
266
266
 
267
267
  #. Sometimes |tox|_ misses out when new dependencies are added, especially to
268
- ``setup.cfg`` and ``docs/requirements.txt``. If you find any problems with
268
+ ``pyproject.toml`` and ``docs/requirements.txt``. If you find any problems with
269
269
  missing dependencies when running a command with |tox|_, try to recreate the
270
270
  ``tox`` environment using the ``-r`` flag. For example, instead of::
271
271
 
@@ -1,15 +1,13 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nwp500-python
3
- Version: 9.12.0
3
+ Version: 9.12.1
4
4
  Summary: A library for controlling Navien NWP500 Water Heaters via NaviLink
5
- Home-page: https://github.com/eman/nwp500-python
6
- Author: Emmanuel Levijarvi
7
- Author-email: emansl@gmail.com
8
- License: MIT
5
+ Author-email: Emmanuel Levijarvi <emansl@gmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/eman/nwp500-python
9
8
  Project-URL: Documentation, https://nwp500-python.readthedocs.io/en/latest/
10
9
  Project-URL: Source, https://github.com/eman/nwp500-python
11
10
  Project-URL: Tracker, https://github.com/eman/nwp500-python/issues
12
- Platform: any
13
11
  Classifier: Development Status :: 4 - Beta
14
12
  Classifier: Programming Language :: Python
15
13
  Classifier: Programming Language :: Python :: 3
@@ -31,9 +29,7 @@ Requires-Dist: numpy>=1.24.0; extra == "sim"
31
29
  Requires-Dist: pandas>=2.0.0; extra == "sim"
32
30
  Requires-Dist: scipy>=1.11.0; extra == "sim"
33
31
  Provides-Extra: testing
34
- Requires-Dist: numpy>=1.24.0; extra == "testing"
35
- Requires-Dist: pandas>=2.0.0; extra == "testing"
36
- Requires-Dist: scipy>=1.11.0; extra == "testing"
32
+ Requires-Dist: nwp500-python[sim]; extra == "testing"
37
33
  Requires-Dist: setuptools; extra == "testing"
38
34
  Requires-Dist: pytest; extra == "testing"
39
35
  Requires-Dist: pytest-cov; extra == "testing"
@@ -42,14 +38,7 @@ Requires-Dist: hypothesis; extra == "testing"
42
38
  Provides-Extra: dev
43
39
  Requires-Dist: ruff>=0.1.0; extra == "dev"
44
40
  Requires-Dist: pyright>=1.1.0; extra == "dev"
45
- Requires-Dist: numpy>=1.24.0; extra == "dev"
46
- Requires-Dist: pandas>=2.0.0; extra == "dev"
47
- Requires-Dist: scipy>=1.11.0; extra == "dev"
48
- Requires-Dist: setuptools; extra == "dev"
49
- Requires-Dist: pytest; extra == "dev"
50
- Requires-Dist: pytest-cov; extra == "dev"
51
- Requires-Dist: pytest-asyncio; extra == "dev"
52
- Requires-Dist: hypothesis; extra == "dev"
41
+ Requires-Dist: nwp500-python[testing]; extra == "dev"
53
42
  Dynamic: license-file
54
43
 
55
44
  =============
@@ -24,8 +24,7 @@ sys.path.insert(0, os.path.join(__location__, "../src"))
24
24
  # This hack is necessary since RTD does not issue `sphinx-apidoc` before running
25
25
  # `sphinx-build -b html . _build/html`. See Issue:
26
26
  # https://github.com/readthedocs/readthedocs.org/issues/1139
27
- # DON'T FORGET: Check the box "Install your project inside a virtualenv using
28
- # setup.py install" in the RTD Advanced Settings.
27
+ # RTD installs the project itself (.readthedocs.yml, python.install).
29
28
  # Additionally it helps us to avoid running apidoc manually
30
29
 
31
30
  try: # for Sphinx >= 1.7
@@ -141,6 +141,7 @@ pass it with ``--sources`` (or set ``NWP500_SOURCES``):
141
141
  demand = "binary_sensor.hot_water_demand" # on while hot water is drawn
142
142
  circulation_speed = "sensor.circulation_pump_speed"
143
143
  weather = "weather.forecast_home"
144
+ draw_flow = "sensor.hot_water_draw_flow" # the household's draws
144
145
 
145
146
  [home_assistant.heater] # optional: the heater's own entities
146
147
  heard = "sensor.water_heater_last_status"
@@ -183,7 +184,25 @@ give:
183
184
  ``demand`` entity: a binary sensor that is on while the household
184
185
  draws hot water, with the circulation's water not among its draws. A
185
186
  circulation pump with no speed reading leaves no estimate, and a draw
186
- before its first reading is skipped.
187
+ before its first reading is skipped;
188
+ - **the heat book:** with the ``[home_assistant.heater]`` table and a
189
+ ``draw_flow`` entity (the household's hot water draw flow, the
190
+ circulation's water not in it), the tank's heat booked from the last
191
+ 72 hours of the heater's readings and the draws
192
+ (``sim_home_assistant.HomeAssistantBook``). The run walks from it,
193
+ and "deliverable now" reads it (``energy``), whatever the gap between
194
+ the two probes. Without it, the probes alone answer only a full tank,
195
+ and a tank with the probes apart reads 0 (``probes``). With no full
196
+ tank in the 72 hours, or a scenario that doesn't start now, the run
197
+ walks without the book, and a warning says why.
198
+
199
+ Under "deliverable now", the table gives the bath and the showers the
200
+ tank is ready for (``sim.time_to_ready``): the bath ready with its
201
+ margin, or how short it is and when the heat pump has it ready; and the
202
+ whole showers, as a shower the tank can't finish isn't counted. Both are
203
+ delivered volumes, past the mixing valve. ``--bath`` and ``--shower``
204
+ give them in the preferred unit; without them, the parameters'
205
+ (``ReadinessParams.bath_gal``, 28.7 gal, and ``shower_gal``, 12 gal).
187
206
 
188
207
  A reading's age is from when Home Assistant last heard the sensor
189
208
  report (``last_reported``). Over the REST API, Home Assistant serves
@@ -1,6 +1,6 @@
1
1
  # Requirements file for ReadTheDocs, check .readthedocs.yml.
2
2
  # To build the module reference correctly, make sure every external package
3
- # under `install_requires` in `setup.cfg` is also listed here!
3
+ # under `dependencies` in `pyproject.toml` is also listed here!
4
4
  sphinx>=3.2.1
5
5
  # sphinx_rtd_theme
6
6
  sphinxcontrib-openapi>=0.8.0
@@ -1,8 +1,78 @@
1
1
  [build-system]
2
- # AVOID CHANGING REQUIRES: IT WILL BE UPDATED BY PYSCAFFOLD!
3
- requires = ["setuptools>=46.1.0", "setuptools_scm[toml]>=5"]
2
+ requires = ["setuptools>=77", "setuptools_scm>=8"]
4
3
  build-backend = "setuptools.build_meta"
5
4
 
5
+ [project]
6
+ name = "nwp500-python"
7
+ description = "A library for controlling Navien NWP500 Water Heaters via NaviLink"
8
+ readme = { file = "README.rst", content-type = "text/x-rst; charset=UTF-8" }
9
+ authors = [{ name = "Emmanuel Levijarvi", email = "emansl@gmail.com" }]
10
+ license = "MIT"
11
+ license-files = ["LICENSE.txt"]
12
+ requires-python = ">=3.14"
13
+ classifiers = [
14
+ "Development Status :: 4 - Beta",
15
+ "Programming Language :: Python",
16
+ "Programming Language :: Python :: 3",
17
+ "Programming Language :: Python :: 3.14",
18
+ "Programming Language :: Python :: 3 :: Only",
19
+ ]
20
+ # The version comes from the git tag (setuptools_scm).
21
+ dynamic = ["version"]
22
+ dependencies = [
23
+ "aiohttp>=3.13.5",
24
+ "awsiotsdk>=1.29.0",
25
+ "pydantic>=2.0.0",
26
+ ]
27
+
28
+ [project.optional-dependencies]
29
+ # Command line interface with optional rich formatting
30
+ cli = [
31
+ "click>=8.3.0",
32
+ "rich>=14.3.0",
33
+ ]
34
+ # Bluetooth LE access to the heater's controller (installer data)
35
+ ble = [
36
+ "bleak>=0.22.0",
37
+ ]
38
+ # The optional heater simulator (nwp500.sim)
39
+ sim = [
40
+ "numpy>=1.24.0",
41
+ "pandas>=2.0.0",
42
+ "scipy>=1.11.0",
43
+ ]
44
+ testing = [
45
+ "nwp500-python[sim]",
46
+ "setuptools",
47
+ "pytest",
48
+ "pytest-cov",
49
+ "pytest-asyncio",
50
+ "hypothesis",
51
+ ]
52
+ dev = [
53
+ "ruff>=0.1.0",
54
+ "pyright>=1.1.0",
55
+ "nwp500-python[testing]",
56
+ ]
57
+
58
+ [project.scripts]
59
+ nwp-cli = "nwp500.cli.__main__:run"
60
+
61
+ [project.urls]
62
+ Homepage = "https://github.com/eman/nwp500-python"
63
+ Documentation = "https://nwp500-python.readthedocs.io/en/latest/"
64
+ Source = "https://github.com/eman/nwp500-python"
65
+ Tracker = "https://github.com/eman/nwp500-python/issues"
66
+
67
+ [tool.setuptools]
68
+ zip-safe = false
69
+ include-package-data = true
70
+
71
+ [tool.setuptools.packages.find]
72
+ where = ["src"]
73
+ exclude = ["tests"]
74
+ namespaces = true
75
+
6
76
  [tool.setuptools_scm]
7
77
  # For smarter version schemes and other configuration options,
8
78
  # check out https://github.com/pypa/setuptools_scm
@@ -49,7 +49,7 @@ python3 scripts/validate_version.py
49
49
  **Checks:**
50
50
  - Verifies `setup.cfg` `[pyscaffold]` version hasn't been modified (should be 4.6)
51
51
  - Ensures no hardcoded `__version__` strings in source code
52
- - Confirms `setup.py` uses `setuptools_scm`
52
+ - Confirms `pyproject.toml` takes the version from `setuptools_scm`
53
53
 
54
54
  **When to run:**
55
55
  - Before creating a release
@@ -5,13 +5,15 @@ Version validation script for nwp500-python.
5
5
  This script checks for common version-related mistakes:
6
6
  1. Verifies that setup.cfg [pyscaffold] version hasn't been changed
7
7
  2. Ensures no hardcoded version strings exist in source code
8
- 3. Validates that version is derived from git tags only
8
+ 3. Validates that pyproject.toml takes the version from git tags
9
+ (setuptools_scm)
9
10
 
10
11
  Run this as part of CI/CD or pre-commit hooks.
11
12
  """
12
13
 
13
14
  import re
14
15
  import sys
16
+ import tomllib
15
17
  from pathlib import Path
16
18
 
17
19
 
@@ -120,21 +122,27 @@ def check_hardcoded_versions() -> bool:
120
122
  return True
121
123
 
122
124
 
123
- def check_setup_py() -> bool:
124
- """Verify setup.py uses setuptools_scm correctly."""
125
- setup_py = Path("setup.py")
125
+ def check_scm_version() -> bool:
126
+ """Verify pyproject.toml takes the version from setuptools_scm."""
127
+ pyproject = Path("pyproject.toml")
126
128
 
127
- if not setup_py.exists():
128
- print("Warning: setup.py not found", file=sys.stderr)
129
- return True
129
+ if not pyproject.exists():
130
+ print("Error: pyproject.toml not found", file=sys.stderr)
131
+ return False
130
132
 
131
- content = setup_py.read_text()
133
+ config = tomllib.loads(pyproject.read_text())
134
+ project = config.get("project", {})
132
135
 
133
- if "use_scm_version" not in content:
134
- print("setup.py does not use setuptools_scm", file=sys.stderr)
135
- print("", file=sys.stderr)
136
+ if "version" in project:
137
+ print("pyproject.toml hardcodes [project] version", file=sys.stderr)
138
+ return False
139
+
140
+ if "version" not in project.get("dynamic", []) or (
141
+ "setuptools_scm" not in config.get("tool", {})
142
+ ):
136
143
  print(
137
- "setup.py should contain: setup(use_scm_version={...})",
144
+ "pyproject.toml should list version in [project] dynamic and "
145
+ "have a [tool.setuptools_scm] table",
138
146
  file=sys.stderr,
139
147
  )
140
148
  return False
@@ -150,7 +158,7 @@ def main() -> int:
150
158
  all_checks = [
151
159
  ("PyScaffold version", check_pyscaffold_version),
152
160
  ("Hardcoded versions", check_hardcoded_versions),
153
- ("setup.py configuration", check_setup_py),
161
+ ("setuptools_scm configuration", check_scm_version),
154
162
  ]
155
163
 
156
164
  results = []
@@ -1,68 +1,3 @@
1
- [metadata]
2
- name = nwp500-python
3
- description = A library for controlling Navien NWP500 Water Heaters via NaviLink
4
- author = Emmanuel Levijarvi
5
- author_email = emansl@gmail.com
6
- license = MIT
7
- license_files = LICENSE.txt
8
- long_description = file: README.rst
9
- long_description_content_type = text/x-rst; charset=UTF-8
10
- url = https://github.com/eman/nwp500-python
11
- project_urls =
12
- Documentation = https://nwp500-python.readthedocs.io/en/latest/
13
- Source = https://github.com/eman/nwp500-python
14
- Tracker = https://github.com/eman/nwp500-python/issues
15
- platforms = any
16
- classifiers =
17
- Development Status :: 4 - Beta
18
- Programming Language :: Python
19
- Programming Language :: Python :: 3
20
- Programming Language :: Python :: 3.14
21
- Programming Language :: Python :: 3 :: Only
22
-
23
- [options]
24
- zip_safe = False
25
- packages = find_namespace:
26
- include_package_data = True
27
- package_dir =
28
- =src
29
- python_requires = >=3.14
30
- install_requires =
31
- aiohttp>=3.13.5
32
- awsiotsdk>=1.29.0
33
- pydantic>=2.0.0
34
-
35
- [options.packages.find]
36
- where = src
37
- exclude =
38
- tests
39
-
40
- [options.extras_require]
41
- cli =
42
- click>=8.3.0
43
- rich>=14.3.0
44
- ble =
45
- bleak>=0.22.0
46
- sim =
47
- numpy>=1.24.0
48
- pandas>=2.0.0
49
- scipy>=1.11.0
50
- testing =
51
- %(sim)s
52
- setuptools
53
- pytest
54
- pytest-cov
55
- pytest-asyncio
56
- hypothesis
57
- dev =
58
- ruff>=0.1.0
59
- pyright>=1.1.0
60
- %(testing)s
61
-
62
- [options.entry_points]
63
- console_scripts =
64
- nwp-cli = nwp500.cli.__main__:run
65
-
66
1
  [tool:pytest]
67
2
  addopts =
68
3
  --cov nwp500 --cov-report term-missing
@@ -1132,6 +1132,18 @@ _tz_option = click.option(
1132
1132
  help="A HeaterParams document (default the tank size's defaults)",
1133
1133
  )
1134
1134
  @_sources_option
1135
+ @click.option(
1136
+ "--bath",
1137
+ type=float,
1138
+ help="The bath readiness is judged against, delivered, preferred unit "
1139
+ "(default the parameters', 28.7 gal)",
1140
+ )
1141
+ @click.option(
1142
+ "--shower",
1143
+ type=float,
1144
+ help="A shower, delivered, preferred unit (default the parameters', "
1145
+ "12 gal)",
1146
+ )
1135
1147
  @click.option(
1136
1148
  "--format",
1137
1149
  "output_format",
@@ -1146,9 +1158,9 @@ def simulate_run(ctx: click.Context, output_format: str, **given: Any) -> None:
1146
1158
  inputs = _read_inputs(sim_cli, **given)
1147
1159
  now = datetime.now(UTC)
1148
1160
 
1149
- def walk(live: Any) -> None:
1161
+ def walk(live: Any, keeper: Any = None) -> None:
1150
1162
  try:
1151
- sim_cli.run(inputs, live, now, output_format)
1163
+ sim_cli.run(inputs, live, now, output_format, keeper=keeper)
1152
1164
  except sim_cli.InputError as e:
1153
1165
  raise click.UsageError(str(e)) from None
1154
1166
 
@@ -1163,9 +1175,10 @@ def simulate_run(ctx: click.Context, output_format: str, **given: Any) -> None:
1163
1175
  live = await sim_cli.compose_live(
1164
1176
  mqtt, ctx.obj["api"], device, inputs, now
1165
1177
  )
1178
+ keeper = await sim_cli.read_book(inputs, now)
1166
1179
  except sim_cli.InputError as e:
1167
1180
  raise click.UsageError(str(e)) from None
1168
- walk(live)
1181
+ walk(live, keeper)
1169
1182
 
1170
1183
  ctx.exit(asyncio.run(_with_device(ctx, command)))
1171
1184
 
@@ -11,6 +11,7 @@ This module needs the ``sim`` extra; ``__main__`` imports it only when a
11
11
  """
12
12
 
13
13
  import csv
14
+ import dataclasses
14
15
  import json
15
16
  import logging
16
17
  import os
@@ -42,6 +43,7 @@ if TYPE_CHECKING:
42
43
  from nwp500.api_client import NavienAPIClient
43
44
  from nwp500.models import Device
44
45
  from nwp500.mqtt import NavienMqttClient
46
+ from nwp500.sim_home_assistant import HomeAssistantBook
45
47
 
46
48
  __author__ = "Emmanuel Levijarvi"
47
49
  __copyright__ = "Emmanuel Levijarvi"
@@ -128,12 +130,20 @@ class Inputs:
128
130
  #: Where the air, its humidity and the inlet are read live
129
131
  #: (``sim_home_assistant.Sources``), when the heater is.
130
132
  sources: Any = None
133
+ #: The bath and the shower readiness is judged against, delivered,
134
+ #: in the preferred unit; None: the parameters'.
135
+ bath: float | None = None
136
+ shower: float | None = None
131
137
 
132
138
  def __post_init__(self) -> None:
133
139
  if self.ambient is not None and self.environment is not None:
134
140
  raise InputError("give --ambient or --environment, not both")
135
141
  if self.start is not None and self.start.tzinfo is None:
136
142
  raise InputError("--start needs its UTC offset")
143
+ for name in ("bath", "shower"):
144
+ value = getattr(self, name)
145
+ if value is not None and not value > 0.0:
146
+ raise InputError(f"--{name} must be a positive volume")
137
147
  if self.tz is not None:
138
148
  zone_name(self.tz)
139
149
  if self.scenario is not None:
@@ -268,6 +278,54 @@ async def compose_live(
268
278
  return composed
269
279
 
270
280
 
281
+ async def read_book(inputs: Inputs, now: datetime) -> HomeAssistantBook | None:
282
+ """The heat book's keeper (``sim_home_assistant.HomeAssistantBook``)
283
+ with the history read to ``now``, when the sources name the heater's
284
+ entities and the draw flow; None otherwise. Its ``book`` makes no
285
+ request, so it books after the client is closed."""
286
+ src = inputs.sources
287
+ if src is None or src.heater is None or src.entities.draw_flow is None:
288
+ return None
289
+ from nwp500 import sim_home_assistant as ha
290
+
291
+ async with ha.HomeAssistantClient(src.url, src.token) as client:
292
+ keeper = ha.HomeAssistantBook(
293
+ client, src.heater, src.entities.draw_flow, src.entities
294
+ )
295
+ await keeper.read(now)
296
+ return keeper
297
+
298
+
299
+ def booked(
300
+ keeper: HomeAssistantBook | None, built: Built, now: datetime
301
+ ) -> sim.Booked | None:
302
+ """The tank's booked heat at the scenario's start, from ``keeper``
303
+ (``read_book``): only for a scenario that starts ``now``, whose
304
+ state is the heater's now. Its gallons convert at the scenario's
305
+ inlet where one is given, as the walk takes it. Its notes are
306
+ logged."""
307
+ if keeper is None:
308
+ return None
309
+ start = built.scenario.start
310
+ if start != now:
311
+ _logger.warning(
312
+ "book: the scenario starts at %s, not now: walked without "
313
+ "the heat book",
314
+ start.isoformat(),
315
+ )
316
+ return None
317
+ answer = keeper.book(now, built.params)
318
+ for note in answer.notes:
319
+ if answer.booked is None:
320
+ _logger.warning(note)
321
+ else:
322
+ _logger.info(note)
323
+ inlet = built.scenario.environment.inlet_f
324
+ if answer.booked is None or inlet is None:
325
+ return answer.booked
326
+ return dataclasses.replace(answer.booked, inlet_f=inlet)
327
+
328
+
271
329
  def window(inputs: Inputs, now: datetime) -> tuple[datetime, timedelta]:
272
330
  """The start and horizon ``build`` will give the scenario: a flag,
273
331
  then the scenario document's, then ``now`` and ``DEFAULT_HOURS``.
@@ -349,20 +407,66 @@ def energy_totals(
349
407
  return compressor, element
350
408
 
351
409
 
410
+ def hot_water_lines(
411
+ built: Built,
412
+ book: sim.Booked | None,
413
+ bath_gal: float | None = None,
414
+ shower_gal: float | None = None,
415
+ ) -> list[str]:
416
+ """The bath and the showers the tank is ready for now
417
+ (``sim.time_to_ready``): the bath's margin, or when the heat pump
418
+ has it ready; the whole showers. Volumes in gallons, None for the
419
+ parameters'."""
420
+ try:
421
+ r = sim.time_to_ready(
422
+ built.scenario,
423
+ built.params,
424
+ book=book,
425
+ bath_gal=bath_gal,
426
+ shower_gal=shower_gal,
427
+ )
428
+ except SimulationError as e:
429
+ return [f"Bath and showers: not computed ({e})"]
430
+ bath = r.bath
431
+ if bath.ready:
432
+ state = f"ready, {_volume(bath.margin_gal)} to spare"
433
+ elif bath.ready_at is not None:
434
+ state = (
435
+ f"not ready, {_volume(-bath.margin_gal)} short; the heat pump "
436
+ f"has it ready at {_local(bath.ready_at, built.scenario.tz)}"
437
+ )
438
+ else:
439
+ state = f"not ready, {_volume(-bath.margin_gal)} short"
440
+ showers = r.showers
441
+ return [
442
+ f"Bath ({_volume(bath.volume_gal)}): {state}",
443
+ f"Showers ({_volume(showers.volume_gal)}): "
444
+ f"{showers.ready_for_whole} ready",
445
+ ]
446
+
447
+
352
448
  def print_table(
353
- built: Built, result: sim.SimulationResult, console: Console
449
+ built: Built,
450
+ result: sim.SimulationResult,
451
+ console: Console,
452
+ book: sim.Booked | None = None,
453
+ bath_gal: float | None = None,
454
+ shower_gal: float | None = None,
354
455
  ) -> None:
355
- """The heating intervals, the energy by source, the readiness, the
356
- draw windows (and which ran cold), the reasons and the defaults."""
456
+ """The heating intervals, the energy by source, the readiness (from
457
+ ``book`` when given) with the bath and the showers, the draw windows
458
+ (and which ran cold), the reasons and the defaults."""
357
459
  s = built.scenario
358
460
  tz = s.tz
359
- ready = sim.readiness(s.state, s.settings, built.params)
461
+ ready = sim.readiness(s.state, s.settings, built.params, book=book)
360
462
  console.print(
361
463
  f"[bold]{_local(s.start, tz)} to {_local(s.until, tz)}[/bold] ({tz}): "
362
464
  f"tank {_temp(s.state.upper_f)} upper, {_temp(s.state.lower_f)} "
363
465
  f"lower, setpoint {_temp(s.settings.setpoint_f)}; "
364
466
  f"{_volume(ready.gal)} deliverable now ({ready.basis})"
365
467
  )
468
+ for line in hot_water_lines(built, book, bath_gal, shower_gal):
469
+ console.print(line)
366
470
 
367
471
  heating = Table(title="Heating")
368
472
  for col in ("Start", "End", "Source", "Why", "Target", "Stop", "Wh"):
@@ -457,13 +561,16 @@ def run(
457
561
  now: datetime,
458
562
  output_format: str,
459
563
  out: TextIO | None = None,
564
+ keeper: HomeAssistantBook | None = None,
460
565
  ) -> sim.SimulationResult:
461
- """Build the scenario, walk it and print the result."""
566
+ """Build the scenario, walk it and print the result: from the tank's
567
+ booked heat when ``keeper`` (``read_book``) books one."""
462
568
  built = build(inputs, live, now)
463
569
  for note in built.notes:
464
570
  _logger.warning(note)
571
+ book = booked(keeper, built, now)
465
572
  try:
466
- result = sim.simulate(built.scenario, built.params)
573
+ result = sim.simulate(built.scenario, built.params, book=book)
467
574
  except SimulationError as e:
468
575
  raise InputError(f"the scenario cannot be simulated: {e}") from None
469
576
  stream = out or sys.stdout
@@ -473,5 +580,12 @@ def run(
473
580
  elif output_format == "csv":
474
581
  write_csv(result, stream)
475
582
  else:
476
- print_table(built, result, Console(file=stream))
583
+ print_table(
584
+ built,
585
+ result,
586
+ Console(file=stream),
587
+ book,
588
+ None if inputs.bath is None else sa.to_sim_volume(inputs.bath),
589
+ None if inputs.shower is None else sa.to_sim_volume(inputs.shower),
590
+ )
477
591
  return result
@@ -137,6 +137,10 @@ class Entities:
137
137
  #: A weather entity with an hourly forecast, its ``temperature``
138
138
  #: attribute the outdoor air.
139
139
  weather: str | None = None
140
+ #: The household's hot water draw flow, the circulation's water not
141
+ #: in it, such as a draw detector's (``HomeAssistantBook``'s
142
+ #: ``flow``): with the heater's entities, the heat book.
143
+ draw_flow: str | None = None
140
144
 
141
145
 
142
146
  @dataclass(frozen=True)
@@ -169,6 +173,7 @@ def sources_from(doc: Mapping[str, Any], environ: Mapping[str, str]) -> Sources:
169
173
  demand = "binary_sensor.hot_water_demand"
170
174
  circulation_speed = "sensor.circulation_pump_speed"
171
175
  weather = "weather.forecast_home"
176
+ draw_flow = "sensor.hot_water_draw_flow"
172
177
 
173
178
  [home_assistant.heater] # optional: the heater's own
174
179
  heard = "sensor.water_heater_last_status"
@@ -1,15 +1,13 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nwp500-python
3
- Version: 9.12.0
3
+ Version: 9.12.1
4
4
  Summary: A library for controlling Navien NWP500 Water Heaters via NaviLink
5
- Home-page: https://github.com/eman/nwp500-python
6
- Author: Emmanuel Levijarvi
7
- Author-email: emansl@gmail.com
8
- License: MIT
5
+ Author-email: Emmanuel Levijarvi <emansl@gmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/eman/nwp500-python
9
8
  Project-URL: Documentation, https://nwp500-python.readthedocs.io/en/latest/
10
9
  Project-URL: Source, https://github.com/eman/nwp500-python
11
10
  Project-URL: Tracker, https://github.com/eman/nwp500-python/issues
12
- Platform: any
13
11
  Classifier: Development Status :: 4 - Beta
14
12
  Classifier: Programming Language :: Python
15
13
  Classifier: Programming Language :: Python :: 3
@@ -31,9 +29,7 @@ Requires-Dist: numpy>=1.24.0; extra == "sim"
31
29
  Requires-Dist: pandas>=2.0.0; extra == "sim"
32
30
  Requires-Dist: scipy>=1.11.0; extra == "sim"
33
31
  Provides-Extra: testing
34
- Requires-Dist: numpy>=1.24.0; extra == "testing"
35
- Requires-Dist: pandas>=2.0.0; extra == "testing"
36
- Requires-Dist: scipy>=1.11.0; extra == "testing"
32
+ Requires-Dist: nwp500-python[sim]; extra == "testing"
37
33
  Requires-Dist: setuptools; extra == "testing"
38
34
  Requires-Dist: pytest; extra == "testing"
39
35
  Requires-Dist: pytest-cov; extra == "testing"
@@ -42,14 +38,7 @@ Requires-Dist: hypothesis; extra == "testing"
42
38
  Provides-Extra: dev
43
39
  Requires-Dist: ruff>=0.1.0; extra == "dev"
44
40
  Requires-Dist: pyright>=1.1.0; extra == "dev"
45
- Requires-Dist: numpy>=1.24.0; extra == "dev"
46
- Requires-Dist: pandas>=2.0.0; extra == "dev"
47
- Requires-Dist: scipy>=1.11.0; extra == "dev"
48
- Requires-Dist: setuptools; extra == "dev"
49
- Requires-Dist: pytest; extra == "dev"
50
- Requires-Dist: pytest-cov; extra == "dev"
51
- Requires-Dist: pytest-asyncio; extra == "dev"
52
- Requires-Dist: hypothesis; extra == "dev"
41
+ Requires-Dist: nwp500-python[testing]; extra == "dev"
53
42
  Dynamic: license-file
54
43
 
55
44
  =============