nwp500-python 9.3.2__tar.gz → 9.4.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 (230) hide show
  1. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/CHANGELOG.rst +173 -0
  2. {nwp500_python-9.3.2/src/nwp500_python.egg-info → nwp500_python-9.4.1}/PKG-INFO +1 -1
  3. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/docs/explanation/index.rst +1 -0
  4. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/docs/explanation/tank-energy.rst +7 -7
  5. nwp500_python-9.4.1/docs/explanation/tou-recovery-cap.rst +188 -0
  6. nwp500_python-9.4.1/docs/how-to/maintenance.rst +139 -0
  7. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/docs/how-to/schedule-operation.rst +36 -69
  8. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/docs/how-to/track-energy.rst +43 -15
  9. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/docs/reference/protocol/data_conversions.rst +3 -3
  10. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/docs/reference/protocol/device_status.rst +2 -2
  11. nwp500_python-9.4.1/docs/reference/protocol/mqtt_protocol.rst +1230 -0
  12. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/docs/reference/protocol/quick_reference.rst +61 -29
  13. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/docs/reference/python_api/cli.rst +133 -24
  14. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/docs/reference/python_api/events.rst +3 -1
  15. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/docs/reference/python_api/models.rst +94 -58
  16. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/docs/reference/python_api/mqtt_client.rst +174 -66
  17. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/examples/advanced/error_code_demo.py +3 -1
  18. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/src/nwp500/__init__.py +12 -4
  19. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/src/nwp500/cli/__main__.py +205 -16
  20. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/src/nwp500/cli/handlers.py +283 -1
  21. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/src/nwp500/cli/output_formatters.py +62 -0
  22. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/src/nwp500/cli/presentation.py +251 -8
  23. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/src/nwp500/cli/rich_output.py +55 -10
  24. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/src/nwp500/converters.py +2 -2
  25. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/src/nwp500/enums.py +40 -16
  26. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/src/nwp500/models/__init__.py +13 -4
  27. nwp500_python-9.4.1/src/nwp500/models/diagnostics.py +147 -0
  28. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/src/nwp500/models/energy.py +35 -3
  29. nwp500_python-9.4.1/src/nwp500/models/firmware.py +47 -0
  30. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/src/nwp500/models/schedule.py +105 -157
  31. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/src/nwp500/models/status.py +13 -1
  32. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/src/nwp500/mqtt/_control_commands.py +42 -33
  33. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/src/nwp500/mqtt/_device_subscriptions.py +105 -17
  34. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/src/nwp500/mqtt/client.py +76 -3
  35. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/src/nwp500/mqtt/control.py +435 -157
  36. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/src/nwp500/mqtt/reconnection.py +7 -0
  37. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/src/nwp500/mqtt/subscriptions.py +327 -157
  38. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/src/nwp500/mqtt/utils.py +9 -0
  39. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/src/nwp500/topic_builder.py +49 -6
  40. {nwp500_python-9.3.2 → nwp500_python-9.4.1/src/nwp500_python.egg-info}/PKG-INFO +1 -1
  41. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/src/nwp500_python.egg-info/SOURCES.txt +4 -0
  42. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/src/nwp500_python.egg-info/scm_file_list.json +4 -0
  43. nwp500_python-9.4.1/src/nwp500_python.egg-info/scm_version.json +8 -0
  44. nwp500_python-9.4.1/tests/test_app_queries.py +901 -0
  45. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/tests/test_cli_commands.py +434 -1
  46. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/tests/test_mqtt_reliability.py +39 -0
  47. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/tests/test_protocol_correctness.py +353 -132
  48. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/tests/test_public_api.py +59 -0
  49. nwp500_python-9.3.2/docs/how-to/maintenance.rst +0 -119
  50. nwp500_python-9.3.2/docs/reference/protocol/mqtt_protocol.rst +0 -929
  51. nwp500_python-9.3.2/src/nwp500_python.egg-info/scm_version.json +0 -8
  52. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/.coveragerc +0 -0
  53. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/.github/RESOLVING_PR_COMMENTS.md +0 -0
  54. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/.github/copilot-instructions.md +0 -0
  55. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/.github/workflows/ci.yml +0 -0
  56. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/.github/workflows/release.yml +0 -0
  57. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/.gitignore +0 -0
  58. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/.pre-commit-config.yaml +0 -0
  59. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/.readthedocs.yml +0 -0
  60. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/AUTHORS.rst +0 -0
  61. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/CONTRIBUTING.rst +0 -0
  62. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/LICENSE.txt +0 -0
  63. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/Makefile +0 -0
  64. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/README.rst +0 -0
  65. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/RELEASE.md +0 -0
  66. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/docs/Makefile +0 -0
  67. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/docs/_static/.gitignore +0 -0
  68. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/docs/conf.py +0 -0
  69. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/docs/explanation/advanced-features.rst +0 -0
  70. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/docs/explanation/architecture.rst +0 -0
  71. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/docs/explanation/unknown-values.rst +0 -0
  72. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/docs/how-to/authenticate.rst +0 -0
  73. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/docs/how-to/auto-recovery.rst +0 -0
  74. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/docs/how-to/diagnose-mqtt.rst +0 -0
  75. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/docs/how-to/home-assistant.rst +0 -0
  76. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/docs/how-to/index.rst +0 -0
  77. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/docs/how-to/manage-units.rst +0 -0
  78. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/docs/how-to/monitor-status.rst +0 -0
  79. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/docs/how-to/optimize-tou.rst +0 -0
  80. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/docs/how-to/queue-commands.rst +0 -0
  81. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/docs/index.rst +0 -0
  82. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/docs/openapi.yaml +0 -0
  83. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/docs/project/authors.rst +0 -0
  84. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/docs/project/changelog.rst +0 -0
  85. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/docs/project/contributing.rst +0 -0
  86. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/docs/project/history.rst +0 -0
  87. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/docs/project/license.rst +0 -0
  88. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/docs/reference/configuration.rst +0 -0
  89. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/docs/reference/enumerations.rst +0 -0
  90. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/docs/reference/index.rst +0 -0
  91. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/docs/reference/installation.rst +0 -0
  92. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/docs/reference/protocol/device_features.rst +0 -0
  93. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/docs/reference/protocol/error_codes.rst +0 -0
  94. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/docs/reference/protocol/rest_api.rst +0 -0
  95. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/docs/reference/python_api/api_client.rst +0 -0
  96. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/docs/reference/python_api/auth_client.rst +0 -0
  97. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/docs/reference/python_api/exceptions.rst +0 -0
  98. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/docs/requirements.txt +0 -0
  99. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/docs/tutorials/getting-started.rst +0 -0
  100. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/examples/.ruff.toml +0 -0
  101. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/examples/README.md +0 -0
  102. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/examples/advanced/air_filter_reset.py +0 -0
  103. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/examples/advanced/anti_legionella.py +0 -0
  104. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/examples/advanced/auto_recovery.py +0 -0
  105. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/examples/advanced/combined_callbacks.py +0 -0
  106. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/examples/advanced/demand_response.py +0 -0
  107. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/examples/advanced/device_capabilities.py +0 -0
  108. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/examples/advanced/device_status_debug.py +0 -0
  109. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/examples/advanced/energy_analytics.py +0 -0
  110. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/examples/advanced/firmware_payload_capture.py +0 -0
  111. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/examples/advanced/mqtt_diagnostics.py +0 -0
  112. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/examples/advanced/power_control.py +0 -0
  113. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/examples/advanced/recirculation_control.py +0 -0
  114. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/examples/advanced/reconnection_demo.py +0 -0
  115. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/examples/advanced/reservation_schedule.py +0 -0
  116. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/examples/advanced/simple_auto_recovery.py +0 -0
  117. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/examples/advanced/token_restoration.py +0 -0
  118. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/examples/advanced/tou_openei.py +0 -0
  119. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/examples/advanced/tou_schedule.py +0 -0
  120. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/examples/advanced/water_reservation.py +0 -0
  121. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/examples/beginner/01_authentication.py +0 -0
  122. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/examples/beginner/02_list_devices.py +0 -0
  123. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/examples/beginner/03_get_status.py +0 -0
  124. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/examples/beginner/04_set_temperature.py +0 -0
  125. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/examples/intermediate/advanced_auth_patterns.py +0 -0
  126. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/examples/intermediate/command_queue.py +0 -0
  127. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/examples/intermediate/device_status_callback.py +0 -0
  128. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/examples/intermediate/error_handling.py +0 -0
  129. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/examples/intermediate/event_driven_control.py +0 -0
  130. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/examples/intermediate/improved_auth.py +0 -0
  131. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/examples/intermediate/legacy_auth_constructor.py +0 -0
  132. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/examples/intermediate/mqtt_realtime_monitoring.py +0 -0
  133. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/examples/intermediate/periodic_requests.py +0 -0
  134. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/examples/intermediate/set_mode.py +0 -0
  135. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/examples/intermediate/vacation_mode.py +0 -0
  136. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/examples/mask.py +0 -0
  137. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/examples/testing/periodic_device_info.py +0 -0
  138. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/examples/testing/simple_periodic_info.py +0 -0
  139. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/examples/testing/test_api_client.py +0 -0
  140. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/examples/testing/test_mqtt_connection.py +0 -0
  141. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/examples/testing/test_mqtt_messaging.py +0 -0
  142. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/examples/testing/test_periodic_minimal.py +0 -0
  143. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/pyproject.toml +0 -0
  144. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/scripts/README.md +0 -0
  145. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/scripts/bump_version.py +0 -0
  146. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/scripts/diagnose_mqtt_connection.py +0 -0
  147. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/scripts/extract_changelog.py +0 -0
  148. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/scripts/format.py +0 -0
  149. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/scripts/lint.py +0 -0
  150. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/scripts/setup-dev.py +0 -0
  151. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/scripts/validate_version.py +0 -0
  152. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/setup.cfg +0 -0
  153. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/setup.py +0 -0
  154. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/src/nwp500/_base.py +0 -0
  155. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/src/nwp500/api_client.py +0 -0
  156. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/src/nwp500/auth.py +0 -0
  157. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/src/nwp500/cli/__init__.py +0 -0
  158. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/src/nwp500/cli/monitoring.py +0 -0
  159. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/src/nwp500/cli/token_storage.py +0 -0
  160. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/src/nwp500/command_decorators.py +0 -0
  161. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/src/nwp500/config.py +0 -0
  162. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/src/nwp500/device_capabilities.py +0 -0
  163. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/src/nwp500/device_info_cache.py +0 -0
  164. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/src/nwp500/encoding.py +0 -0
  165. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/src/nwp500/events.py +0 -0
  166. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/src/nwp500/exceptions.py +0 -0
  167. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/src/nwp500/factory.py +0 -0
  168. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/src/nwp500/field_factory.py +0 -0
  169. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/src/nwp500/models/_converters.py +0 -0
  170. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/src/nwp500/models/device.py +0 -0
  171. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/src/nwp500/models/feature.py +0 -0
  172. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/src/nwp500/models/mqtt_models.py +0 -0
  173. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/src/nwp500/models/tou.py +0 -0
  174. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/src/nwp500/mqtt/__init__.py +0 -0
  175. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/src/nwp500/mqtt/command_queue.py +0 -0
  176. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/src/nwp500/mqtt/connection.py +0 -0
  177. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/src/nwp500/mqtt/diagnostics.py +0 -0
  178. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/src/nwp500/mqtt/periodic.py +0 -0
  179. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/src/nwp500/mqtt/state_tracker.py +0 -0
  180. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/src/nwp500/mqtt/types.py +0 -0
  181. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/src/nwp500/mqtt_events.py +0 -0
  182. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/src/nwp500/openei.py +0 -0
  183. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/src/nwp500/py.typed +0 -0
  184. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/src/nwp500/reservations.py +0 -0
  185. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/src/nwp500/temperature.py +0 -0
  186. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/src/nwp500/tou_schedule.py +0 -0
  187. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/src/nwp500/unit_system.py +0 -0
  188. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/src/nwp500/utils.py +0 -0
  189. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/src/nwp500_python.egg-info/dependency_links.txt +0 -0
  190. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/src/nwp500_python.egg-info/entry_points.txt +0 -0
  191. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/src/nwp500_python.egg-info/not-zip-safe +0 -0
  192. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/src/nwp500_python.egg-info/requires.txt +0 -0
  193. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/src/nwp500_python.egg-info/top_level.txt +0 -0
  194. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/tests/conftest.py +0 -0
  195. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/tests/test_api_helpers.py +0 -0
  196. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/tests/test_auth.py +0 -0
  197. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/tests/test_auth_session_lifecycle.py +0 -0
  198. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/tests/test_bug_fixes.py +0 -0
  199. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/tests/test_canonical_schedule.py +0 -0
  200. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/tests/test_cli_basic.py +0 -0
  201. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/tests/test_cli_tristate_presentation.py +0 -0
  202. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/tests/test_command_decorators.py +0 -0
  203. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/tests/test_command_queue.py +0 -0
  204. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/tests/test_device_capabilities.py +0 -0
  205. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/tests/test_device_info_cache.py +0 -0
  206. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/tests/test_device_rest_models.py +0 -0
  207. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/tests/test_events.py +0 -0
  208. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/tests/test_exceptions.py +0 -0
  209. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/tests/test_model_converters.py +0 -0
  210. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/tests/test_models.py +0 -0
  211. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/tests/test_mqtt_clean_session_resume.py +0 -0
  212. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/tests/test_mqtt_client_init.py +0 -0
  213. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/tests/test_mqtt_events.py +0 -0
  214. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/tests/test_mqtt_hypothesis.py +0 -0
  215. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/tests/test_mqtt_reconnection.py +0 -0
  216. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/tests/test_mqtt_reconnection_storm.py +0 -0
  217. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/tests/test_multi_device.py +0 -0
  218. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/tests/test_openei.py +0 -0
  219. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/tests/test_reservations.py +0 -0
  220. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/tests/test_temperature_converters.py +0 -0
  221. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/tests/test_threading_model.py +0 -0
  222. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/tests/test_token_storage.py +0 -0
  223. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/tests/test_tou_api.py +0 -0
  224. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/tests/test_tou_schedule.py +0 -0
  225. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/tests/test_unit_switching.py +0 -0
  226. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/tests/test_unit_system_process_wide.py +0 -0
  227. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/tests/test_utility_modules.py +0 -0
  228. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/tests/test_utils.py +0 -0
  229. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/tox.ini +0 -0
  230. {nwp500_python-9.3.2 → nwp500_python-9.4.1}/uv.lock +0 -0
