Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,15 @@ The version bump script:

**Validation**: Run `make validate-version` to check for version-related mistakes before committing.

### Review Comments

When working on pull requests, use the GitHub CLI to access review comments:
- **List review comments**: `gh pr review-comment list --repo=<owner>/<repo>`
- **Get PR details with reviews**: `gh pr view <number> --repo=<owner>/<repo>`
- **Apply review feedback** before final submission

This ensures you can address all feedback from code reviewers systematically.

### Before Committing Changes
Always run these checks before finalizing changes to ensure your code will pass CI:
1. **Linting**: `make ci-lint` - Ensures code style matches CI requirements
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.10'
python-version: '3.13'

- name: Install tox
run: |
Expand Down Expand Up @@ -68,7 +68,7 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.10'
python-version: '3.13'

- name: Install build dependencies
run: |
Expand Down
41 changes: 41 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,47 @@
Changelog
=========

Version 7.2.0 (2025-12-23)
==========================

Added
-----

- **Dynamic Unit Extraction in CLI**: CLI output now dynamically extracts units from DeviceStatus model metadata
- New helper functions: ``_get_unit_suffix()`` and ``_add_numeric_item()``
- Eliminates hardcoded units in output formatter
- Single source of truth: model metadata drives CLI display

Fixed
-----

- **Superheat Temperature Units**: Target and Current SuperHeat now correctly display in °F instead of °C
- Both fields use ``DeciCelsiusToF`` conversion, now properly reflected in CLI output
- Fields were displaying inconsistent units compared to all other temperature readings

- **Missing CLI Output Units**: Multiple fields now display with proper units from model metadata
- ``current_dhw_flow_rate``: Now shows GPM unit
- ``total_energy_capacity``: Now shows Wh unit
- ``available_energy_capacity``: Now shows Wh unit
- ``dr_override_status``: Now shows hours unit
- ``vacation_day_setting``: Now shows days unit
- ``vacation_day_elapsed``: Now shows days unit
- ``anti_legionella_period``: Fixed to show days unit (was incorrectly h)
- ``wifi_rssi``: Now shows dBm unit

- **Invalid MQTT Topic Filter**: Fixed ``reservations get`` command subscription topic
- Changed invalid topic pattern ``cmd/52/navilink-+/#`` to valid ``cmd/52/+/#``
- AWS IoT Core MQTT does not support wildcards within topic segments
- Affected: ``handle_get_reservations_request()`` in commands.py

Changed
-------

- **CLI Output Formatter Refactoring**: Restructured ``print_device_status()`` to use dynamic unit extraction
- Reduced code duplication by ~400 lines
- Improved maintainability: field additions automatically get correct units
- No breaking changes to CLI output format or behavior

Version 7.1.0 (2025-12-22)
==========================

Expand Down
63 changes: 63 additions & 0 deletions CONTRIBUTING.rst
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,69 @@ The project is organized around a modular architecture:

Design principles include separation of concerns, clear public APIs, and extensibility for new device features. Contributors should review the `src/nwp500/` directory for module structure and refer to the documentation for details on each component.

Naming Conventions
------------------

Consistent naming conventions improve code readability and maintainability. Follow these patterns when adding new classes, methods, and exceptions.

**Classes**

- **Client classes**: Use ``Navien<Component>Client`` format for main client classes (e.g., ``NavienAuthClient``, ``NavienAPIClient``, ``NavienMqttClient``). This prefix clearly indicates these are the primary library clients.
- **Manager/Controller classes**: Use ``<Domain><Responsibility>`` format for classes managing specific functionality (e.g., ``MqttConnectionManager``, ``DeviceInfoCache``). Avoid Navien prefix for utility/internal classes.
- **Utility classes**: Use ``<Domain><Feature>`` or ``<Domain>Utilities`` format for helper classes (e.g., ``MqttDiagnostics``, ``DeviceCapabilityChecker``).

**Methods**

Follow these patterns for consistent method naming:

- **Getters**: Use ``get_<resource>()`` for single item retrieval (e.g., ``get_device_info()``, ``get_firmware_info()``)
- **Listers**: Use ``list_<resources>()`` for collection retrieval (e.g., ``list_devices()``)
- **Setters**: Use ``set_<field>(value)`` for direct assignment or ``configure_<feature>()`` for complex configuration (e.g., ``set_power()``, ``set_dhw_temperature()``)
- **Actions**: Use ``<action>_<resource>()`` format for operations that modify state (e.g., ``reset_filter()``, ``enable_anti_legionella()``)
- **Requesters**: Use ``request_<data>()`` for async data fetching from devices (e.g., ``request_device_status()``, ``request_device_info()``)

**Enums**

- **Enum names**: Use descriptive names that indicate the device state or protocol value (e.g., ``OnOffFlag``, ``CurrentOperationMode``, ``HeatSource``). These names should reflect the Navien protocol directly.
- **Enum values**: Document device protocol mappings in code comments. For example:

.. code-block:: python

class OnOffFlag(IntEnum):
"""Device on/off state per Navien protocol."""
OFF = 0 # 0 = False per Navien protocol
ON = 1 # 1 = True per Navien protocol

**Exceptions**

Follow a clear exception hierarchy with consistent naming:

- **Pattern**: ``<Domain><Situation>Error`` (e.g., ``MqttConnectionError``, ``AuthenticationError``, ``DeviceCapabilityError``)
- **Grouping**: Group related errors under a base class that consumers can catch for broad error handling:

- ``AuthenticationError`` - Base for all authentication failures (covers ``InvalidCredentialsError``, ``TokenExpiredError``, ``TokenRefreshError``)
- ``MqttError`` - Base for all MQTT operations (covers ``MqttConnectionError``, ``MqttNotConnectedError``, ``MqttPublishError``, etc.)
- ``ValidationError`` - Base for all validation failures (covers ``ParameterValidationError``, ``RangeValidationError``)
- ``DeviceError`` - Base for all device operations (covers ``DeviceNotFoundError``, ``DeviceOfflineError``, ``DeviceCapabilityError``, etc.)

- **Example usage**:

.. code-block:: python

try:
await mqtt_client.control.set_temperature(device, 150)
except MqttNotConnectedError:
# Handle specific case: not connected
print("Connect to device first")
except MqttError:
# Handle other MQTT errors
print("MQTT operation failed")
except RangeValidationError as e:
print(f"Invalid {e.field}: must be {e.min_value}-{e.max_value}")
except ValidationError:
# Handle other validation errors
print("Invalid parameter")

Submit an issue
---------------

Expand Down
28 changes: 28 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,34 @@ Basic Usage
# Change operation mode
await api_client.set_device_mode(device, "heat_pump")

For more detailed authentication information, see the `Authentication & Session Management <https://nwp500-python.readthedocs.io/en/latest/guides/authentication.html>`_ guide.

MQTT Real-Time Monitoring
--------------------------

Monitor your device in real-time using MQTT:

.. code-block:: python

from nwp500 import NavienAuthClient, NavienMqttClient

async with NavienAuthClient("your_email@example.com", "your_password") as auth_client:
# Create MQTT client
mqtt_client = NavienMqttClient(auth_client=auth_client)
await mqtt_client.connect()

# Subscribe to device status updates
def on_status(status):
print(f"Temperature: {status.dhw_temperature}°F")
print(f"Mode: {status.operation_mode}")

device = (await api_client.list_devices())[0]
await mqtt_client.subscribe_device_status(device, on_status)

# Keep the connection alive
await mqtt_client.wait()


Command Line Interface
======================

Expand Down
Loading