nwp500-python 8.1.1__tar.gz → 8.1.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (201) hide show
  1. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/CHANGELOG.rst +1 -1
  2. {nwp500_python-8.1.1/src/nwp500_python.egg-info → nwp500_python-8.1.2}/PKG-INFO +1 -1
  3. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500/mqtt/client.py +37 -2
  4. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500/mqtt/periodic.py +0 -6
  5. {nwp500_python-8.1.1 → nwp500_python-8.1.2/src/nwp500_python.egg-info}/PKG-INFO +1 -1
  6. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500_python.egg-info/SOURCES.txt +1 -0
  7. nwp500_python-8.1.2/tests/test_mqtt_clean_session_resume.py +190 -0
  8. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/.coveragerc +0 -0
  9. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/.github/RESOLVING_PR_COMMENTS.md +0 -0
  10. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/.github/copilot-instructions.md +0 -0
  11. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/.github/workflows/ci.yml +0 -0
  12. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/.github/workflows/release.yml +0 -0
  13. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/.gitignore +0 -0
  14. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/.pre-commit-config.yaml +0 -0
  15. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/.readthedocs.yml +0 -0
  16. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/AUTHORS.rst +0 -0
  17. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/CONTRIBUTING.rst +0 -0
  18. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/LICENSE.txt +0 -0
  19. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/Makefile +0 -0
  20. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/README.rst +0 -0
  21. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/RELEASE.md +0 -0
  22. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/docs/Makefile +0 -0
  23. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/docs/_static/.gitignore +0 -0
  24. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/docs/conf.py +0 -0
  25. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/docs/explanation/advanced-features.rst +0 -0
  26. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/docs/explanation/architecture.rst +0 -0
  27. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/docs/explanation/index.rst +0 -0
  28. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/docs/how-to/authenticate.rst +0 -0
  29. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/docs/how-to/auto-recovery.rst +0 -0
  30. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/docs/how-to/diagnose-mqtt.rst +0 -0
  31. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/docs/how-to/home-assistant.rst +0 -0
  32. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/docs/how-to/index.rst +0 -0
  33. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/docs/how-to/maintenance.rst +0 -0
  34. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/docs/how-to/manage-units.rst +0 -0
  35. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/docs/how-to/monitor-status.rst +0 -0
  36. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/docs/how-to/optimize-tou.rst +0 -0
  37. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/docs/how-to/queue-commands.rst +0 -0
  38. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/docs/how-to/schedule-operation.rst +0 -0
  39. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/docs/how-to/track-energy.rst +0 -0
  40. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/docs/index.rst +0 -0
  41. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/docs/openapi.yaml +0 -0
  42. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/docs/project/authors.rst +0 -0
  43. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/docs/project/changelog.rst +0 -0
  44. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/docs/project/contributing.rst +0 -0
  45. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/docs/project/history.rst +0 -0
  46. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/docs/project/license.rst +0 -0
  47. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/docs/reference/configuration.rst +0 -0
  48. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/docs/reference/enumerations.rst +0 -0
  49. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/docs/reference/index.rst +0 -0
  50. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/docs/reference/installation.rst +0 -0
  51. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/docs/reference/protocol/data_conversions.rst +0 -0
  52. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/docs/reference/protocol/device_features.rst +0 -0
  53. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/docs/reference/protocol/device_status.rst +0 -0
  54. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/docs/reference/protocol/error_codes.rst +0 -0
  55. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/docs/reference/protocol/mqtt_protocol.rst +0 -0
  56. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/docs/reference/protocol/quick_reference.rst +0 -0
  57. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/docs/reference/protocol/rest_api.rst +0 -0
  58. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/docs/reference/python_api/api_client.rst +0 -0
  59. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/docs/reference/python_api/auth_client.rst +0 -0
  60. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/docs/reference/python_api/cli.rst +0 -0
  61. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/docs/reference/python_api/events.rst +0 -0
  62. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/docs/reference/python_api/exceptions.rst +0 -0
  63. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/docs/reference/python_api/models.rst +0 -0
  64. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/docs/reference/python_api/mqtt_client.rst +0 -0
  65. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/docs/requirements.txt +0 -0
  66. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/docs/tutorials/getting-started.rst +0 -0
  67. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/examples/.ruff.toml +0 -0
  68. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/examples/README.md +0 -0
  69. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/examples/advanced/air_filter_reset.py +0 -0
  70. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/examples/advanced/anti_legionella.py +0 -0
  71. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/examples/advanced/auto_recovery.py +0 -0
  72. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/examples/advanced/combined_callbacks.py +0 -0
  73. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/examples/advanced/demand_response.py +0 -0
  74. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/examples/advanced/device_capabilities.py +0 -0
  75. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/examples/advanced/device_status_debug.py +0 -0
  76. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/examples/advanced/energy_analytics.py +0 -0
  77. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/examples/advanced/error_code_demo.py +0 -0
  78. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/examples/advanced/firmware_payload_capture.py +0 -0
  79. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/examples/advanced/mqtt_diagnostics.py +0 -0
  80. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/examples/advanced/power_control.py +0 -0
  81. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/examples/advanced/recirculation_control.py +0 -0
  82. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/examples/advanced/reconnection_demo.py +0 -0
  83. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/examples/advanced/reservation_schedule.py +0 -0
  84. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/examples/advanced/simple_auto_recovery.py +0 -0
  85. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/examples/advanced/token_restoration.py +0 -0
  86. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/examples/advanced/tou_openei.py +0 -0
  87. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/examples/advanced/tou_schedule.py +0 -0
  88. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/examples/advanced/water_reservation.py +0 -0
  89. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/examples/beginner/01_authentication.py +0 -0
  90. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/examples/beginner/02_list_devices.py +0 -0
  91. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/examples/beginner/03_get_status.py +0 -0
  92. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/examples/beginner/04_set_temperature.py +0 -0
  93. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/examples/intermediate/advanced_auth_patterns.py +0 -0
  94. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/examples/intermediate/command_queue.py +0 -0
  95. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/examples/intermediate/device_status_callback.py +0 -0
  96. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/examples/intermediate/error_handling.py +0 -0
  97. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/examples/intermediate/event_driven_control.py +0 -0
  98. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/examples/intermediate/improved_auth.py +0 -0
  99. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/examples/intermediate/legacy_auth_constructor.py +0 -0
  100. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/examples/intermediate/mqtt_realtime_monitoring.py +0 -0
  101. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/examples/intermediate/periodic_requests.py +0 -0
  102. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/examples/intermediate/set_mode.py +0 -0
  103. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/examples/intermediate/vacation_mode.py +0 -0
  104. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/examples/mask.py +0 -0
  105. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/examples/testing/periodic_device_info.py +0 -0
  106. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/examples/testing/simple_periodic_info.py +0 -0
  107. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/examples/testing/test_api_client.py +0 -0
  108. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/examples/testing/test_mqtt_connection.py +0 -0
  109. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/examples/testing/test_mqtt_messaging.py +0 -0
  110. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/examples/testing/test_periodic_minimal.py +0 -0
  111. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/pyproject.toml +0 -0
  112. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/scripts/README.md +0 -0
  113. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/scripts/bump_version.py +0 -0
  114. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/scripts/diagnose_mqtt_connection.py +0 -0
  115. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/scripts/extract_changelog.py +0 -0
  116. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/scripts/format.py +0 -0
  117. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/scripts/lint.py +0 -0
  118. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/scripts/setup-dev.py +0 -0
  119. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/scripts/validate_version.py +0 -0
  120. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/setup.cfg +0 -0
  121. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/setup.py +0 -0
  122. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500/__init__.py +0 -0
  123. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500/_base.py +0 -0
  124. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500/api_client.py +0 -0
  125. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500/auth.py +0 -0
  126. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500/cli/__init__.py +0 -0
  127. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500/cli/__main__.py +0 -0
  128. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500/cli/commands.py +0 -0
  129. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500/cli/handlers.py +0 -0
  130. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500/cli/monitoring.py +0 -0
  131. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500/cli/output_formatters.py +0 -0
  132. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500/cli/rich_output.py +0 -0
  133. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500/cli/token_storage.py +0 -0
  134. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500/command_decorators.py +0 -0
  135. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500/config.py +0 -0
  136. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500/converters.py +0 -0
  137. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500/device_capabilities.py +0 -0
  138. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500/device_info_cache.py +0 -0
  139. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500/encoding.py +0 -0
  140. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500/enums.py +0 -0
  141. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500/events.py +0 -0
  142. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500/exceptions.py +0 -0
  143. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500/factory.py +0 -0
  144. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500/field_factory.py +0 -0
  145. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500/models/__init__.py +0 -0
  146. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500/models/_converters.py +0 -0
  147. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500/models/device.py +0 -0
  148. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500/models/energy.py +0 -0
  149. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500/models/feature.py +0 -0
  150. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500/models/mqtt_models.py +0 -0
  151. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500/models/schedule.py +0 -0
  152. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500/models/status.py +0 -0
  153. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500/models/tou.py +0 -0
  154. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500/mqtt/__init__.py +0 -0
  155. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500/mqtt/command_queue.py +0 -0
  156. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500/mqtt/connection.py +0 -0
  157. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500/mqtt/control.py +0 -0
  158. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500/mqtt/diagnostics.py +0 -0
  159. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500/mqtt/reconnection.py +0 -0
  160. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500/mqtt/state_tracker.py +0 -0
  161. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500/mqtt/subscriptions.py +0 -0
  162. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500/mqtt/utils.py +0 -0
  163. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500/mqtt_events.py +0 -0
  164. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500/openei.py +0 -0
  165. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500/py.typed +0 -0
  166. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500/reservations.py +0 -0
  167. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500/temperature.py +0 -0
  168. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500/topic_builder.py +0 -0
  169. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500/unit_system.py +0 -0
  170. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500/utils.py +0 -0
  171. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500_python.egg-info/dependency_links.txt +0 -0
  172. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500_python.egg-info/entry_points.txt +0 -0
  173. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500_python.egg-info/not-zip-safe +0 -0
  174. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500_python.egg-info/requires.txt +0 -0
  175. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500_python.egg-info/top_level.txt +0 -0
  176. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/tests/conftest.py +0 -0
  177. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/tests/test_api_helpers.py +0 -0
  178. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/tests/test_auth.py +0 -0
  179. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/tests/test_bug_fixes.py +0 -0
  180. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/tests/test_cli_basic.py +0 -0
  181. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/tests/test_cli_commands.py +0 -0
  182. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/tests/test_command_decorators.py +0 -0
  183. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/tests/test_command_queue.py +0 -0
  184. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/tests/test_device_capabilities.py +0 -0
  185. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/tests/test_device_info_cache.py +0 -0
  186. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/tests/test_events.py +0 -0
  187. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/tests/test_exceptions.py +0 -0
  188. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/tests/test_model_converters.py +0 -0
  189. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/tests/test_models.py +0 -0
  190. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/tests/test_mqtt_client_init.py +0 -0
  191. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/tests/test_mqtt_hypothesis.py +0 -0
  192. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/tests/test_mqtt_reconnection.py +0 -0
  193. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/tests/test_mqtt_reconnection_storm.py +0 -0
  194. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/tests/test_multi_device.py +0 -0
  195. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/tests/test_openei.py +0 -0
  196. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/tests/test_reservations.py +0 -0
  197. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/tests/test_temperature_converters.py +0 -0
  198. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/tests/test_tou_api.py +0 -0
  199. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/tests/test_unit_switching.py +0 -0
  200. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/tests/test_utils.py +0 -0
  201. {nwp500_python-8.1.1 → nwp500_python-8.1.2}/tox.ini +0 -0
@@ -5,7 +5,7 @@ Changelog
5
5
  Unreleased
6
6
  ==========
7
7
 
8
- Version 8.1.1 (2026-05-18)
8
+ Version 8.1.2 (2026-05-25)
9
9
  ==========================
10
10
 
11
11
  Version 8.1.0 (2026-05-16)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nwp500-python
3
- Version: 8.1.1
3
+ Version: 8.1.2
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
@@ -364,8 +364,20 @@ class NavienMqttClient(EventEmitter):
364
364
  )
365
365
  )
366
366
 
367
- # Send any queued commands
368
- if self.config.enable_command_queue and self._command_queue:
367
+ # When the broker starts a clean session (session_present=False), all
368
+ # previous subscriptions have been dropped server-side. We must
369
+ # re-establish them before any device data can flow. This covers the
370
+ # common case where the AWS IoT SDK auto-reconnects internally before
371
+ # the MqttReconnectionHandler fires its own reconnect path — in that
372
+ # scenario the reconnect handler sees _connected==True and exits early,
373
+ # so resubscribe_all() would never be called without this block.
374
+ #
375
+ # When session_present=False, we must resubscribe before sending queued
376
+ # commands to ensure subscriptions are restored before device responses
377
+ # are processed. Use a composite coroutine to enforce ordering.
378
+ if not session_present and self._subscription_manager:
379
+ self._schedule_coroutine(self._handle_clean_session_resume())
380
+ elif self.config.enable_command_queue and self._command_queue:
369
381
  self._schedule_coroutine(self._send_queued_commands_internal())
