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.
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/CHANGELOG.rst +1 -1
- {nwp500_python-8.1.1/src/nwp500_python.egg-info → nwp500_python-8.1.2}/PKG-INFO +1 -1
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500/mqtt/client.py +37 -2
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500/mqtt/periodic.py +0 -6
- {nwp500_python-8.1.1 → nwp500_python-8.1.2/src/nwp500_python.egg-info}/PKG-INFO +1 -1
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500_python.egg-info/SOURCES.txt +1 -0
- nwp500_python-8.1.2/tests/test_mqtt_clean_session_resume.py +190 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/.coveragerc +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/.github/RESOLVING_PR_COMMENTS.md +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/.github/copilot-instructions.md +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/.github/workflows/ci.yml +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/.github/workflows/release.yml +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/.gitignore +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/.pre-commit-config.yaml +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/.readthedocs.yml +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/AUTHORS.rst +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/CONTRIBUTING.rst +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/LICENSE.txt +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/Makefile +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/README.rst +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/RELEASE.md +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/docs/Makefile +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/docs/_static/.gitignore +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/docs/conf.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/docs/explanation/advanced-features.rst +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/docs/explanation/architecture.rst +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/docs/explanation/index.rst +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/docs/how-to/authenticate.rst +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/docs/how-to/auto-recovery.rst +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/docs/how-to/diagnose-mqtt.rst +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/docs/how-to/home-assistant.rst +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/docs/how-to/index.rst +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/docs/how-to/maintenance.rst +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/docs/how-to/manage-units.rst +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/docs/how-to/monitor-status.rst +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/docs/how-to/optimize-tou.rst +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/docs/how-to/queue-commands.rst +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/docs/how-to/schedule-operation.rst +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/docs/how-to/track-energy.rst +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/docs/index.rst +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/docs/openapi.yaml +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/docs/project/authors.rst +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/docs/project/changelog.rst +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/docs/project/contributing.rst +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/docs/project/history.rst +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/docs/project/license.rst +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/docs/reference/configuration.rst +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/docs/reference/enumerations.rst +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/docs/reference/index.rst +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/docs/reference/installation.rst +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/docs/reference/protocol/data_conversions.rst +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/docs/reference/protocol/device_features.rst +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/docs/reference/protocol/device_status.rst +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/docs/reference/protocol/error_codes.rst +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/docs/reference/protocol/mqtt_protocol.rst +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/docs/reference/protocol/quick_reference.rst +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/docs/reference/protocol/rest_api.rst +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/docs/reference/python_api/api_client.rst +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/docs/reference/python_api/auth_client.rst +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/docs/reference/python_api/cli.rst +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/docs/reference/python_api/events.rst +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/docs/reference/python_api/exceptions.rst +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/docs/reference/python_api/models.rst +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/docs/reference/python_api/mqtt_client.rst +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/docs/requirements.txt +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/docs/tutorials/getting-started.rst +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/examples/.ruff.toml +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/examples/README.md +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/examples/advanced/air_filter_reset.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/examples/advanced/anti_legionella.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/examples/advanced/auto_recovery.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/examples/advanced/combined_callbacks.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/examples/advanced/demand_response.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/examples/advanced/device_capabilities.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/examples/advanced/device_status_debug.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/examples/advanced/energy_analytics.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/examples/advanced/error_code_demo.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/examples/advanced/firmware_payload_capture.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/examples/advanced/mqtt_diagnostics.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/examples/advanced/power_control.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/examples/advanced/recirculation_control.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/examples/advanced/reconnection_demo.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/examples/advanced/reservation_schedule.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/examples/advanced/simple_auto_recovery.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/examples/advanced/token_restoration.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/examples/advanced/tou_openei.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/examples/advanced/tou_schedule.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/examples/advanced/water_reservation.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/examples/beginner/01_authentication.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/examples/beginner/02_list_devices.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/examples/beginner/03_get_status.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/examples/beginner/04_set_temperature.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/examples/intermediate/advanced_auth_patterns.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/examples/intermediate/command_queue.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/examples/intermediate/device_status_callback.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/examples/intermediate/error_handling.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/examples/intermediate/event_driven_control.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/examples/intermediate/improved_auth.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/examples/intermediate/legacy_auth_constructor.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/examples/intermediate/mqtt_realtime_monitoring.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/examples/intermediate/periodic_requests.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/examples/intermediate/set_mode.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/examples/intermediate/vacation_mode.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/examples/mask.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/examples/testing/periodic_device_info.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/examples/testing/simple_periodic_info.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/examples/testing/test_api_client.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/examples/testing/test_mqtt_connection.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/examples/testing/test_mqtt_messaging.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/examples/testing/test_periodic_minimal.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/pyproject.toml +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/scripts/README.md +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/scripts/bump_version.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/scripts/diagnose_mqtt_connection.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/scripts/extract_changelog.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/scripts/format.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/scripts/lint.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/scripts/setup-dev.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/scripts/validate_version.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/setup.cfg +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/setup.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500/__init__.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500/_base.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500/api_client.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500/auth.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500/cli/__init__.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500/cli/__main__.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500/cli/commands.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500/cli/handlers.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500/cli/monitoring.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500/cli/output_formatters.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500/cli/rich_output.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500/cli/token_storage.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500/command_decorators.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500/config.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500/converters.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500/device_capabilities.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500/device_info_cache.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500/encoding.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500/enums.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500/events.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500/exceptions.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500/factory.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500/field_factory.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500/models/__init__.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500/models/_converters.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500/models/device.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500/models/energy.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500/models/feature.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500/models/mqtt_models.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500/models/schedule.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500/models/status.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500/models/tou.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500/mqtt/__init__.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500/mqtt/command_queue.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500/mqtt/connection.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500/mqtt/control.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500/mqtt/diagnostics.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500/mqtt/reconnection.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500/mqtt/state_tracker.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500/mqtt/subscriptions.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500/mqtt/utils.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500/mqtt_events.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500/openei.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500/py.typed +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500/reservations.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500/temperature.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500/topic_builder.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500/unit_system.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500/utils.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500_python.egg-info/dependency_links.txt +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500_python.egg-info/entry_points.txt +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500_python.egg-info/not-zip-safe +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500_python.egg-info/requires.txt +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/src/nwp500_python.egg-info/top_level.txt +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/tests/conftest.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/tests/test_api_helpers.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/tests/test_auth.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/tests/test_bug_fixes.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/tests/test_cli_basic.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/tests/test_cli_commands.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/tests/test_command_decorators.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/tests/test_command_queue.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/tests/test_device_capabilities.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/tests/test_device_info_cache.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/tests/test_events.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/tests/test_exceptions.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/tests/test_model_converters.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/tests/test_models.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/tests/test_mqtt_client_init.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/tests/test_mqtt_hypothesis.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/tests/test_mqtt_reconnection.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/tests/test_mqtt_reconnection_storm.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/tests/test_multi_device.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/tests/test_openei.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/tests/test_reservations.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/tests/test_temperature_converters.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/tests/test_tou_api.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/tests/test_unit_switching.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/tests/test_utils.py +0 -0
- {nwp500_python-8.1.1 → nwp500_python-8.1.2}/tox.ini +0 -0
|
@@ -364,8 +364,20 @@ class NavienMqttClient(EventEmitter):
|
|
|
364
364
|
)
|
|
365
365
|
)
|
|
366
366
|
|
|
367
|
-
#
|
|
368
|
-
|
|
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",
|
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
File without changes
|
{nwp500_python-8.1.1 → nwp500_python-8.1.2}/examples/intermediate/legacy_auth_constructor.py
RENAMED
|
File without changes
|
{nwp500_python-8.1.1 → nwp500_python-8.1.2}/examples/intermediate/mqtt_realtime_monitoring.py
RENAMED
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|