nwp500-python 9.13.0__tar.gz → 9.13.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 (375) hide show
  1. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/.github/workflows/ci.yml +11 -11
  2. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/.github/workflows/release.yml +7 -7
  3. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/CHANGELOG.rst +116 -0
  4. {nwp500_python-9.13.0/src/nwp500_python.egg-info → nwp500_python-9.13.1}/PKG-INFO +2 -2
  5. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/how-to/diagnose-mqtt.rst +42 -9
  6. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/reference/configuration.rst +1 -1
  7. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/reference/installation.rst +3 -3
  8. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/reference/protocol/mqtt_protocol.rst +4 -3
  9. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/pyproject.toml +1 -5
  10. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/setup.cfg +9 -3
  11. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/__init__.py +1 -1
  12. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/ble/mqtt_adapter.py +13 -0
  13. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/cli/__main__.py +6 -2
  14. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/cli/handlers.py +48 -7
  15. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/converters.py +39 -5
  16. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/device_capabilities.py +6 -4
  17. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/models/feature.py +9 -3
  18. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/models/status.py +27 -12
  19. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/mqtt/_device_subscriptions.py +13 -13
  20. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/mqtt/client.py +25 -22
  21. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/mqtt/connection.py +137 -30
  22. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/mqtt/control.py +17 -2
  23. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/mqtt/periodic.py +28 -0
  24. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/mqtt/subscriptions.py +371 -150
  25. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/mqtt/utils.py +153 -2
  26. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/sim/api.py +35 -6
  27. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/sim/engine.py +174 -28
  28. {nwp500_python-9.13.0 → nwp500_python-9.13.1/src/nwp500_python.egg-info}/PKG-INFO +2 -2
  29. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500_python.egg-info/SOURCES.txt +6 -0
  30. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500_python.egg-info/requires.txt +1 -1
  31. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500_python.egg-info/scm_file_list.json +6 -0
  32. nwp500_python-9.13.1/src/nwp500_python.egg-info/scm_version.json +8 -0
  33. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_cli_commands.py +10 -0
  34. nwp500_python-9.13.1/tests/test_cli_reply_cleanup.py +139 -0
  35. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_model_converters.py +19 -4
  36. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_mqtt_clean_session_resume.py +2 -1
  37. nwp500_python-9.13.1/tests/test_mqtt_connection_builder.py +114 -0
  38. nwp500_python-9.13.1/tests/test_mqtt_connection_robustness.py +446 -0
  39. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_mqtt_control_fail.py +2 -1
  40. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_mqtt_last_will.py +2 -6
  41. nwp500_python-9.13.1/tests/test_mqtt_parse_once.py +502 -0
  42. nwp500_python-9.13.1/tests/test_mqtt_payload_log.py +305 -0
  43. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_mqtt_reconnection_storm.py +5 -1
  44. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_sim_import_contract.py +2 -3
  45. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_sim_ready_api.py +22 -0
  46. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_sim_walk_cost.py +128 -2
  47. nwp500_python-9.13.1/tests/test_unknown_enum_values.py +143 -0
  48. nwp500_python-9.13.0/src/nwp500_python.egg-info/scm_version.json +0 -8
  49. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/.coveragerc +0 -0
  50. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/.github/RESOLVING_PR_COMMENTS.md +0 -0
  51. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/.github/copilot-instructions.md +0 -0
  52. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/.gitignore +0 -0
  53. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/.pre-commit-config.yaml +0 -0
  54. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/.readthedocs.yml +0 -0
  55. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/AUTHORS.rst +0 -0
  56. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/CONTRIBUTING.rst +0 -0
  57. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/LICENSE.txt +0 -0
  58. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/Makefile +0 -0
  59. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/README.rst +0 -0
  60. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/RELEASE.md +0 -0
  61. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/Makefile +0 -0
  62. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/_static/.gitignore +0 -0
  63. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/_static/light-page.js +0 -0
  64. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/conf.py +0 -0
  65. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/explanation/architecture.rst +0 -0
  66. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/explanation/draws.rst +0 -0
  67. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/explanation/heating-elements.rst +0 -0
  68. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/explanation/index.rst +0 -0
  69. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/explanation/recovery-speed.rst +0 -0
  70. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/explanation/simulator-methodology.rst +0 -0
  71. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/explanation/simulator-tried.rst +0 -0
  72. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/explanation/simulator.rst +0 -0
  73. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/explanation/standby-loss.rst +0 -0
  74. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/explanation/tank-energy.rst +0 -0
  75. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/explanation/tou-recovery-cap.rst +0 -0
  76. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/explanation/unknown-values.rst +0 -0
  77. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/explanation/what-starts-a-recovery.rst +0 -0
  78. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/how-to/authenticate.rst +0 -0
  79. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/how-to/auto-recovery.rst +0 -0
  80. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/how-to/bluetooth.rst +0 -0
  81. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/how-to/calibrate-simulator.rst +0 -0
  82. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/how-to/home-assistant.rst +0 -0
  83. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/how-to/index.rst +0 -0
  84. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/how-to/maintenance.rst +0 -0
  85. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/how-to/manage-units.rst +0 -0
  86. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/how-to/monitor-status.rst +0 -0
  87. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/how-to/optimize-tou.rst +0 -0
  88. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/how-to/queue-commands.rst +0 -0
  89. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/how-to/schedule-operation.rst +0 -0
  90. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/how-to/simulate-heater.rst +0 -0
  91. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/how-to/track-energy.rst +0 -0
  92. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/index.rst +0 -0
  93. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/openapi.yaml +0 -0
  94. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/project/authors.rst +0 -0
  95. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/project/changelog.rst +0 -0
  96. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/project/contributing.rst +0 -0
  97. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/project/history.rst +0 -0
  98. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/project/license.rst +0 -0
  99. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/reference/enumerations.rst +0 -0
  100. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/reference/index.rst +0 -0
  101. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/reference/protocol/data_conversions.rst +0 -0
  102. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/reference/protocol/device_features.rst +0 -0
  103. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/reference/protocol/device_status.rst +0 -0
  104. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/reference/protocol/error_codes.rst +0 -0
  105. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/reference/protocol/quick_reference.rst +0 -0
  106. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/reference/protocol/rest_api.rst +0 -0
  107. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/reference/python_api/api_client.rst +0 -0
  108. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/reference/python_api/auth_client.rst +0 -0
  109. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/reference/python_api/cli.rst +0 -0
  110. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/reference/python_api/events.rst +0 -0
  111. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/reference/python_api/exceptions.rst +0 -0
  112. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/reference/python_api/models.rst +0 -0
  113. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/reference/python_api/mqtt_client.rst +0 -0
  114. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/reference/python_api/sim.rst +0 -0
  115. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/reference/simulator-inputs.rst +0 -0
  116. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/requirements.txt +0 -0
  117. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/docs/tutorials/getting-started.rst +0 -0
  118. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/examples/.ruff.toml +0 -0
  119. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/examples/README.md +0 -0
  120. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/examples/advanced/air_filter_reset.py +0 -0
  121. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/examples/advanced/anti_legionella.py +0 -0
  122. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/examples/advanced/auto_recovery.py +0 -0
  123. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/examples/advanced/combined_callbacks.py +0 -0
  124. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/examples/advanced/demand_response.py +0 -0
  125. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/examples/advanced/device_capabilities.py +0 -0
  126. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/examples/advanced/device_status_debug.py +0 -0
  127. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/examples/advanced/energy_analytics.py +0 -0
  128. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/examples/advanced/error_code_demo.py +0 -0
  129. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/examples/advanced/firmware_payload_capture.py +0 -0
  130. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/examples/advanced/mqtt_diagnostics.py +0 -0
  131. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/examples/advanced/power_control.py +0 -0
  132. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/examples/advanced/recirculation_control.py +0 -0
  133. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/examples/advanced/reconnection_demo.py +0 -0
  134. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/examples/advanced/reservation_schedule.py +0 -0
  135. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/examples/advanced/simple_auto_recovery.py +0 -0
  136. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/examples/advanced/token_restoration.py +0 -0
  137. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/examples/advanced/tou_openei.py +0 -0
  138. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/examples/advanced/tou_schedule.py +0 -0
  139. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/examples/advanced/water_reservation.py +0 -0
  140. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/examples/beginner/01_authentication.py +0 -0
  141. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/examples/beginner/02_list_devices.py +0 -0
  142. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/examples/beginner/03_get_status.py +0 -0
  143. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/examples/beginner/04_set_temperature.py +0 -0
  144. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/examples/intermediate/advanced_auth_patterns.py +0 -0
  145. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/examples/intermediate/command_queue.py +0 -0
  146. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/examples/intermediate/device_status_callback.py +0 -0
  147. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/examples/intermediate/error_handling.py +0 -0
  148. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/examples/intermediate/event_driven_control.py +0 -0
  149. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/examples/intermediate/improved_auth.py +0 -0
  150. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/examples/intermediate/legacy_auth_constructor.py +0 -0
  151. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/examples/intermediate/mqtt_realtime_monitoring.py +0 -0
  152. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/examples/intermediate/periodic_requests.py +0 -0
  153. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/examples/intermediate/set_mode.py +0 -0
  154. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/examples/intermediate/vacation_mode.py +0 -0
  155. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/examples/mask.py +0 -0
  156. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/examples/simulator/lower-setpoint.json +0 -0
  157. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/examples/simulator/plan.json +0 -0
  158. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/examples/simulator/scenario.json +0 -0
  159. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/examples/testing/periodic_device_info.py +0 -0
  160. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/examples/testing/simple_periodic_info.py +0 -0
  161. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/examples/testing/test_api_client.py +0 -0
  162. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/examples/testing/test_mqtt_connection.py +0 -0
  163. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/examples/testing/test_mqtt_messaging.py +0 -0
  164. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/examples/testing/test_periodic_minimal.py +0 -0
  165. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/scripts/README.md +0 -0
  166. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/scripts/bump_version.py +0 -0
  167. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/scripts/diagnose_mqtt_connection.py +0 -0
  168. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/scripts/extract_changelog.py +0 -0
  169. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/scripts/format.py +0 -0
  170. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/scripts/lint.py +0 -0
  171. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/scripts/setup-dev.py +0 -0
  172. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/scripts/validate_version.py +0 -0
  173. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/_base.py +0 -0
  174. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/api_client.py +0 -0
  175. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/auth.py +0 -0
  176. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/ble/__init__.py +0 -0
  177. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/ble/__main__.py +0 -0
  178. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/ble/client.py +0 -0
  179. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/ble/protocol.py +0 -0
  180. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/cli/__init__.py +0 -0
  181. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/cli/ble_backend.py +0 -0
  182. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/cli/calibration.py +0 -0
  183. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/cli/monitoring.py +0 -0
  184. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/cli/output_formatters.py +0 -0
  185. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/cli/presentation.py +0 -0
  186. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/cli/recording.py +0 -0
  187. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/cli/rich_output.py +0 -0
  188. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/cli/simulate.py +0 -0
  189. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/cli/token_storage.py +0 -0
  190. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/command_decorators.py +0 -0
  191. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/config.py +0 -0
  192. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/device_info_cache.py +0 -0
  193. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/encoding.py +0 -0
  194. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/enums.py +0 -0
  195. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/events.py +0 -0
  196. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/exceptions.py +0 -0
  197. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/factory.py +0 -0
  198. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/field_factory.py +0 -0
  199. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/models/__init__.py +0 -0
  200. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/models/_converters.py +0 -0
  201. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/models/account.py +0 -0
  202. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/models/device.py +0 -0
  203. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/models/diagnostics.py +0 -0
  204. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/models/energy.py +0 -0
  205. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/models/firmware.py +0 -0
  206. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/models/maintenance.py +0 -0
  207. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/models/mqtt_models.py +0 -0
  208. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/models/schedule.py +0 -0
  209. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/models/tou.py +0 -0
  210. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/mqtt/__init__.py +0 -0
  211. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/mqtt/_control_commands.py +0 -0
  212. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/mqtt/command_queue.py +0 -0
  213. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/mqtt/diagnostics.py +0 -0
  214. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/mqtt/reconnection.py +0 -0
  215. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/mqtt/state_tracker.py +0 -0
  216. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/mqtt/types.py +0 -0
  217. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/mqtt_events.py +0 -0
  218. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/openei.py +0 -0
  219. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/py.typed +0 -0
  220. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/reservations.py +0 -0
  221. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/sim/__init__.py +0 -0
  222. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/sim/attribution.py +0 -0
  223. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/sim/calibration.py +0 -0
  224. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/sim/carry.py +0 -0
  225. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/sim/circulation.py +0 -0
  226. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/sim/codec.py +0 -0
  227. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/sim/cold_supply.py +0 -0
  228. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/sim/controller.py +0 -0
  229. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/sim/decay.py +0 -0
  230. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/sim/defaults.py +0 -0
  231. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/sim/delivery.py +0 -0
  232. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/sim/draw_ensemble.py +0 -0
  233. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/sim/draw_forecast.py +0 -0
  234. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/sim/extensions.py +0 -0
  235. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/sim/heat_pump_fit.py +0 -0
  236. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/sim/heat_pump_recovery.py +0 -0
  237. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/sim/heating_trajectory.py +0 -0
  238. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/sim/history.py +0 -0
  239. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/sim/history_frame.py +0 -0
  240. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/sim/inputs.py +0 -0
  241. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/sim/location_air.py +0 -0
  242. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/sim/margins.py +0 -0
  243. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/sim/observed.py +0 -0
  244. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/sim/params.py +0 -0
  245. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/sim/physics/__init__.py +0 -0
  246. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/sim/physics/draw.py +0 -0
  247. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/sim/physics/element.py +0 -0
  248. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/sim/physics/heat_pump.py +0 -0
  249. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/sim/physics/stratified.py +0 -0
  250. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/sim/physics/water.py +0 -0
  251. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/sim/profile.py +0 -0
  252. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/sim/ready.py +0 -0
  253. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/sim/record.py +0 -0
  254. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/sim/result.py +0 -0
  255. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/sim/schedules.py +0 -0
  256. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/sim/tank_fit.py +0 -0
  257. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/sim/tank_physics.py +0 -0
  258. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/sim/tou_triggers.py +0 -0
  259. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/sim/units.py +0 -0
  260. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/sim_adapters.py +0 -0
  261. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/sim_calibrate.py +0 -0
  262. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/sim_home_assistant.py +0 -0
  263. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/sim_live.py +0 -0
  264. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/sim_score.py +0 -0
  265. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/status_history.py +0 -0
  266. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/temperature.py +0 -0
  267. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/topic_builder.py +0 -0
  268. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/tou_schedule.py +0 -0
  269. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/unit_system.py +0 -0
  270. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500/utils.py +0 -0
  271. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500_python.egg-info/dependency_links.txt +0 -0
  272. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500_python.egg-info/entry_points.txt +0 -0
  273. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500_python.egg-info/not-zip-safe +0 -0
  274. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/src/nwp500_python.egg-info/top_level.txt +0 -0
  275. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/conftest.py +0 -0
  276. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/fixtures/sim/circulation_run_2026-10-29.json +0 -0
  277. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/fixtures/sim/engine_golden.json +0 -0
  278. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/fixtures/sim/heater_params.json +0 -0
  279. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/fixtures/sim/heater_params_fitted.json +0 -0
  280. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/fixtures/sim/heater_params_two_zone.json +0 -0
  281. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/sim_cases.py +0 -0
  282. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_api_endpoints_201.py +0 -0
  283. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_api_helpers.py +0 -0
  284. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_app_queries.py +0 -0
  285. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_auth.py +0 -0
  286. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_auth_session_lifecycle.py +0 -0
  287. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_auth_session_refresh.py +0 -0
  288. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_ble_mqtt_adapter.py +0 -0
  289. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_ble_protocol.py +0 -0
  290. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_bug_fixes.py +0 -0
  291. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_canonical_schedule.py +0 -0
  292. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_cli_basic.py +0 -0
  293. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_cli_calibrate.py +0 -0
  294. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_cli_simulate.py +0 -0
  295. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_cli_tristate_presentation.py +0 -0
  296. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_command_decorators.py +0 -0
  297. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_command_queue.py +0 -0
  298. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_device_capabilities.py +0 -0
  299. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_device_info_cache.py +0 -0
  300. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_device_rest_models.py +0 -0
  301. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_events.py +0 -0
  302. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_exceptions.py +0 -0
  303. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_models.py +0 -0
  304. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_mqtt_client_init.py +0 -0
  305. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_mqtt_events.py +0 -0
  306. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_mqtt_hypothesis.py +0 -0
  307. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_mqtt_reconnection.py +0 -0
  308. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_mqtt_reliability.py +0 -0
  309. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_multi_device.py +0 -0
  310. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_navilink_201_enums.py +0 -0
  311. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_openei.py +0 -0
  312. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_protocol_correctness.py +0 -0
  313. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_public_api.py +0 -0
  314. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_reservation_validation.py +0 -0
  315. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_reservations.py +0 -0
  316. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_sim_adapters.py +0 -0
  317. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_sim_api.py +0 -0
  318. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_sim_calibrate.py +0 -0
  319. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_sim_calibration.py +0 -0
  320. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_sim_carry.py +0 -0
  321. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_sim_change_history.py +0 -0
  322. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_sim_circulation.py +0 -0
  323. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_sim_codec.py +0 -0
  324. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_sim_cold_supply.py +0 -0
  325. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_sim_controller.py +0 -0
  326. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_sim_correction_top.py +0 -0
  327. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_sim_defaults.py +0 -0
  328. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_sim_documents.py +0 -0
  329. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_sim_draw.py +0 -0
  330. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_sim_draw_duration.py +0 -0
  331. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_sim_draw_ensemble.py +0 -0
  332. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_sim_draw_forecast.py +0 -0
  333. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_sim_electric.py +0 -0
  334. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_sim_element.py +0 -0
  335. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_sim_element_fit.py +0 -0
  336. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_sim_envelope.py +0 -0
  337. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_sim_fast_draws.py +0 -0
  338. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_sim_forecast.py +0 -0
  339. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_sim_golden.py +0 -0
  340. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_sim_heat_pump.py +0 -0
  341. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_sim_heat_pump_fit.py +0 -0
  342. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_sim_history.py +0 -0
  343. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_sim_home_assistant.py +0 -0
  344. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_sim_hot_water.py +0 -0
  345. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_sim_live.py +0 -0
  346. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_sim_location_air.py +0 -0
  347. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_sim_margins.py +0 -0
  348. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_sim_meter_reads.py +0 -0
  349. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_sim_no_clock_no_state.py +0 -0
  350. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_sim_observed.py +0 -0
  351. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_sim_overlay.py +0 -0
  352. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_sim_profile.py +0 -0
  353. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_sim_readiness.py +0 -0
  354. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_sim_ready.py +0 -0
  355. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_sim_replay.py +0 -0
  356. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_sim_resume.py +0 -0
  357. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_sim_schedules.py +0 -0
  358. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_sim_score.py +0 -0
  359. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_sim_score_days.py +0 -0
  360. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_sim_stratified.py +0 -0
  361. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_sim_tank_fit.py +0 -0
  362. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_sim_tank_physics.py +0 -0
  363. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_sim_undiluted_share.py +0 -0
  364. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_sim_water.py +0 -0
  365. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_status_history.py +0 -0
  366. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_temperature_converters.py +0 -0
  367. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_threading_model.py +0 -0
  368. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_token_storage.py +0 -0
  369. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_tou_api.py +0 -0
  370. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_tou_schedule.py +0 -0
  371. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_unit_switching.py +0 -0
  372. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_unit_system_process_wide.py +0 -0
  373. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_utility_modules.py +0 -0
  374. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tests/test_utils.py +0 -0
  375. {nwp500_python-9.13.0 → nwp500_python-9.13.1}/tox.ini +0 -0
@@ -13,10 +13,10 @@ jobs:
13
13
  name: Lint and Format Check
14
14
  runs-on: ubuntu-latest
15
15
  steps:
16
- - uses: actions/checkout@v4
16
+ - uses: actions/checkout@v7
17
17
 
18
18
  - name: Set up Python
19
- uses: actions/setup-python@v5
19
+ uses: actions/setup-python@v7
20
20
  with:
21
21
  python-version: '3.14'
22
22
 
@@ -32,10 +32,10 @@ jobs:
32
32
  name: Security Check
33
33
  runs-on: ubuntu-latest
34
34
  steps:
35
- - uses: actions/checkout@v4
35
+ - uses: actions/checkout@v7
36
36
 
37
37
  - name: Set up Python
38
- uses: actions/setup-python@v5
38
+ uses: actions/setup-python@v7
39
39
  with:
40
40
  python-version: '3.14'
41
41
 
@@ -55,10 +55,10 @@ jobs:
55
55
  python-version: ['3.14']
56
56
 
57
57
  steps:
58
- - uses: actions/checkout@v4
58
+ - uses: actions/checkout@v7
59
59
 
60
60
  - name: Set up Python ${{ matrix.python-version }}
61
- uses: actions/setup-python@v5
61
+ uses: actions/setup-python@v7
62
62
  with:
63
63
  python-version: ${{ matrix.python-version }}
64
64
 
@@ -82,10 +82,10 @@ jobs:
82
82
  runs-on: ubuntu-latest
83
83
 
84
84
  steps:
85
- - uses: actions/checkout@v4
85
+ - uses: actions/checkout@v7
86
86
 
87
87
  - name: Set up Python
88
- uses: actions/setup-python@v5
88
+ uses: actions/setup-python@v7
89
89
  with:
90
90
  python-version: '3.14'
91
91
 
@@ -110,12 +110,12 @@ jobs:
110
110
  needs: [lint, test, test-without-cli-extras]
111
111
 
112
112
  steps:
113
- - uses: actions/checkout@v4
113
+ - uses: actions/checkout@v7
114
114
  with:
115
115
  fetch-depth: 0 # Required for setuptools_scm
116
116
 
117
117
  - name: Set up Python
118
- uses: actions/setup-python@v5
118
+ uses: actions/setup-python@v7
119
119
  with:
120
120
  python-version: '3.14'
121
121
 
@@ -131,7 +131,7 @@ jobs:
131
131
  run: twine check dist/*
132
132
 
133
133
  - name: Upload artifacts
134
- uses: actions/upload-artifact@v4
134
+ uses: actions/upload-artifact@v7
135
135
  with:
136
136
  name: dist
137
137
  path: dist/
@@ -13,10 +13,10 @@ jobs:
13
13
  name: Pre-release Checks
14
14
  runs-on: ubuntu-latest
15
15
  steps:
16
- - uses: actions/checkout@v4
16
+ - uses: actions/checkout@v7
17
17
 
18
18
  - name: Set up Python
19
- uses: actions/setup-python@v5
19
+ uses: actions/setup-python@v7
20
20
  with:
21
21
  python-version: '3.14'
22
22
 
@@ -46,12 +46,12 @@ jobs:
46
46
  id-token: write # Required for trusted publishing
47
47
 
48
48
  steps:
49
- - uses: actions/checkout@v4
49
+ - uses: actions/checkout@v7
50
50
  with:
51
51
  fetch-depth: 0 # Required for setuptools_scm
52
52
 
53
53
  - name: Set up Python
54
- uses: actions/setup-python@v5
54
+ uses: actions/setup-python@v7
55
55
  with:
56
56
  python-version: '3.14'
57
57
 
@@ -75,10 +75,10 @@ jobs:
75
75
  needs: build-and-publish
76
76
 
77
77
  steps:
78
- - uses: actions/checkout@v4
78
+ - uses: actions/checkout@v7
79
79
 
80
80
  - name: Set up Python
81
- uses: actions/setup-python@v5
81
+ uses: actions/setup-python@v7
82
82
  with:
83
83
  python-version: '3.14'
84
84
 
@@ -93,7 +93,7 @@ jobs:
93
93
  echo "EOF" >> $GITHUB_OUTPUT
94
94
 
95
95
  - name: Create Release
96
- uses: softprops/action-gh-release@v1
96
+ uses: softprops/action-gh-release@v3
97
97
  with:
98
98
  body: ${{ steps.changelog.outputs.notes }}
99
99
  draft: false
@@ -2,6 +2,122 @@
2
2
  Changelog
3
3
  =========
4
4
 
5
+ Unreleased
6
+ ==========
7
+
8
+ Version 9.13.1 (2026-11-14)
9
+ ===========================
10
+
11
+ This release makes the MQTT connection steadier, drops the
12
+ ``awsiotsdk`` dependency for ``awscrt`` directly, keeps a message whose
13
+ enum code the library does not know, and makes a simulated recovery
14
+ with a TOU window ahead much faster.
15
+
16
+ Added
17
+ -----
18
+
19
+ - **Every MQTT payload sent and received can be logged.** Set the
20
+ ``nwp500.mqtt.payloads`` logger to DEBUG for one line per message:
21
+ topic, packet id for a send, and the JSON with MAC addresses
22
+ redacted. See "What did the device get?" in the MQTT diagnostics
23
+ guide.
24
+
25
+ Changed
26
+ -------
27
+
28
+ - **Each command carries its own ``sessionID``**, the send time in
29
+ milliseconds, in place of one value for the life of the client.
30
+ Acknowledgements repeat it, so a reply can be matched to its
31
+ command. App-connection events and the Last-Will keep the client's
32
+ ``session_id``.
33
+ - **The MQTT keep-alive default is 60 seconds**, down from 1200, so a
34
+ connection that dies without a disconnect is noticed in about a
35
+ minute and a half instead of about 20 minutes. Set
36
+ ``MqttConnectionConfig.keep_alive_secs`` to keep the old value.
37
+ - **The MQTT connection is built with ``awscrt`` directly; the
38
+ ``awsiotsdk`` dependency is gone.** It was used only to build the
39
+ signed websocket connection and pinned ``awscrt`` to one release.
40
+ The library now requires ``awscrt>=0.36.1,<0.39``. The CONNECT
41
+ packet no longer carries the SDK's name and version in its username.
42
+
43
+ Fixed
44
+ -----
45
+
46
+ - **A simulated recovery with a TOU window ahead is no longer slowed
47
+ by each draw.** A draw during a recovery judged the TOU cap again on
48
+ a 16-hour walk ahead, so a forecast's small draw every few minutes
49
+ walked the tank 40 to 50 times the simulated span. The cap is now
50
+ judged where it can act: as a window opens, at the cap's level, or
51
+ at the level inside a window. Results are unchanged. A 28-hour day
52
+ of 10-minute draws with a peak ahead went from 3 to 11 seconds to
53
+ about half a second, close to the same day without TOU (#409).
54
+ - **The TOU cap's look-ahead stops a running element at its off
55
+ point.** It walked the tank with what ran at its start, so an
56
+ element running beside the compressor heated for the whole 16 hours;
57
+ the cap was judged on an overheated tank, and a long enough walk
58
+ failed with non-finite layers.
59
+ - **A code the library does not know no longer drops the whole
60
+ message.** An unlisted ``errorCode``, ``operationMode``,
61
+ ``currentHeatUse`` or any other enum field in a status or feature
62
+ message failed validation, and the message was discarded, so status
63
+ updates stopped when the heater reported a new fault. An unknown code
64
+ now becomes a pseudo-member named ``UNKNOWN_<code>``: it compares
65
+ equal to the code, ``.name`` and ``.value`` work as for any member,
66
+ and it survives pickling and copying. The device list's
67
+ ``connected`` and the feature's ``volumeCode`` do the same, through
68
+ ``converters.enum_validator``.
69
+ - **An unknown ``dhwTemperatureSettingUse`` code does not permit
70
+ temperature writes.** Only the three known enabled control types
71
+ pass the ``dhw_temperature_setting_use`` capability check.
72
+ - **A status or feature message is parsed and announced once.** With
73
+ two callbacks subscribed for one device, each message was validated
74
+ twice and ``status_received`` (or ``feature_received``) was emitted
75
+ twice. Callbacks for the same device now share one parsed object,
76
+ and the event and state tracking run once per message.
77
+ - **The CLI unsubscribes the callbacks it registers to wait for a
78
+ reply**, so ``monitor`` and other long-running commands no longer
79
+ get each later message again for every earlier request. Over
80
+ Bluetooth the adapter gains the matching
81
+ ``unsubscribe_device_status`` and ``unsubscribe_device_feature``.
82
+ - **An unacknowledged unsubscribe no longer leaves the topic marked
83
+ subscribed.** When the last callback for a topic was removed and the
84
+ broker's acknowledgement never came, the next subscribe to that topic
85
+ skipped SUBSCRIBE and got no messages if the broker had dropped it.
86
+ SUBSCRIBE and UNSUBSCRIBE for a topic now run one at a time and the
87
+ topic is unmarked before UNSUBSCRIBE is sent, so the next subscribe
88
+ sends SUBSCRIBE, a subscribe made meanwhile keeps its callback, and a
89
+ late SUBACK cannot mark a topic that was unsubscribed.
90
+ - **Unsubscribing while disconnected removes the callback.** The
91
+ ``unsubscribe_*`` methods returned without doing anything while the
92
+ connection was down, so the callback came back with the next
93
+ resubscribe. The callback is now removed locally and nothing is sent
94
+ to the broker.
95
+ - **A failed or cancelled resubscribe no longer loses subscriptions.**
96
+ After a reconnect without a session, a ``TimeoutError`` or a rejected
97
+ SUBACK stopped the resubscribe, and every topic not yet restored was
98
+ dropped for good. Topics that fail are tried twice more before the
99
+ queued commands are sent, and any still not restored stay registered
100
+ for the next reconnect. An unsubscribe made while they wait, or while
101
+ a SUBACK is pending, is kept. A rejected SUBACK in ``subscribe()``
102
+ removes the handler it added, but not a callback that was already
103
+ registered. ``resubscribe_all()`` returns whether every subscription
104
+ was restored.
105
+ - **Reconnects sign with the current AWS credentials.** The connection
106
+ read the credentials once, so the SDK's own reconnect kept signing
107
+ with expired ones after a token refresh. It now reads the current
108
+ ones on each attempt.
109
+ - **A late interruption or resumption from a replaced or closed
110
+ connection is ignored**, instead of changing the connected state.
111
+ - **Topic logs in the MQTT connection redact the MAC address**, and a
112
+ failed subscribe logs the error type rather than its text, which
113
+ carries the topic.
114
+ - **A periodic request task that stops on an unexpected error logs
115
+ it**, instead of ending silently while still listed as running.
116
+ - **``time_to_ready(..., inlet_measured=True)`` no longer says
117
+ ``inlet_temp_modelled`` when the scenario has draws.** The mode walks
118
+ now get the flag, so the heat pump's walk's reasons no longer bring
119
+ the reason back into the answer.
120
+
5
121
  Version 9.13.0 (2026-11-06)
6
122
  ===========================
7
123
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nwp500-python
3
- Version: 9.13.0
3
+ Version: 9.13.1
4
4
  Summary: A library for controlling Navien NWP500 Water Heaters via NaviLink
5
5
  Author-email: Emmanuel Levijarvi <emansl@gmail.com>
6
6
  License-Expression: MIT
@@ -17,7 +17,7 @@ Requires-Python: >=3.14
17
17
  Description-Content-Type: text/x-rst; charset=UTF-8
18
18
  License-File: LICENSE.txt
19
19
  Requires-Dist: aiohttp>=3.13.5
20
- Requires-Dist: awsiotsdk>=1.29.0
20
+ Requires-Dist: awscrt<0.39,>=0.36.1
21
21
  Requires-Dist: pydantic>=2.0.0
22
22
  Provides-Extra: cli
23
23
  Requires-Dist: click>=8.3.0; extra == "cli"
@@ -33,7 +33,7 @@ Quick Start
33
33
  diagnostics = MqttDiagnosticsCollector(enable_verbose_logging=True)
34
34
 
35
35
  # Connect with hardened config
36
- config = MqttConnectionConfig(keep_alive_secs=60) # Reduced from 1200
36
+ config = MqttConnectionConfig(keep_alive_secs=60)
37
37
  mqtt_client = NavienMqttClient(auth_client, config=config)
38
38
 
39
39
  # Hook events
@@ -164,8 +164,8 @@ Start with these hardened settings:
164
164
  from nwp500.mqtt_utils import MqttConnectionConfig
165
165
 
166
166
  config = MqttConnectionConfig(
167
- # 1. Reduce keep-alive (prevents NAT idle timeout)
168
- keep_alive_secs=60, # Reduced from 1200
167
+ # 1. Keep-alive below NAT idle timeouts (the default)
168
+ keep_alive_secs=60,
169
169
 
170
170
  # 2. Faster reconnection for transient failures
171
171
  initial_reconnect_delay=0.5, # 500ms (was 1s)
@@ -187,10 +187,10 @@ Start with these hardened settings:
187
187
  Why These Settings Matter
188
188
  --------------------------
189
189
 
190
- **Keep-Alive (1200s → 60s)**
191
- Most NAT devices timeout idle connections after 300-600 seconds. Default
192
- 1200s keep-alive allows NAT to timeout before keep-alive packets arrive,
193
- causing connection to die. At 60s, packets arrive before the timeout.
190
+ **Keep-Alive (60s, the default)**
191
+ Most NAT devices time out idle connections after 300-600 seconds. A 60s
192
+ keep-alive sends a ping well before that, and a dead connection is noticed
193
+ within about a minute and a half.
194
194
 
195
195
  **Faster Reconnection (1s → 0.5s)**
196
196
  Transient network glitches often recover in <1 second. Faster initial retry
@@ -489,8 +489,9 @@ and emits as the ``control_failed`` event for every device subscription
489
489
  ``failCode`` is 2, "DHW control interval exceeded": the previous DHW control (mode, temperature, power) was sent
490
490
  too recently, so wait and resend. The message names the client whose
491
491
  command failed, so ``event.own_command`` tells your rejections from
492
- another client's. A command that is neither acknowledged nor rejected was
493
- most likely never delivered: check the connection drop log above.
492
+ another client's. The acknowledgement is not sent for every command:
493
+ some commands apply with no reply, so read the device status to see
494
+ whether a command took effect.
494
495
 
495
496
  .. code-block:: python
496
497
 
@@ -502,6 +503,38 @@ most likely never delivered: check the connection drop log above.
502
503
 
503
504
  mqtt.on(MqttClientEvents.CONTROL_FAILED, on_control_failed)
504
505
 
506
+ What did the device get?
507
+ ------------------------
508
+
509
+ Set the ``nwp500.mqtt.payloads`` logger to DEBUG to log every payload
510
+ the client sends and receives, one line each:
511
+
512
+ .. code-block:: text
513
+
514
+ send #1910 cmd/52/navilink-REDACTED/ctrl {"clientID":"navien-client-1996271a","sessionID":"1763116258162",...}
515
+ recv cmd/52/navilink-REDACTED/navien-client-1996271a/res {"clientID":"navilink-REDACTED","sessionID":"1763116258162",...}
516
+
517
+ A sent line carries the MQTT packet id after ``#``. MAC addresses are
518
+ replaced with ``REDACTED``; ``clientID`` and ``sessionID`` are kept.
519
+ Every command carries its own ``sessionID``, and a reply carries the
520
+ ``sessionID`` of the command it answers, so search the log for a
521
+ command's ``sessionID`` to find its reply, or see that none came.
522
+ ``clientID`` names the client a message came from.
523
+
524
+ .. code-block:: python
525
+
526
+ import logging
527
+
528
+ logging.getLogger("nwp500.mqtt.payloads").setLevel(logging.DEBUG)
529
+
530
+ In Home Assistant:
531
+
532
+ .. code-block:: yaml
533
+
534
+ logger:
535
+ logs:
536
+ nwp500.mqtt.payloads: debug
537
+
505
538
  Integration Examples
506
539
  ====================
507
540
 
@@ -158,7 +158,7 @@ Basic Example
158
158
  config = MqttConnectionConfig(
159
159
  # Connection settings
160
160
  client_id="my-custom-client",
161
- keep_alive_secs=1200,
161
+ keep_alive_secs=60,
162
162
 
163
163
  # Enable features (see guides for details)
164
164
  auto_reconnect=True,
@@ -55,7 +55,7 @@ Core Dependencies
55
55
  The library requires:
56
56
 
57
57
  * ``aiohttp>=3.13.5``: Async HTTP client for REST API
58
- * ``awsiotsdk>=1.29.0``: AWS IoT SDK for MQTT
58
+ * ``awscrt>=0.36.1,<0.39``: AWS Common Runtime, for the MQTT connection
59
59
  * ``pydantic>=2.0.0``: Data validation and models
60
60
 
61
61
  Optional Dependencies
@@ -134,11 +134,11 @@ If you get SSL certificate errors:
134
134
  AWS IoT Connection Issues
135
135
  --------------------------
136
136
 
137
- The MQTT client requires the AWS IoT SDK:
137
+ The MQTT client requires the AWS Common Runtime:
138
138
 
139
139
  .. code-block:: bash
140
140
 
141
- pip install awsiotsdk>=1.29.0
141
+ pip install "awscrt>=0.36.1,<0.39"
142
142
 
143
143
  Upgrading
144
144
  =========
@@ -147,7 +147,7 @@ All MQTT messages are JSON with this structure:
147
147
 
148
148
  {
149
149
  "clientID": "navien-client-1996271a",
150
- "sessionID": "session-67890",
150
+ "sessionID": "1763116258162",
151
151
  "requestTopic": "cmd/52/navilink-04786332fca0/ctrl",
152
152
  "responseTopic": "cmd/52/navilink-04786332fca0/navien-client-1996271a/res",
153
153
  "protocolVersion": 2,
@@ -165,7 +165,8 @@ All MQTT messages are JSON with this structure:
165
165
  **Fields:**
166
166
 
167
167
  * ``clientID``: MQTT client identifier
168
- * ``sessionID``: Session identifier for tracking
168
+ * ``sessionID``: The send time in milliseconds, a new value per command.
169
+ The acknowledgement repeats it.
169
170
  * ``requestTopic``: Topic the request is published to
170
171
  * ``responseTopic``: Topic the reply should be published to
171
172
  * ``protocolVersion``: Protocol version (always 2)
@@ -931,7 +932,7 @@ Control commands and ``st`` are acknowledged on
931
932
 
932
933
  {
933
934
  "clientID": "navilink-04786332fca0",
934
- "sessionID": "session-67890",
935
+ "sessionID": "1763116258162",
935
936
  "requestTopic": "...",
936
937
  "responseTopic": "...",
937
938
  "response": {
@@ -21,7 +21,7 @@ classifiers = [
21
21
  dynamic = ["version"]
22
22
  dependencies = [
23
23
  "aiohttp>=3.13.5",
24
- "awsiotsdk>=1.29.0",
24
+ "awscrt>=0.36.1,<0.39",
25
25
  "pydantic>=2.0.0",
26
26
  ]
27
27
 
@@ -231,10 +231,6 @@ cache_dir = ".mypy_cache"
231
231
  module = "awscrt.*"
232
232
  ignore_missing_imports = true
233
233
 
234
- [[tool.mypy.overrides]]
235
- module = "awsiot.*"
236
- ignore_missing_imports = true
237
-
238
234
  [[tool.mypy.overrides]]
239
235
  module = "aiohttp.*"
240
236
  ignore_missing_imports = true
@@ -3,9 +3,16 @@ addopts =
3
3
  --cov nwp500 --cov-report term-missing
4
4
  --verbose
5
5
  norecursedirs =
6
- dist
6
+ .*
7
+ *.egg
8
+ _darcs
7
9
  build
8
- .tox
10
+ CVS
11
+ dist
12
+ node_modules
13
+ venv
14
+ {arch}
15
+ asyncio_default_fixture_loop_scope = function
9
16
  testpaths = tests
10
17
 
11
18
  [devpi:upload]
@@ -44,7 +51,6 @@ forbidden_modules =
44
51
  nwp500.sim_home_assistant
45
52
  aiohttp
46
53
  awscrt
47
- awsiot
48
54
  logging
49
55
 
50
56
  [importlinter:contract:calibrate-no-cli]
@@ -25,7 +25,7 @@ finally:
25
25
 
26
26
  # The exports load on first use (PEP 562), so a part of the library
27
27
  # imports without the others' dependencies: ``nwp500.sim`` needs neither
28
- # aiohttp nor awsiotsdk, which the clients load.
28
+ # aiohttp nor awscrt, which the clients load.
29
29
  if TYPE_CHECKING:
30
30
  from nwp500.api_client import (
31
31
  NavienAPIClient,
@@ -11,6 +11,7 @@ adapter maps.
11
11
 
12
12
  from __future__ import annotations
13
13
 
14
+ import contextlib
14
15
  from collections.abc import Callable
15
16
  from dataclasses import dataclass
16
17
  from typing import Any
@@ -155,6 +156,12 @@ class BleMqttAdapter:
155
156
  self._feature_callbacks.append(callback)
156
157
  return 0
157
158
 
159
+ async def unsubscribe_device_feature(
160
+ self, device: Any, callback: FeatureCallback
161
+ ) -> None:
162
+ with contextlib.suppress(ValueError):
163
+ self._feature_callbacks.remove(callback)
164
+
158
165
  async def request_device_info(self, device: Any) -> int:
159
166
  """Read the blocks and deliver a :class:`BleDeviceFeature`.
160
167
 
@@ -180,6 +187,12 @@ class BleMqttAdapter:
180
187
  self._status_callbacks.append(callback)
181
188
  return 0
182
189
 
190
+ async def unsubscribe_device_status(
191
+ self, device: Any, callback: StatusCallback
192
+ ) -> None:
193
+ with contextlib.suppress(ValueError):
194
+ self._status_callbacks.remove(callback)
195
+
183
196
  async def request_device_status(self, device: Any) -> int:
184
197
  """Deliver the next :class:`~nwp500.ble.RealTimeStatus` block.
185
198
 
@@ -59,9 +59,9 @@ async def _detect_unit_system(
59
59
  if not future.done():
60
60
  future.set_result(status)
61
61
 
62
- await mqtt.subscribe_device_status(device, _on_status)
63
- await mqtt.request_device_status(device)
64
62
  try:
63
+ await mqtt.subscribe_device_status(device, _on_status)
64
+ await mqtt.request_device_status(device)
65
65
  status = await asyncio.wait_for(future, timeout=5.0)
66
66
  if status.temperature_type == TemperatureType.CELSIUS:
67
67
  _logger.info("Auto-detected metric unit system from device")
@@ -73,6 +73,10 @@ async def _detect_unit_system(
73
73
  "Timed out detecting unit system, defaulting to us_customary"
74
74
  )
75
75
  return "us_customary"
76
+ finally:
77
+ await handlers.unsubscribe_quietly(
78
+ mqtt.unsubscribe_device_status, device, _on_status
79
+ )
76
80
 
77
81
 
78
82
  def _parse_filter_hours(
@@ -59,6 +59,9 @@ async def _wait_for_response(
59
59
  subscribe_func: Callable[
60
60
  [Device, Callable[[Any], None]], Coroutine[Any, Any, Any]
61
61
  ],
62
+ unsubscribe_func: Callable[
63
+ [Device, Callable[[Any], None]], Coroutine[Any, Any, Any]
64
+ ],
62
65
  device: Device,
63
66
  action_func: Callable[[], Coroutine[Any, Any, Any]],
64
67
  timeout: float = 10.0,
@@ -71,15 +74,40 @@ async def _wait_for_response(
71
74
  if not future.done():
72
75
  future.set_result(res)
73
76
 
74
- await subscribe_func(device, callback)
75
- _logger.info(f"Requesting {action_name}...")
76
- await action_func()
77
+ try:
78
+ # Inside the try: a subscribe cancelled while its acknowledgement
79
+ # is pending has already registered the callback.
80
+ await subscribe_func(device, callback)
81
+ _logger.info(f"Requesting {action_name}...")
82
+ await action_func()
83
+ try:
84
+ return await asyncio.wait_for(future, timeout=timeout)
85
+ except TimeoutError:
86
+ _logger.error(f"Timed out waiting for {action_name} response.")
87
+ raise
88
+ finally:
89
+ await unsubscribe_quietly(unsubscribe_func, device, callback)
90
+
91
+
92
+ async def unsubscribe_quietly(
93
+ unsubscribe_func: Callable[
94
+ [Device, Callable[[Any], None]], Coroutine[Any, Any, Any]
95
+ ],
96
+ device: Device,
97
+ callback: Callable[[Any], None],
98
+ ) -> None:
99
+ """Remove a reply callback without replacing the caller's outcome.
77
100
 
101
+ The callback is removed locally before the broker is asked; a failed
102
+ or unacknowledged broker unsubscribe is logged, so it can neither
103
+ hide a reply already received nor replace the request's own error.
104
+ """
78
105
  try:
79
- return await asyncio.wait_for(future, timeout=timeout)
80
- except TimeoutError:
81
- _logger.error(f"Timed out waiting for {action_name} response.")
82
- raise
106
+ await unsubscribe_func(device, callback)
107
+ except Exception as e:
108
+ _logger.warning(
109
+ "Failed to unsubscribe a reply callback: %s", type(e).__name__
110
+ )
83
111
 
84
112
 
85
113
  async def _handle_command_with_status_feedback(
@@ -94,6 +122,7 @@ async def _handle_command_with_status_feedback(
94
122
  try:
95
123
  status: Any = await _wait_for_response(
96
124
  mqtt.subscribe_device_status,
125
+ mqtt.unsubscribe_device_status,
97
126
  device,
98
127
  action_func,
99
128
  action_name=action_name,
@@ -124,6 +153,7 @@ async def get_controller_serial_number(
124
153
  try:
125
154
  feature: Any = await _wait_for_response(
126
155
  mqtt.subscribe_device_feature,
156
+ mqtt.unsubscribe_device_feature,
127
157
  device,
128
158
  lambda: mqtt.request_device_info(device),
129
159
  timeout=timeout,
@@ -155,6 +185,9 @@ async def _handle_info_request(
155
185
  subscribe_method: Callable[
156
186
  [Device, Callable[[Any], None]], Coroutine[Any, Any, Any]
157
187
  ],
188
+ unsubscribe_method: Callable[
189
+ [Device, Callable[[Any], None]], Coroutine[Any, Any, Any]
190
+ ],
158
191
  request_method: Callable[[Device], Coroutine[Any, Any, Any]],
159
192
  data_key: str,
160
193
  action_name: str,
@@ -166,6 +199,7 @@ async def _handle_info_request(
166
199
  if not raw:
167
200
  res = await _wait_for_response(
168
201
  subscribe_method,
202
+ unsubscribe_method,
169
203
  device,
170
204
  lambda: request_method(device),
171
205
  action_name=action_name,
@@ -199,6 +233,7 @@ async def handle_status_request(
199
233
  mqtt,
200
234
  device,
201
235
  mqtt.subscribe_device_status,
236
+ mqtt.unsubscribe_device_status,
202
237
  mqtt.request_device_status,
203
238
  "status",
204
239
  "device status",
@@ -215,6 +250,7 @@ async def handle_device_info_request(
215
250
  mqtt,
216
251
  device,
217
252
  mqtt.subscribe_device_feature,
253
+ mqtt.unsubscribe_device_feature,
218
254
  mqtt.request_device_info,
219
255
  "feature",
220
256
  "device information",
@@ -888,6 +924,7 @@ async def handle_get_energy_request(
888
924
  try:
889
925
  res: Any = await _wait_for_response(
890
926
  mqtt.subscribe_energy_usage,
927
+ mqtt.unsubscribe_energy_usage,
891
928
  device,
892
929
  lambda: mqtt.request_energy_usage(device, year, months),
893
930
  action_name="energy usage",
@@ -913,6 +950,7 @@ async def handle_get_energy_monthly_request(
913
950
  try:
914
951
  res: Any = await _wait_for_response(
915
952
  mqtt.subscribe_energy_usage_monthly,
953
+ mqtt.unsubscribe_energy_usage_monthly,
916
954
  device,
917
955
  lambda: mqtt.request_energy_usage_monthly(device, years),
918
956
  action_name="monthly energy usage",
@@ -937,6 +975,7 @@ async def handle_diagnostics_request(
937
975
  try:
938
976
  diagnostics: Any = await _wait_for_response(
939
977
  mqtt.subscribe_diagnostics,
978
+ mqtt.unsubscribe_diagnostics,
940
979
  device,
941
980
  lambda: mqtt.request_diagnostics(device),
942
981
  action_name="diagnostics",
@@ -962,6 +1001,7 @@ async def handle_firmware_download_info_request(
962
1001
  try:
963
1002
  info: Any = await _wait_for_response(
964
1003
  mqtt.subscribe_firmware_download_info,
1004
+ mqtt.unsubscribe_firmware_download_info,
965
1005
  device,
966
1006
  lambda: mqtt.request_firmware_download_info(device),
967
1007
  action_name="firmware download info",
@@ -986,6 +1026,7 @@ async def handle_get_recirculation_schedule_request(
986
1026
  try:
987
1027
  schedule: Any = await _wait_for_response(
988
1028
  mqtt.subscribe_recirculation_schedule_response,
1029
+ mqtt.unsubscribe_recirculation_schedule_response,
989
1030
  device,
990
1031
  lambda: mqtt.request_recirculation_schedule(device),
991
1032
  action_name="recirculation schedule",