370
382
 
371
383
  async def _send_queued_commands_internal(self) -> None:
@@ -377,6 +389,29 @@ class NavienMqttClient(EventEmitter):
377
389
  self._connection_manager.publish, lambda: self._connected
378
390
  )
379
391
 
392
+ async def _handle_clean_session_resume(self) -> None:
393
+ """
394
+ Handle clean session reconnection with ordered resubscription.
395
+
396
+ When session_present=False (clean session), the broker has dropped all
397
+ subscriptions. This method ensures subscriptions are restored BEFORE
398
+ sending any queued commands, preventing commands from being processed
399
+ before their subscriptions are re-established.
400
+ """
401
+ if not self._subscription_manager or not self._connection_manager:
402
+ return
403
+
404
+ if not self._connection_manager.connection:
405
+ return
406
+
407
+ self._subscription_manager.update_connection(
408
+ self._connection_manager.connection
409
+ )
410
+ await self._subscription_manager.resubscribe_all()
411
+
412
+ if self.config.enable_command_queue and self._command_queue:
413
+ await self._send_queued_commands_internal()
414
+
380
415
  async def _active_reconnect(self) -> None:
381
416
  """
382
417
  Actively trigger a reconnection attempt.
@@ -173,12 +173,6 @@ class MqttPeriodicRequestManager:
173
173
  await self._request_device_info(device)
174
174
  elif request_type == PeriodicRequestType.DEVICE_STATUS:
175
175
  await self._request_device_status(device)
176
- else:
177
- _logger.error(
178
- "Unknown periodic request type: %s",
179
- request_type,
180
- )
181
- break
182
176
 
183
177
  _logger.debug(
184
178
  "Sent periodic %s request for %s",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nwp500-python
3
- Version: 8.1.1
3
+ Version: 8.1.2
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
@@ -186,6 +186,7 @@ tests/test_events.py
186
186
  tests/test_exceptions.py
187
187
  tests/test_model_converters.py
188
188
  tests/test_models.py
189
+ tests/test_mqtt_clean_session_resume.py
189
190
  tests/test_mqtt_client_init.py
190
191
  tests/test_mqtt_hypothesis.py
191
192
  tests/test_mqtt_reconnection.py
@@ -0,0 +1,190 @@
1
+ """Tests for MQTT client clean session reconnection handling."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from unittest.mock import AsyncMock, MagicMock, patch
6
+
7
+ import pytest
8
+
9
+ from nwp500.auth import AuthenticationResponse, AuthTokens, UserInfo
10
+ from nwp500.mqtt import NavienMqttClient
11
+
12
+
13
+ @pytest.fixture
14
+ def auth_client_with_valid_tokens():
15
+ """Create an auth client with valid tokens."""
16
+ from nwp500.auth import NavienAuthClient
17
+
18
+ auth_client = NavienAuthClient("test@example.com", "password")
19
+ valid_tokens = AuthTokens(
20
+ id_token="test_id",
21
+ access_token="test_access",
22
+ refresh_token="test_refresh",
23
+ authentication_expires_in=3600,
24
+ access_key_id="test_key_id",
25
+ secret_key="test_secret_key",
26
+ session_token="test_session",
27
+ authorization_expires_in=3600,
28
+ )
29
+ auth_client._auth_response = AuthenticationResponse(
30
+ user_info=UserInfo(user_first_name="Test", user_last_name="User"),
31
+ tokens=valid_tokens,
32
+ )
33
+ return auth_client
34
+
35
+
36
+ class TestMqttCleanSessionResume:
37
+ """Tests for clean session (session_present=False) reconnection handling."""
38
+
39
+ @pytest.mark.asyncio(loop_scope="function")
40
+ async def test_on_connection_resumed_with_clean_session_resubscribes(
41
+ self, auth_client_with_valid_tokens
42
+ ):
43
+ """Resubscribe when session_present=False on connection resume."""
44
+ client = NavienMqttClient(auth_client_with_valid_tokens)
45
+
46
+ # Mock the components
47
+ mock_subscription_manager = AsyncMock()
48
+ mock_subscription_manager.resubscribe_all = AsyncMock()
49
+ client._subscription_manager = mock_subscription_manager
50
+
51
+ mock_connection_manager = MagicMock()
52
+ mock_connection = MagicMock()
53
+ mock_connection_manager.connection = mock_connection
54
+ client._connection_manager = mock_connection_manager
55
+
56
+ # Mock the event emitter and diagnostics
57
+ client.emit = AsyncMock()
58
+ client._diagnostics = MagicMock()
59
+ client._diagnostics.record_connection_success = AsyncMock()
60
+
61
+ # Call with session_present=False (clean session)
62
+ client._on_connection_resumed_internal(
63
+ connection=mock_connection, return_code=0, session_present=False
64
+ )
65
+
66
+ # Give the scheduled coroutine time to run
67
+ import asyncio
68
+
69
+ await asyncio.sleep(0.1)
70
+
71
+ # Verify resubscribe_all was called
72
+ mock_subscription_manager.update_connection.assert_called_once_with(
73
+ mock_connection
74
+ )
75
+ # The resubscribe should be scheduled via _schedule_coroutine
76
+ # We need to wait for it or check the internal state
77
+
78
+ @pytest.mark.asyncio(loop_scope="function")
79
+ async def test_resubscribe_before_queued_commands(
80
+ self, auth_client_with_valid_tokens
81
+ ):
82
+ """Resubscribe completes before queued commands are sent."""
83
+ client = NavienMqttClient(auth_client_with_valid_tokens)
84
+
85
+ # Track call order
86
+ call_order = []
87
+
88
+ # Mock the components
89
+ mock_subscription_manager = MagicMock()
90
+ mock_subscription_manager.resubscribe_all = AsyncMock(
91
+ side_effect=lambda: call_order.append("resubscribe")
92
+ )
93
+ client._subscription_manager = mock_subscription_manager
94
+
95
+ mock_connection_manager = MagicMock()
96
+ mock_connection = MagicMock()
97
+ mock_connection_manager.connection = mock_connection
98
+ client._connection_manager = mock_connection_manager
99
+
100
+ # Mock command queue
101
+ client._command_queue = AsyncMock()
102
+ client.config.enable_command_queue = True
103
+
104
+ # Mock send_queued_commands to track it's called after resubscribe
105
+ original_send = client._send_queued_commands_internal
106
+
107
+ async def mock_send():
108
+ call_order.append("send_queued")
109
+ await original_send()
110
+
111
+ client._send_queued_commands_internal = mock_send
112
+
113
+ # Call the method
114
+ await client._handle_clean_session_resume()
115
+
116
+ # Verify subscription manager was updated with connection
117
+ mock_subscription_manager.update_connection.assert_called_once_with(
118
+ mock_connection
119
+ )
120
+
121
+ # Verify resubscribe was called before queued commands
122
+ assert call_order == ["resubscribe", "send_queued"]
123
+
124
+ @pytest.mark.asyncio(loop_scope="function")
125
+ async def test_skip_when_no_subscription_manager(
126
+ self, auth_client_with_valid_tokens
127
+ ):
128
+ """Return early if subscription_manager is None."""
129
+ client = NavienMqttClient(auth_client_with_valid_tokens)
130
+ client._subscription_manager = None
131
+
132
+ # Should not raise
133
+ await client._handle_clean_session_resume()
134
+
135
+ @pytest.mark.asyncio(loop_scope="function")
136
+ async def test_handle_clean_session_resume_skips_when_no_connection(
137
+ self, auth_client_with_valid_tokens
138
+ ):
139
+ """Return early if connection is None."""
140
+ client = NavienMqttClient(auth_client_with_valid_tokens)
141
+
142
+ mock_subscription_manager = MagicMock()
143
+ client._subscription_manager = mock_subscription_manager
144
+
145
+ mock_connection_manager = MagicMock()
146
+ mock_connection_manager.connection = None
147
+ client._connection_manager = mock_connection_manager
148
+
149
+ # Should not raise
150
+ await client._handle_clean_session_resume()
151
+
152
+ # Should not try to update connection
153
+ mock_subscription_manager.update_connection.assert_not_called()
154
+
155
+ @pytest.mark.asyncio(loop_scope="function")
156
+ async def test_on_connection_resumed_with_session_sends_queued_commands(
157
+ self, auth_client_with_valid_tokens
158
+ ):
159
+ """Send queued commands normally when session_present=True."""
160
+ client = NavienMqttClient(auth_client_with_valid_tokens)
161
+
162
+ # Mock the components
163
+ mock_command_queue = AsyncMock()
164
+ client._command_queue = mock_command_queue
165
+ client.config.enable_command_queue = True
166
+
167
+ # Mock the event emitter and diagnostics
168
+ client.emit = AsyncMock()
169
+ client._diagnostics = MagicMock()
170
+ client._diagnostics.record_connection_success = AsyncMock()
171
+
172
+ # Mock connection
173
+ mock_connection = MagicMock()
174
+
175
+ # Patch _send_queued_commands_internal to track if called
176
+ with patch.object(
177
+ client, "_send_queued_commands_internal", new_callable=AsyncMock
178
+ ):
179
+ # Call with session_present=True (session resumed)
180
+ client._on_connection_resumed_internal(
181
+ connection=mock_connection, return_code=0, session_present=True
182
+ )
183
+
184
+ # Give the scheduled coroutine time to run
185
+ import asyncio
186
+
187
+ await asyncio.sleep(0.1)
188
+
189
+ # Verify send_queued_commands_internal was scheduled
190
+ # (it will be called through _schedule_coroutine)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes