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
27 changes: 27 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,33 @@
Changelog
=========

Version 7.2.1 (2025-12-25)
==========================

Added
-----
- **CLI Command**: New ``device-info`` command to retrieve basic device information from REST API

.. code-block:: bash

# Get basic device info (DeviceInfo model)
python3 -m nwp500.cli device-info
python3 -m nwp500.cli device-info --raw

- **InstallType Enum**: New ``InstallType`` enum for device installation classification

- ``InstallType.RESIDENTIAL`` = "R" - Residential use
- ``InstallType.COMMERCIAL`` = "C" - Commercial use
- Used in ``DeviceInfo.install_type`` field with automatic validation

- **String Enum Validator**: New ``str_enum_validator()`` converter for string-based enums

Changed
-------
- **DeviceInfo Model**: ``install_type`` field now uses ``InstallType`` enum instead of plain string
- **CLI Documentation**: Clarified distinction between ``info`` (DeviceFeature via MQTT) and ``device-info`` (DeviceInfo via REST API) commands


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

Expand Down
5 changes: 4 additions & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -104,9 +104,12 @@ The library includes a command line interface for monitoring and controlling you
# Get current device status
python3 -m nwp500.cli status

# Get device information and firmware
# Get device information and firmware (via MQTT - DeviceFeature)
python3 -m nwp500.cli info

# Get basic device info from REST API (DeviceInfo)
python3 -m nwp500.cli device-info

# Get controller serial number
python3 -m nwp500.cli serial

Expand Down
2 changes: 2 additions & 0 deletions src/nwp500/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
ErrorCode,
FilterChange,
HeatSource,
InstallType,
OnOffFlag,
Operation,
RecirculationMode,
Expand Down Expand Up @@ -161,6 +162,7 @@
"ErrorCode",
"FilterChange",
"HeatSource",
"InstallType",
"OnOffFlag",
"Operation",
"RecirculationMode",
Expand Down
2 changes: 2 additions & 0 deletions src/nwp500/cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from .handlers import (
handle_device_info_request,
handle_get_controller_serial_request,
handle_get_device_info_rest,
handle_get_energy_request,
handle_get_reservations_request,
handle_get_tou_request,
Expand All @@ -28,6 +29,7 @@
# Command handlers
"handle_device_info_request",
"handle_get_controller_serial_request",
"handle_get_device_info_rest",
"handle_get_energy_request",
"handle_get_reservations_request",
"handle_get_tou_request",
Expand Down
19 changes: 19 additions & 0 deletions src/nwp500/cli/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,25 @@ async def info(mqtt: NavienMqttClient, device: Any, raw: bool) -> None:
await handlers.handle_device_info_request(mqtt, device, raw)


@cli.command() # type: ignore[attr-defined]
@click.option("--raw", is_flag=True, help="Output raw JSON response")
@async_command
async def device_info(
mqtt: NavienMqttClient,
device: Any,
raw: bool,
) -> None:
"""Show basic device info from REST API (DeviceInfo model)."""
ctx = click.get_current_context()
api = None
if ctx and hasattr(ctx, "obj") and ctx.obj is not None:
api = ctx.obj.get("api")
if api:
await handlers.handle_get_device_info_rest(api, device, raw)
else:
_logger.error("API client not available")


@cli.command() # type: ignore[attr-defined]
@click.option("--raw", is_flag=True, help="Output raw JSON response")
@async_command
Expand Down
33 changes: 33 additions & 0 deletions src/nwp500/cli/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
DeviceFeature,
DeviceStatus,
EnergyUsageResponse,
NavienAPIClient,
NavienMqttClient,
)
from nwp500.exceptions import (
Expand Down Expand Up @@ -347,6 +348,38 @@
_logger.error("Timed out updating reservations.")


async def handle_get_device_info_rest(
api_client: NavienAPIClient, device: Device, raw: bool = False
) -> None:
"""Get device info from REST API (minimal DeviceInfo fields)."""
try:
device_info_obj = await api_client.get_device_info(
mac_address=device.device_info.mac_address,
additional_value=device.device_info.additional_value,
)
if raw:
print_json(device_info_obj.model_dump())
else:
# Print simple formatted output
info = device_info_obj.device_info

install_type_str = info.install_type if info.install_type else "N/A"
print("\n=== Device Info (REST API) ===\n")
print(f"Device Name: {info.device_name}")
mac_display = (
redact_serial(info.mac_address) if info.mac_address else "N/A"
)
print(f"MAC Address: {mac_display}")
print(f"Device Type: {info.device_type}")
print(f"Home Seq: {info.home_seq}")
print(f"Connected: {info.connected}")
print(f"Install Type: {install_type_str}")
print(f"Additional Value: {info.additional_value or 'N/A'}")
print()
except Exception as e:
_logger.error(f"Error fetching device info: {e}")


async def handle_get_tou_request(
mqtt: NavienMqttClient, device: Device, api_client: Any
) -> None:
Expand Down
32 changes: 32 additions & 0 deletions src/nwp500/converters.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
"device_bool_from_python",
"tou_override_to_python",
"div_10",
"enum_validator",
"str_enum_validator",
]


Expand Down Expand Up @@ -131,3 +133,33 @@ def validate(value: Any) -> Any:
return enum_class(int(value))

return validate


def str_enum_validator(enum_class: type[Any]) -> Callable[[Any], Any]:
"""Create a validator for converting string to str-based Enum.

Args:
enum_class: The str Enum class to validate against.

Returns:
A validator function compatible with Pydantic BeforeValidator.

Example:
>>> from enum import Enum
>>> class Status(str, Enum):
... ACTIVE = "A"
... INACTIVE = "I"
>>> validator = str_enum_validator(Status)
>>> validator("A")
<Status.ACTIVE: 'A'>
"""

def validate(value: Any) -> Any:
"""Validate and convert value to enum."""
if isinstance(value, enum_class):
return value
if isinstance(value, str):
return enum_class(value)
return enum_class(str(value))

return validate
18 changes: 17 additions & 1 deletion src/nwp500/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
See docs/protocol/quick_reference.rst for comprehensive protocol details.
"""

from enum import IntEnum
from enum import Enum, IntEnum

# ============================================================================
# Status Value Enumerations
Expand Down Expand Up @@ -239,6 +239,17 @@ class VolumeCode(IntEnum):
VOLUME_80 = 3 # NWP500-80: 80-gallon (302.8 liters) tank capacity


class InstallType(str, Enum):
"""Installation type classification.

Indicates whether the device is installed for residential or commercial use.
This affects warranty terms and service requirements.
"""

RESIDENTIAL = "R" # Residential use
COMMERCIAL = "C" # Commercial use


class UnitType(IntEnum):
"""Navien device/unit model types."""

Expand Down Expand Up @@ -434,6 +445,11 @@ class FirmwareType(IntEnum):
VolumeCode.VOLUME_80: "80 gallons",
}

INSTALL_TYPE_TEXT = {
InstallType.RESIDENTIAL: "Residential",
InstallType.COMMERCIAL: "Commercial",
}


# ============================================================================
# Error Code Enumerations
Expand Down