@@ -5,6 +5,178 @@ Changelog
5
5
  Unreleased
6
6
  ==========
7
7
 
8
+ Version 9.4.1 (2026-09-17)
9
+ ==========================
10
+
11
+ Fixed
12
+ -----
13
+ - **The reconnect attempt counter never reset after a successful quick or
14
+ deep reconnect.** Both build a new connection, so the SDK never calls
15
+ ``on_connection_resumed``, the only place the counter was reset. Each
16
+ later interruption started where the last one stopped: its first delay
17
+ was longer and it reached the deep-reconnect threshold sooner. A
18
+ long-running client saw the first delay grow from about 1 s to 12 s
19
+ over five daily AWS IoT disconnects. The counter now resets whenever the
20
+ backoff loop ends connected.
21
+
22
+ Version 9.4.0 (2026-09-12)
23
+ ==========================
24
+
25
+ This release realigns the MQTT command set with what the NaviLink app
26
+ actually sends. The app (version 2.03.00) was decompiled and its request
27
+ builder compared with the library; every query it sends now has a
28
+ method, six commands it never sends were removed, and the recirculation
29
+ schedule write was corrected. Breaking changes are listed under
30
+ *Removed* and *Changed*.
31
+
32
+ Added
33
+ -----
34
+ - **Installer diagnostics query.** ``request_diagnostics()`` /
35
+ ``subscribe_diagnostics()`` and the ``nwp-cli diagnostics`` command
36
+ return the lifetime counters the app's installer screen shows: heat
37
+ pump and element energy, days since installation, fault event counts,
38
+ demand-response operation times, hot-water draw statistics and
39
+ component run times and start counts, as
40
+ :class:`~nwp500.models.DeviceDiagnostics`. The device answers the
41
+ ``st/td/rd`` query with packed hex; the NaviLink cloud decodes it into
42
+ JSON only when the reply topic has the app's five-segment form, so the
43
+ library requests that form. Verified live: the energies and compressor
44
+ and element run times match the energy query's lifetime totals.
45
+ - **Monthly energy query.** ``request_energy_usage_monthly()`` /
46
+ ``subscribe_energy_usage_monthly()`` and ``nwp-cli energy --years``
47
+ return one entry per year with twelve per-month items. Verified live.
48
+ - **Hourly energy query.** ``request_energy_usage_hourly()`` /
49
+ ``subscribe_energy_usage_hourly()`` send the app's per-hour query. The
50
+ NWP500 tested never answered it in four attempts, so it has no CLI
51
+ command.
52
+ - **Recirculation schedule read.** ``request_recirculation_schedule()``
53
+ and ``nwp-cli recirc-schedule get``; the write is exposed as
54
+ ``nwp-cli recirc-schedule set``, which checks the JSON before
55
+ connecting (known keys only, integers in range, at most 20 entries).
56
+ The read was verified live on a unit that reports recirculation
57
+ scheduling but has no pump (empty, disabled schedule); the write and
58
+ its echo are unverified. The CLI reports a write as updated only when
59
+ the echo matches what was written. ``RecirculationSchedule`` and
60
+ ``RecirculationScheduleEntry`` fields are strict integers, so ``True``
61
+ or ``6.0`` is rejected instead of coerced.
62
+ - **Firmware download info.** ``request_firmware_download_info()`` /
63
+ ``subscribe_firmware_download_info()`` and ``nwp-cli firmware info``
64
+ return :class:`~nwp500.models.FirmwareDownloadInfo`. The device replies
65
+ on its own topic rather than a client-keyed one. Verified live.
66
+ - **Session end.** ``end_session()`` sends the ``st/end`` query the app
67
+ sends whenever it leaves a device. No reply to it has been observed.
68
+ Unlike other commands it is never queued while disconnected (it raises
69
+ ``MqttNotConnectedError``), since a session end replayed after a
70
+ reconnect would end the new session.
71
+ - ``MqttConnectionConfig.send_session_end_on_disconnect`` (default
72
+ ``True``) controls whether ``disconnect()`` sends it.
73
+ - ``subscribe_firmware_commit_response()`` receives the reply to
74
+ ``commit_firmware_update()``, which is requested on the app-form
75
+ ``res/commit-ota`` topic that no other subscription covers.
76
+ - **Controls with app builder cases that had no method:**
77
+ ``set_vacation_duration(days)`` (``goout-day``; the app has a builder
78
+ case but no screen that calls it; verified live to change the day count
79
+ without changing the operation mode),
80
+ ``set_air_filter_life(hours)`` (``air-filter-life``; the wire value is
81
+ hours divided by 500, matching the app's 0 or 1000-10000 picker;
82
+ verified live) and ``reset_condenser_fault()`` (``cond-fault-reset``,
83
+ installer-level in the app; acknowledged live, clearing effect not
84
+ observable without a fault). CLI: ``vacation-duration``,
85
+ ``filter-life``, ``reset-condenser-fault``.
86
+ - **Command codes** for every member of the app's ``DeviceControlMGPP``
87
+ enum, with the declared-but-never-sent ones annotated.
88
+ - ``EnergyUsageResponse.type_of_usage``, ``get_year_data()``,
89
+ ``MonthlyEnergyData.day`` and ``EnergyUsageDay.ep_usage`` /
90
+ ``water_usage`` (declared by the app; not reported by the NWP500).
91
+
92
+ Changed
93
+ -------
94
+ - **Recirculation schedule models take the app's entry shape** (breaking).
95
+ :class:`~nwp500.models.RecirculationScheduleEntry` is now
96
+ ``enable``/``week``/``hour``/``min``/``mode``/``param`` like a
97
+ reservation entry (``mode`` is the entry's pump on/off toggle in the
98
+ app's editor) and :class:`~nwp500.models.RecirculationSchedule` carries
99
+ ``reservation_use`` plus ``reservation`` instead of ``schedule``. The
100
+ previous start/end window fields were not what the device accepts.
101
+ - ``MonthlyEnergyData.month`` is optional: monthly-query entries have no
102
+ month.
103
+ - **``NavienMqttClient.disconnect()`` now sends ``st/end``** to every
104
+ device that was passed to any subscription, as the NaviLink app does
105
+ when it leaves a device. The publishes go straight to the connection,
106
+ never into the offline command queue, stop if the connection drops,
107
+ are bounded by a short timeout and never fail the disconnect. What it
108
+ does to other clients connected to the same device (for example a Home
109
+ Assistant integration) is not known; turn it off with
110
+ ``MqttConnectionConfig(send_session_end_on_disconnect=False)``.
111
+ - **CLI energy summaries total the requested periods.** ``energy
112
+ --month``, ``--months`` and ``--years`` printed the device's lifetime
113
+ total under a per-month or per-year heading. Each summary now covers
114
+ only the rows shown, and the lifetime total is printed once under its
115
+ own heading. (The response's ``total`` is lifetime whatever is
116
+ requested; checked live.)
117
+ - **Parameter validation.** ``set_air_filter_life()``,
118
+ ``set_vacation_duration()`` and the daily, monthly and hourly energy
119
+ queries reject non-integers (including ``True`` and floats) and
120
+ out-of-range values; years must be 2000-2099 and ``--years`` drops
121
+ duplicates. ``filter-life``, ``--year`` and ``--years`` reject bad
122
+ input before connecting. ``request_energy_usage()`` now rejects an
123
+ empty month list.
124
+
125
+ Fixed
126
+ -----
127
+ - **``configure_recirculation_schedule()`` published to the wrong topic
128
+ with the wrong keys.** It sent ``{"schedule": [...]}`` on the bare
129
+ ``ctrl`` topic; the app publishes RECIR_RESERVATION on
130
+ ``ctrl/recirc-rsv/rd`` with ``reservationUse`` and ``reservation`` and
131
+ asks for the echo on ``recirc-rsv/rd``.
132
+ - **``commit_firmware_update()`` published to the bare ``ctrl`` topic.**
133
+ The app publishes OTA_COMMIT on ``ctrl/commit-ota`` and asks for the
134
+ reply on ``res/commit-ota``; the library now does the same. Not
135
+ exercised against a device.
136
+ - **Unsubscribing could remove the wrong handler.** When one callback was
137
+ registered for more than one device-wildcard subscription (status,
138
+ feature, firmware info), unsubscribing one removed whichever handler
139
+ came first. Handlers are now matched on subscription kind as well.
140
+ - **Query replies could reach another device's callback.** Client-keyed
141
+ reply topics are shared by every device a client queries, so with two
142
+ devices subscribed a reservation, energy, TOU, diagnostics or
143
+ recirculation reply reached both callbacks. Typed subscriptions now
144
+ ignore replies whose ``macAddress`` names a different device.
145
+ - Recirculation read-backs in hex carry the unused ``param`` as ``0xFF``
146
+ and in JSON as ``-1``; the model normalizes to ``-1`` so the two compare
147
+ equal.
148
+ - **Protocol documentation** rewritten from the decompiled request
149
+ builder and live captures: real topic structure, the anti-legionella
150
+ modes (``anti-leg-on [days]`` / ``anti-leg-off``, not
151
+ ``anti-legionella-setting``), the message example that paired the
152
+ weekly-reservation code with ``dhw-temperature``, the undocumented
153
+ 16777226 reservation write, response shapes for every query, and the
154
+ quick reference's invented ``header``/``body`` format with 0x11/0x21/0x31
155
+ codes that never existed. Stale ``docs/protocol/`` paths in code
156
+ comments and docstrings now point at ``docs/reference/protocol/``.
157
+ - ``models/schedule.py`` docstrings cited 33554444 (the hot button) for
158
+ the recirculation reservation; it is 33554440.
159
+ - CLI reference: token cache path is ``~/.nwp500_tokens.json``, ``-vv``
160
+ is a repeated ``-v`` (there was no ``--very-verbose``), and the
161
+ ``--unit-system`` option and ``tou rates``/``plan``/``apply`` commands
162
+ are documented.
163
+
164
+ Removed
165
+ -------
166
+ - **Six MQTT commands the NaviLink app never sends** (breaking):
167
+ ``check_firmware_update()``, ``reconnect_wifi()``, ``reset_wifi()``,
168
+ ``set_freeze_protection_temperature()``, ``run_smart_diagnostic()`` and
169
+ ``update_weekly_reservation()`` with
170
+ ``subscribe_weekly_reservation_response()`` and the
171
+ ``WeeklyReservationEntry`` / ``WeeklyReservationSchedule`` models. The
172
+ app's enum declares the first five codes but its request builder has
173
+ no case for them, so the library's ``ota-check``, ``wifi-reconnect``,
174
+ ``wifi-reset``, ``frez-temp`` and ``smart-diagnostic`` mode strings were
175
+ invented. The app's weekly schedule is the ordinary reservation
176
+ program: it writes with 16777226 on ``ctrl/rsv/rd``
177
+ (``update_reservations()``), never with 33554438 or a ``rsv-weekly/rd``
178
+ topic. The ``CommandCode`` members stay, annotated as declared only.
179
+
8
180
  Version 9.3.2 (2026-09-01)
9
181
  ==========================
10
182
 
@@ -129,6 +301,7 @@ Fixed
129
301
  other clients on the same device. Query results the device routes back to a
130
302
  client-keyed topic, such as reservations and energy usage, were already
131
303
  covered by the existing response wildcard.
304
+
132
305
  Version 9.3.0 (2026-08-03)
133
306
  ==========================
134
307
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nwp500-python
3
- Version: 9.3.2
3
+ Version: 9.4.1
4
4
  Summary: A library for controlling Navien NWP500 Water Heaters via NaviLink
5
5
  Home-page: https://github.com/eman/nwp500-python
6
6
  Author: Emmanuel Levijarvi
@@ -10,4 +10,5 @@ Understanding-oriented deep dives into the library's design and advanced feature
10
10
  advanced-features
11
11
  architecture
12
12
  tank-energy
13
+ tou-recovery-cap
13
14
  unknown-values
@@ -3,7 +3,7 @@ Tank Energy
3
3
  ===========
4
4
 
5
5
  The NWP500 reports two energy figures, ``totalEnergyCapacity`` and
6
- ``availableEnergyCapacity``. Both names mislead, and before v10.0 this
6
+ ``availableEnergyCapacity``. Both names mislead, and before v9.3.0 this
7
7
  library also scaled them wrongly. This page explains what they
8
8
  actually measure and shows the evidence for the correction.
9
9
 
@@ -33,7 +33,7 @@ The short version
33
33
  whenever the setpoint moves.
34
34
 
35
35
  Both are raw counts of 4 Wh each, not Watt-hours. Library versions before
36
- 10.0 multiplied by 10, overstating tank energy by 2.5x.
36
+ 9.3.0 multiplied by 10, overstating tank energy by 2.5x.
37
37
 
38
38
  Both are also measured **from the setpoint**, so both describe potential
39
39
  rather than content: move the setpoint and both change while the water in
@@ -226,7 +226,7 @@ coefficient of performance:
226
226
  * - 4 Wh/count (corrected)
227
227
  - 2.89
228
228
  - Normal for a heat pump water heater
229
- * - 10 Wh/count (pre-10.0)
229
+ * - 10 Wh/count (pre-9.3.0)
230
230
  - 7.02
231
231
  - Physically impossible
232
232
 
@@ -236,7 +236,7 @@ A heat pump water heater in a 72 degF room runs at a COP of roughly 2 to
236
236
  This argument is worth stating separately because it needs no tank
237
237
  volume, specific heat or stratification model - only the device's own
238
238
  reported energy and its own reported power. It cannot tell you what the
239
- quantum *is*, but it rules out the pre-10.0 value regardless of anything
239
+ quantum *is*, but it rules out the pre-9.3.0 value regardless of anything
240
240
  assumed elsewhere on this page.
241
241
 
242
242
  .. _two branches:
@@ -403,8 +403,8 @@ the heat capacity is 156 Wh per degF:
403
403
  drawable_Wh = 156 * (tank_mean_temperature - your_floor_degF)
404
404
 
405
405
 
406
- Migrating from before v10.0
407
- ===========================
406
+ Migrating from before v9.3.0
407
+ ============================
408
408
 
409
409
  .. list-table::
410
410
  :header-rows: 1
@@ -429,7 +429,7 @@ rename that is missed fails immediately with ``AttributeError`` instead
429
429
  of silently returning a number 2.5x too large.
430
430
 
431
431
  If you logged these values historically, the stored series needs
432
- rescaling by 0.4 to be comparable with values from v10.0 onward.
432
+ rescaling by 0.4 to be comparable with values from v9.3.0 onward.
433
433
 
434
434
 
435
435
  See also
@@ -0,0 +1,188 @@
1
+ ==========================
2
+ The TOU Recovery Cap
3
+ ==========================
4
+
5
+ When a Time-of-Use schedule is active, the NWP500 stops a heat-pump
6
+ recovery at about **90 % charge** instead of running to the setpoint. The
7
+ tank finishes roughly 1.9 degC below ``hp_lower_off_temp_setting`` and
8
+ stays there. Nothing in the protocol announces this, and no error is
9
+ raised - the compressor simply stops early.
10
+
11
+ This matters for anything that waits for the tank to reach its setpoint:
12
+ under an active TOU window that instant never arrives.
13
+
14
+ .. contents::
15
+ :local:
16
+ :depth: 2
17
+
18
+
19
+ The short version
20
+ =================
21
+
22
+ .. list-table::
23
+ :header-rows: 1
24
+ :widths: 30 70
25
+
26
+ * - Observation
27
+ - Value
28
+ * - Charge at termination
29
+ - ``dhw_charge_per`` = 90 % (p10 89, p90 91). Never reaches 100.
30
+ * - Shortfall below setpoint
31
+ - ``tank_lower_temperature`` finishes 1.9 degC (3.42 degF) below
32
+ ``hp_lower_off_temp_setting``
33
+ * - Precondition
34
+ - An expensive TOU period in force, un-overridden. ``tou_status``
35
+ alone is **not** the test - see below.
36
+ * - Frequency
37
+ - 59 of 333 recoveries (18 %) over 260 days
38
+
39
+ A normal recovery, for contrast, ends with ``dhw_charge_per`` at 100 %
40
+ (p10 99) and the lower probe at or above the setpoint.
41
+
42
+
43
+ Evidence
44
+ ========
45
+
46
+ Measured across 333 heat-pump recovery cycles of 15-480 minutes, from
47
+ 2025-12-28 to 2026-09-06, on one unit. 196 reached the lower-off
48
+ setpoint; 137 did not.
49
+
50
+ The shortfall of the 137 is **bimodal**, not a spread:
51
+
52
+ .. code:: text
53
+
54
+ shortfall below hp_lower_off_temp_setting, cycles that never reached it
55
+
56
+ min 0.18 p10 1.66 p25 3.42 median 3.42 p75 4.32
57
+ p90 31.03 max 58.86 (degF)
58
+
59
+ most common values: 3.42 degF x 59 cycles 3.24 x 6 3.60 x 6
60
+
61
+ The spike at 3.42 degF is 1.9 degC exactly, and 19 steps of the 0.1 degC
62
+ tank-probe quantum. It holds across eight different setpoints from 140.0
63
+ to 147.2 degF and appears in all nine months observed, so it is an
64
+ offset in the control logic rather than a proportional effect or a
65
+ seasonal one. The long tail beyond 8 degF is ordinary interruption -
66
+ a draw starting mid-recovery, a mode change - and is unrelated.
67
+
68
+ ``tou_status`` looks like a clean separator, and is not one:
69
+
70
+ .. list-table::
71
+ :header-rows: 1
72
+ :widths: 34 14 26 26
73
+
74
+ * - Population
75
+ - n
76
+ - ``tou_status`` True
77
+ - ``tou_override_status`` True
78
+ * - Stopped 3.42 degF short
79
+ - 59
80
+ - **59 / 59 (100 %)**
81
+ - 0 / 59
82
+ * - Other unreached
83
+ - 78
84
+ - 49 / 78 (63 %)
85
+ - 6 / 78
86
+ * - Reached setpoint
87
+ - 196
88
+ - 98 / 196 (50 %)
89
+ - 27 / 196
90
+
91
+ All 59 ran in ``HEAT_PUMP`` mode throughout, so this is not
92
+ ``ENERGY_SAVER`` or ``VACATION`` behaviour.
93
+
94
+ But read that table carefully: 98 cycles reached the setpoint with
95
+ ``tou_status`` True. **The flag only reports that TOU scheduling is
96
+ enabled.** It says nothing about whether the recovery ran inside an
97
+ expensive period, and the cap bites only when it did - outside one, an
98
+ enabled schedule does nothing at all.
99
+
100
+ The device marks a period itself. While inside one it applies non-zero
101
+ ``*_diff_temp_setting`` offsets and reverts them at the end; on the unit
102
+ measured this toggles at 21:00 and 04:00 UTC daily. Using that as the
103
+ in-period test, among ``tou_status`` True cycles:
104
+
105
+ .. list-table::
106
+ :header-rows: 1
107
+ :widths: 34 22 22 22
108
+
109
+ * - At cycle end
110
+ - Capped
111
+ - Reached
112
+ - Other
113
+ * - Inside a period
114
+ - **42 (62 %)**
115
+ - 12
116
+ - 14
117
+ * - Outside a period
118
+ - 16 (12 %)
119
+ - 86
120
+ - 34
121
+
122
+ Test it at the moment the cycle *ended*, not when it began: a recovery
123
+ that starts off-peak and runs into a period is still capped, and testing
124
+ the start catches 17 of 59 against 43 testing the end.
125
+
126
+ Two plausible explanations were tested and **refuted**:
127
+
128
+ - *The upper zone satisfied its own cut-out first.* No -
129
+ ``tank_upper_temperature`` reached ``hp_upper_off_temp_setting`` on
130
+ only 2 of 137 unreached cycles, against 179 of 196 reached ones. The
131
+ upper probe finishes short too, by a median of 4.5 degF.
132
+ - *The device terminates on outlet temperature.* No -
133
+ ``dhw_outlet_temperature`` reached the setpoint on only 2 of 136.
134
+
135
+
136
+ What is not yet established
137
+ ===========================
138
+
139
+ Being inside a period is a strong predictor but not a deterministic one:
140
+ 12 cycles reached the setpoint from inside one, and 16 were capped from
141
+ outside. Some of that is the marker's own resolution - it is sampled at
142
+ the cycle end, and a period boundary crossed mid-recovery is not captured
143
+ - but it has not been reconciled against the schedule the device was
144
+ actually holding, which ``configure_tou_schedule_confirmed`` can read
145
+ back.
146
+
147
+ The 90 % figure has been observed on one unit under one TOU schedule. It
148
+ is not known whether the ceiling is fixed in firmware, derived from the
149
+ schedule, or configurable.
150
+
151
+ A note on reading ``tou_status`` over time: the field emits brief
152
+ ``unknown`` values on integration reconnect - sub-second blips that
153
+ return immediately to the value they interrupted - so a raw state history
154
+ contains entries that are not transitions. Stores that keep only boolean
155
+ values drop these, which is the right behaviour and leaves a faithful
156
+ record of the genuine changes.
157
+
158
+ Do not read a low record count as lost data. It means the state was
159
+ *held*: on the unit measured, ``tou_status`` logged 165 changes in one
160
+ month and 5 in another, and the sparse month was the one in which TOU was
161
+ active nearly continuously. Forward-filling the genuine transitions is
162
+ correct. It can be corroborated against the ``*_diff_temp_setting``
163
+ fields, which toggle away from zero at the start of each TOU window and
164
+ back at the end.
165
+
166
+
167
+ Working with the cap
168
+ ====================
169
+
170
+ **Do not wait for the setpoint.** Code that treats "tank reached
171
+ ``hp_lower_off_temp_setting``" as the completion signal will block
172
+ indefinitely on a capped recovery. Watch ``dhw_charge_per`` plateauing,
173
+ or the compressor stopping, instead.
174
+
175
+ **Do not read a capped cycle as a fault or as degraded capacity.** The
176
+ appliance is doing what the schedule told it to. A recovery that ends at
177
+ 90 % charge with ``tou_status`` True is a normal outcome.
178
+
179
+ **When measuring recovery duration, treat capped cycles separately.**
180
+ They are not censored observations of a full recovery - they are
181
+ complete observations of a different, shorter target. Pooling them with
182
+ uncapped recoveries biases any duration estimate downward; discarding
183
+ them biases it upward, because capped cycles are systematically shorter
184
+ (median 161 minutes against 235 for cycles that ran to setpoint).
185
+
186
+ **To force a full recovery,** set ``tou_override_status`` - none of the
187
+ 59 capped cycles had it set, and 27 of the cycles that ran to setpoint
188
+ under an active TOU schedule did.
@@ -0,0 +1,139 @@
1
+ ==================
2
+ Device Maintenance
3
+ ==================
4
+
5
+ Maintenance commands cover firmware update information, the installer
6
+ diagnostics counters, air filter service intervals and fault resets. All
7
+ of them mirror what the NaviLink app sends; the CLI equivalents are noted
8
+ with each one.
9
+
10
+ .. contents:: On This Page
11
+ :local:
12
+ :depth: 2
13
+
14
+ Before You Start
15
+ ================
16
+
17
+ Some maintenance operations are gated on device capabilities. Request
18
+ device features first so the client can inspect capability flags.
19
+
20
+ .. code-block:: python
21
+
22
+ await mqtt.subscribe_device_feature(device, lambda feature: print(feature))
23
+ await mqtt.request_device_info(device)
24
+
25
+ Installer Diagnostics
26
+ =====================
27
+
28
+ The NaviLink app's installer screen shows lifetime counters the device
29
+ keeps: heat pump and element energy, days since installation, fault
30
+ event counts, demand-response operation times, hot-water draw statistics
31
+ and component run times and start counts. The gate is in the app, so a
32
+ consumer account's unit answers the query.
33
+
34
+ .. code-block:: python
35
+
36
+ from nwp500 import DeviceDiagnostics
37
+
38
+ def on_diagnostics(diag: DeviceDiagnostics) -> None:
39
+ ts, ta = diag.ts_data, diag.ta_data
40
+ print(f"Installed {ts.days_since_installation} days ago")
41
+ print(f"Heat pump: {ts.cumulated_pwr_hp} Wh over "
42
+ f"{ta.cumulated_op_time_comp} h, {ta.cumulated_op_num_comp} starts")
43
+ print(f"Condensate overflows: {ts.cumulated_occ_num_con_ovr_flow}")
44
+
45
+ await mqtt.subscribe_diagnostics(device, on_diagnostics)
46
+ await mqtt.request_diagnostics(device)
47
+
48
+ The two energies are watt-hours and ``cumulated_op_time_*`` values are
49
+ hours (both cross-checked against the energy query); the remaining
50
+ counters have no documented unit. See
51
+ :class:`~nwp500.models.DeviceDiagnostics` for every field.
52
+
53
+ CLI: ``nwp-cli diagnostics`` (add ``--json`` for the raw model).
54
+
55
+ Firmware Download Information
56
+ =============================
57
+
58
+ The device reports which firmware components it has downloaded for an
59
+ over-the-air update. A unit with nothing pending reports one all-zero
60
+ entry.
61
+
62
+ .. code-block:: python
63
+
64
+ from nwp500 import FirmwareDownloadInfo
65
+
66
+ def on_firmware(info: FirmwareDownloadInfo) -> None:
67
+ for entry in info.download_sw_info:
68
+ print(entry.component_name, entry.sw_version, entry.status)
69
+
70
+ await mqtt.subscribe_firmware_download_info(device, on_firmware)
71
+ await mqtt.request_firmware_download_info(device)
72
+
73
+ To apply a downloaded update, call
74
+ :meth:`nwp500.mqtt.client.NavienMqttClient.commit_firmware_update` with an
75
+ :class:`~nwp500.models.OtaCommitPayload` naming the component code and
76
+ version from the entry above.
77
+
78
+ .. warning::
79
+
80
+ Committing firmware reboots the device. Heating and MQTT connectivity
81
+ will be interrupted until the upgrade completes.
82
+
83
+ .. code-block:: python
84
+
85
+ from nwp500 import OtaCommitPayload
86
+
87
+ payload = OtaCommitPayload(swCode=1, swVersion=1234)
88
+ await mqtt.commit_firmware_update(device, payload)
89
+
90
+ CLI: ``nwp-cli firmware info``.
91
+
92
+ Air Filter
93
+ ==========
94
+
95
+ The air filter timer is reset after cleaning or replacing the filter. The
96
+ service interval is set in evaporator-fan hours: ``0`` disables the alarm,
97
+ otherwise 1000 to 10000 hours in 500-hour steps, which are the values the
98
+ NaviLink app offers. The device reports the interval back as
99
+ ``DeviceStatus.air_filter_alarm_period``.
100
+
101
+ .. code-block:: python
102
+
103
+ await mqtt.set_air_filter_life(device, 3000)
104
+ await mqtt.reset_air_filter(device)
105
+
106
+ CLI: ``nwp-cli filter-life 3000`` and ``nwp-cli reset-filter``.
107
+
108
+ Condenser Fault Reset
109
+ =====================
110
+
111
+ Clears a condenser fault. The NaviLink app exposes this on its status
112
+ screen to installer accounts only; the gate is in the app. A consumer
113
+ account's unit acknowledges the command; the clearing effect could not be
114
+ observed because no fault was present.
115
+
116
+ .. code-block:: python
117
+
118
+ await mqtt.reset_condenser_fault(device)
119
+
120
+ CLI: ``nwp-cli reset-condenser-fault``.
121
+
122
+ Commands the App Never Sends
123
+ ============================
124
+
125
+ The NaviLink app's command enum also declares an OTA check, WiFi reconnect
126
+ and reset, a freeze-protection temperature and a smart diagnostic trigger,
127
+ but its request builder has no case for any of them, so no NaviLink client
128
+ publishes those codes and their payloads are unknown. Earlier versions of
129
+ this library sent made-up payloads for them; those methods were removed.
130
+ The freeze-protection range and the smart diagnostic result remain
131
+ readable from :class:`~nwp500.models.DeviceFeature` and
132
+ :class:`~nwp500.models.DeviceStatus`.
133
+
134
+ Related Documentation
135
+ =====================
136
+
137
+ * :doc:`../reference/python_api/mqtt_client` - Full MQTT client API reference
138
+ * :doc:`schedule-operation` - Reservations, recirculation schedules, and intelligent scheduling
139
+ * :doc:`diagnose-mqtt` - Connection troubleshooting and diagnostics