Skip to content

feat: Add device capability system and advanced control commands#52

Merged
eman merged 28 commits intomainfrom
feature/device-capabilities-and-advanced-controls
Dec 23, 2025
Merged

feat: Add device capability system and advanced control commands#52
eman merged 28 commits intomainfrom
feature/device-capabilities-and-advanced-controls

Conversation

@eman
Copy link
Copy Markdown
Owner

@eman eman commented Dec 20, 2025

  • Implement device capability checking with DeviceCapabilityChecker class
  • Add device info caching with configurable update intervals via DeviceInfoCache
  • Introduce @requires_capability decorator for automatic capability validation
  • Add DeviceCapabilityError exception for unsupported features

Advanced MQTT Control Commands:

  • Demand response: enable_demand_response(), disable_demand_response()
  • Air filter maintenance: reset_air_filter()
  • Vacation mode: set_vacation_days()
  • Water program reservations: configure_reservation_water_program()
  • Recirculation control: set_recirculation_mode(), configure_recirculation_schedule(), trigger_recirculation_hot_button()

Enhancements:

  • CLI with new diagnostics capabilities
  • Documentation for device control and capabilities
  • Example scripts for new features
  • Type hint improvements for optional parameters
  • Export new classes and decorators from main package

Bug Fixes & Improvements:

  • Reduced boilerplate in models with field factory functions (temperature_field, signal_strength_field, energy_field, power_field)
  • Fixed missing TOU status validator and corrected mixing valve alias
  • Improved CLI status output formatting with numeric values rounded to one decimal place
  • Removed sensitive data logging (MAC addresses) from log statements to address security scanning concerns
  • Fixed all linting and type checking issues for CI compatibility

- Implement device capability checking with DeviceCapabilityChecker class
- Add device info caching with configurable update intervals via DeviceInfoCache
- Introduce @requires_capability decorator for automatic capability validation
- Add DeviceCapabilityError exception for unsupported features

Advanced MQTT Control Commands:
- Demand response: enable_demand_response(), disable_demand_response()
- Air filter maintenance: reset_air_filter()
- Vacation mode: set_vacation_days()
- Water program reservations: configure_reservation_water_program()
- Recirculation control: set_recirculation_mode(), configure_recirculation_schedule(), trigger_recirculation_hot_button()

Enhancements:
- CLI with new diagnostics capabilities
- Documentation for device control and capabilities
- Example scripts for new features
- Type hint improvements for optional parameters
- Export new classes and decorators from main package
eman added 3 commits December 19, 2025 18:22
- Remove sensitive device identifiers (MAC addresses) from log messages
- Addresses GitHub Advanced Security code scanning alerts about clear-text logging of sensitive data
- Fixes 7 security code scanning issues in command_decorators.py, mqtt_client.py, and mqtt_subscriptions.py
- Maintains all functionality while improving security posture
- Remove examples/mqtt_diagnostics_output from version control
- Add examples/mqtt_diagnostics_output/ to .gitignore to prevent future tracking
@eman eman requested a review from Copilot December 20, 2025 02:29
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR implements a comprehensive device capability system for the nwp500 library, enabling automatic validation of device features before executing MQTT control commands. The changes introduce three core components: a DeviceCapabilityChecker for mapping features to capabilities, a DeviceInfoCache for managing device feature data with configurable TTL, and a @requires_capability decorator for automatic validation. Additionally, the PR adds advanced control commands for demand response, air filter maintenance, vacation mode, and recirculation pump control, all with automatic capability checking.

Key Changes:

  • Device capability framework with automatic validation and caching (30-minute default TTL)
  • Seven new advanced MQTT control commands with automatic capability verification
  • Enhanced CLI with new diagnostic and control options
  • Comprehensive documentation including new device_control.rst guide and example scripts

Reviewed changes

Copilot reviewed 28 out of 29 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
src/nwp500/device_capabilities.py Core capability checker using mapping-based validation
src/nwp500/device_info_cache.py Async cache for device features with TTL expiration
src/nwp500/command_decorators.py @requires_capability decorator with auto-request fallback
src/nwp500/mqtt_device_control.py Seven new control commands with capability decorators
src/nwp500/mqtt_client.py Integration of cache and auto-request infrastructure
src/nwp500/mqtt_subscriptions.py Auto-caching of device features on receipt
src/nwp500/mqtt_connection.py Type hint fix for payload parameter
src/nwp500/models.py New fields for recirculation and DHW control precision
src/nwp500/enums.py DHWControlTypeFlag enum for temperature control granularity
src/nwp500/exceptions.py DeviceCapabilityError for unsupported features
src/nwp500/api_client.py Type hint improvements and None value filtering
src/nwp500/init.py Export new capability classes and decorators
src/nwp500/cli/main.py Seven new CLI commands with early capability checking
src/nwp500/cli/commands.py Handler functions for new control commands
examples/*.py Four example scripts demonstrating new features
docs/python_api/device_control.rst Complete control API reference (1188 lines)
docs/python_api/mqtt_client.rst Updated to reference capability checking
docs/python_api/exceptions.rst DeviceCapabilityError documentation
docs/protocol/mqtt_protocol.rst Corrected command codes and added new commands

…tures

- Add 14 tests for DeviceCapabilityChecker
- Add 25 tests for DeviceInfoCache async caching operations
- Add 12 tests for requires_capability decorator
- Total: 51 new tests with 100%, 97%, and 100% code coverage respectively

Tests cover:
- Capability detection and validation
- Cache expiration and concurrent access
- Decorator behavior with supported/unsupported features
- Auto-request of device info when not cached
- Proper exception handling and logging
eman added 7 commits December 19, 2025 18:40
Security fixes:
- Remove sensitive device MAC logging from device_info_cache.py
- Use TYPE_CHECKING import for DeviceInfoCache type hint in mqtt_subscriptions.py

Type safety improvements:
- Add TypedDict for CachedDeviceInfo and CacheInfoResult in device_info_cache.py
- Fix type annotation for clean_params and clean_json_data in api_client.py
- Remove unnecessary cast() usage in api_client.py

Validation:
- Add max_value=365 to vacation days validation in mqtt_device_control.py

Code quality:
- Refactor duplicate device info request handlers into shared helper function
- Fix error message formatting in cli/__main__.py
- Reorder MQTT protocol table for logical flow
- Clarify temperature formula documentation in data_conversions.rst
Changed _ensure_device_info_cached to ensure_device_info_cached to indicate
this is a public API method that can be called from CLI and other external code,
not just an internal method for control commands.

This addresses the review comment about accessing protected methods.
Added upper bound check to validate that vacation days are between 1-365.
Previously only checked for days <= 0, but didn't enforce the 365-day maximum
recommended in the docstring.

This addresses the code review comment about enforcing the max_value constraint.
Changed error message from single long line to implicit string concatenation
pattern used elsewhere in the file to stay within 80-character line limit.

This addresses the review comment about consistency with other similar messages.
Addresses PR #52 review feedback:
- ✓ Line 127: Using public ensure_device_info_cached() method (commit f0edcca)
- ✓ Line 300: Using implicit string concatenation (commit 55e6650)
- ✓ Line 199: Refactored with shared _request_device_info_common() helper
- ✓ All linting checks pass
- ✓ All type checks pass
- ✓ All tests pass (210/210)
…requests, and fix parsing regressions

- Consolidated device control methods and removed mirrored interfaces in NavienMqttClient.
- Introduced MqttTopicBuilder and improved MqttDeviceController command dispatch.
- Unified periodic request methods into a single start_periodic_requests API.
- Fixed regressions in CLI, documentation, and 40+ examples to match the new API.
- Enhanced DeviceFeature model with AvailabilityFlag and CapabilityFlag for accurate protocol parsing.
- Improved NavienAPIClient logging and NavienAuthClient token refresh logic.
…ility

- Added redact_mac and redact_serial helpers to mqtt_utils.
- Masked MAC addresses and topics in core MQTT and decorated logs.
- Refactored CLI status and info handlers to shared common logic.
- Standardized CLI 'no action' error message onto a single line.
- Reordered Device Info/Status commands in protocol docs for better flow.
- Clarified temperature conversion math in documentation.
eman added 5 commits December 19, 2025 22:08
- Remove clear-text printing of access_token and refresh_token in examples
- Replace with safe examples that print user names instead
- Add comments noting tokens should not be printed in production
- Fixes CodeQL alert: Clear-text logging of sensitive information
…tors

- Remove f-string interpolation of mac address in warning log
- Remove unused import of redact_mac
- Remove unused mac variable assignment
- Fixes CodeQL alert: Clear-text logging of sensitive data
- Remove f-string interpolation of mac address in TimeoutError log
- Remove unused import of redact_mac
- Fixes CodeQL alert: Clear-text logging of sensitive data
…ntrol

- Remove f-string interpolation of mac address in info log
- Remove unused import of redact_mac
- Fixes CodeQL alert: Clear-text logging of sensitive data
@eman eman requested a review from Copilot December 20, 2025 06:29
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 74 out of 75 changed files in this pull request and generated 4 comments.

eman added 2 commits December 19, 2025 23:10
- Fix type annotations in models._convert_enums_to_names for dict/list return type
- Convert IntEnum device_type values to strings for MQTT topic builders
- Add proper type casting in api_client._make_request for ClientSession
- Create async wrapper for ensure_device_info_cached callback in mqtt_client
- Use cast() for method return type in mqtt_client._delegate_subscription
- Import DeviceFeature and EnergyUsageResponse in cli/commands
- Fix energy request to use subscribe_energy_usage instead of subscribe_device
- Break long import lines to comply with 80-character limit
- All 209 tests passing, linting and type checking clean
- Create field_factory.py module with reusable field creation functions
- Add temperature_field(), signal_strength_field(), energy_field(), power_field()
- Refactor ~30 field definitions in DeviceStatus and DeviceFeature
- Reduces models.py from 1290 to 1143 lines (147 lines saved)
- Eliminates repetitive json_schema_extra definitions
- Maintains backward compatibility - all 209 tests passing
- Improves maintainability by centralizing metadata configuration

NET IMPACT:
- Added: field_factory.py (150 lines)
- Removed: 147 lines from models.py
- Net change: -3 LOC, but significantly improved readability
@eman eman requested a review from Copilot December 20, 2025 08:15
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 75 out of 76 changed files in this pull request and generated 7 comments.

Comments suppressed due to low confidence (1)

tests/test_command_decorators.py:1

  • Using lambda with noqa disables the linting rule that suggests using def instead. While lambda is convenient for tests, using a proper function definition improves readability and avoids the need for noqa comments. Consider: def custom_check(f): return True
"""Tests for command decorators."""

…resses

The security scanner detects sensitive variables being used in string
interpolation before redaction. Store the redacted value in an intermediate
variable before logging to prevent this detection.
@eman eman requested a review from Copilot December 22, 2025 21:52
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 76 out of 77 changed files in this pull request and generated 8 comments.

@staticmethod
def _convert_enums_to_names(
data: Any, visited: set[int | None] = None
data: Any, visited: set[int] | None = None
Copy link

Copilot AI Dec 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The docstring comment about 'Object IDs are never None' is incorrect. The parameter annotation shows visited can be None on initial call. Update docstring to clarify that None indicates uninitialized/first call.

Copilot uses AI. Check for mistakes.
import csv
import json
import logging
from calendar import month_name
Copy link

Copilot AI Dec 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The month_name import is used without bounds checking in line 100. Consider adding validation to ensure month_data.month is in valid range (1-12) before indexing.

Copilot uses AI. Check for mistakes.
Comment on lines +119 to +120
print(" python -m nwp500.cli --configure-water-program")
print(" python -m nwp500.cli --configure-water-program --status")
Copy link

Copilot AI Dec 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The CLI command examples reference deprecated command-line syntax. According to the CLI refactor in __main__.py, commands now use subcommands (e.g., water-program). Update to reflect new syntax.

Suggested change
print(" python -m nwp500.cli --configure-water-program")
print(" python -m nwp500.cli --configure-water-program --status")
print(" python -m nwp500 water-program")
print(" python -m nwp500 water-program --status")

Copilot uses AI. Check for mistakes.
Comment on lines +119 to +121
print(" python -m nwp500.cli --set-vacation-days 7")
print(" python -m nwp500.cli --set-vacation-days 14 --status")
print(" python -m nwp500.cli --set-vacation-days 21 --status")
Copy link

Copilot AI Dec 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CLI command examples use old flag-based syntax. Update to new subcommand syntax: python -m nwp500.cli vacation 7

Suggested change
print(" python -m nwp500.cli --set-vacation-days 7")
print(" python -m nwp500.cli --set-vacation-days 14 --status")
print(" python -m nwp500.cli --set-vacation-days 21 --status")
print(" python -m nwp500.cli vacation 7")
print(" python -m nwp500.cli vacation 14 --status")
print(" python -m nwp500.cli vacation 21 --status")

Copilot uses AI. Check for mistakes.
Comment on lines +161 to +163
print(" python -m nwp500.cli --set-recirculation-mode 1")
print(" python -m nwp500.cli --recirculation-hot-button")
print(" python -m nwp500.cli --set-recirculation-mode 2 --status")
Copy link

Copilot AI Dec 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CLI examples use deprecated flag-based syntax. Update to new subcommand syntax: python -m nwp500.cli recirc 1 and python -m nwp500.cli hot-button

Suggested change
print(" python -m nwp500.cli --set-recirculation-mode 1")
print(" python -m nwp500.cli --recirculation-hot-button")
print(" python -m nwp500.cli --set-recirculation-mode 2 --status")
print(" python -m nwp500.cli recirc 1")
print(" python -m nwp500.cli hot-button")
print(" python -m nwp500.cli recirc 2 --status")

Copilot uses AI. Check for mistakes.
Comment on lines +138 to +140
print(" python -m nwp500.cli --enable-demand-response")
print(" python -m nwp500.cli --disable-demand-response")
print(" python -m nwp500.cli --enable-demand-response --status")
Copy link

Copilot AI Dec 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CLI examples use old syntax. Update to new subcommand format: python -m nwp500.cli dr enable and python -m nwp500.cli dr disable

Suggested change
print(" python -m nwp500.cli --enable-demand-response")
print(" python -m nwp500.cli --disable-demand-response")
print(" python -m nwp500.cli --enable-demand-response --status")
print(" python -m nwp500.cli dr enable")
print(" python -m nwp500.cli dr disable")
print(" python -m nwp500.cli dr enable --status")

Copilot uses AI. Check for mistakes.
Comment on lines +123 to +124
print(" python -m nwp500.cli --reset-air-filter")
print(" python -m nwp500.cli --reset-air-filter --status")
Copy link

Copilot AI Dec 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CLI examples use deprecated flag syntax. Update to new subcommand syntax: python -m nwp500.cli reset-filter

Suggested change
print(" python -m nwp500.cli --reset-air-filter")
print(" python -m nwp500.cli --reset-air-filter --status")
print(" python -m nwp500.cli reset-filter")
print(" python -m nwp500.cli reset-filter --status")

Copilot uses AI. Check for mistakes.
Comment on lines +137 to +147
filtered_params: dict[str, Any] = {}
for k, v in params.items():
if v is not None:
filtered_params[k] = v
clean_params = filtered_params
if json_data:
filtered_json: dict[str, Any] = {}
for k, v in json_data.items():
if v is not None:
filtered_json[k] = v
clean_json_data = filtered_json
Copy link

Copilot AI Dec 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use dictionary comprehension for more concise and Pythonic filtering: filtered_params = {k: v for k, v in params.items() if v is not None}

Suggested change
filtered_params: dict[str, Any] = {}
for k, v in params.items():
if v is not None:
filtered_params[k] = v
clean_params = filtered_params
if json_data:
filtered_json: dict[str, Any] = {}
for k, v in json_data.items():
if v is not None:
filtered_json[k] = v
clean_json_data = filtered_json
clean_params = {k: v for k, v in params.items() if v is not None}
if json_data:
clean_json_data = {
k: v for k, v in json_data.items() if v is not None
}

Copilot uses AI. Check for mistakes.
eman added 2 commits December 22, 2025 14:12
- Fix models.py docstring: clarify None means uninitialized/first call
- Improve output_formatters.py: add lower bound check for month validation (1-12)
- Refactor api_client.py: use dictionary comprehensions instead of loops
- Reorganize mqtt_client.py: redact MAC address earlier to prevent clear-text logging
- Update example files: reflect new CLI subcommand syntax (vacation, recirc, dr, reset-filter, water-program)
- Update CHANGELOG.rst with comprehensive v7.1.0 release notes
  - Document all device capabilities and advanced control features
  - Document CLI documentation updates and examples
  - Document model refactoring and security improvements
  - Consolidate all 25+ commits since v7.0.1

- Update CLI documentation (docs/python_api/cli.rst)
  - Complete rewrite for subcommand-based interface
  - Add 18+ command reference documentation
  - Add 8+ practical usage examples
  - Add troubleshooting and best practices sections

- Update README.rst
  - Replace old flag-based CLI documentation with subcommand examples
  - Add examples for all major commands

- Update example scripts
  - power_control_example.py: Use new CLI subcommand syntax
  - set_mode_example.py: Use new CLI subcommand syntax
  - set_dhw_temperature_example.py: Use new CLI subcommand syntax

- Update protocol documentation
  - firmware_tracking.rst: Update old CLI reference to new syntax

All checks passing: linting, type checking, tests
@eman eman merged commit 1fa35ac into main Dec 23, 2025
7 checks passed
@eman eman deleted the feature/device-capabilities-and-advanced-controls branch January 26, 2026 22:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants