Skip to content

Commit 478c624

Browse files
authored
Merge pull request #56 from eman/feat/device-info-command
Add device-info CLI command and InstallType enum
2 parents 96aa1cc + fbdd306 commit 478c624

8 files changed

Lines changed: 136 additions & 2 deletions

File tree

CHANGELOG.rst

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,33 @@
22
Changelog
33
=========
44

5+
Version 7.2.1 (2025-12-25)
6+
==========================
7+
8+
Added
9+
-----
10+
- **CLI Command**: New ``device-info`` command to retrieve basic device information from REST API
11+
12+
.. code-block:: bash
13+
14+
# Get basic device info (DeviceInfo model)
15+
python3 -m nwp500.cli device-info
16+
python3 -m nwp500.cli device-info --raw
17+
18+
- **InstallType Enum**: New ``InstallType`` enum for device installation classification
19+
20+
- ``InstallType.RESIDENTIAL`` = "R" - Residential use
21+
- ``InstallType.COMMERCIAL`` = "C" - Commercial use
22+
- Used in ``DeviceInfo.install_type`` field with automatic validation
23+
24+
- **String Enum Validator**: New ``str_enum_validator()`` converter for string-based enums
25+
26+
Changed
27+
-------
28+
- **DeviceInfo Model**: ``install_type`` field now uses ``InstallType`` enum instead of plain string
29+
- **CLI Documentation**: Clarified distinction between ``info`` (DeviceFeature via MQTT) and ``device-info`` (DeviceInfo via REST API) commands
30+
31+
532
Version 7.2.0 (2025-12-23)
633
==========================
734

README.rst

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,9 +104,12 @@ The library includes a command line interface for monitoring and controlling you
104104
# Get current device status
105105
python3 -m nwp500.cli status
106106
107-
# Get device information and firmware
107+
# Get device information and firmware (via MQTT - DeviceFeature)
108108
python3 -m nwp500.cli info
109109
110+
# Get basic device info from REST API (DeviceInfo)
111+
python3 -m nwp500.cli device-info
112+
110113
# Get controller serial number
111114
python3 -m nwp500.cli serial
112115

src/nwp500/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
ErrorCode,
5858
FilterChange,
5959
HeatSource,
60+
InstallType,
6061
OnOffFlag,
6162
Operation,
6263
RecirculationMode,
@@ -161,6 +162,7 @@
161162
"ErrorCode",
162163
"FilterChange",
163164
"HeatSource",
165+
"InstallType",
164166
"OnOffFlag",
165167
"Operation",
166168
"RecirculationMode",

src/nwp500/cli/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from .handlers import (
55
handle_device_info_request,
66
handle_get_controller_serial_request,
7+
handle_get_device_info_rest,
78
handle_get_energy_request,
89
handle_get_reservations_request,
910
handle_get_tou_request,
@@ -28,6 +29,7 @@
2829
# Command handlers
2930
"handle_device_info_request",
3031
"handle_get_controller_serial_request",
32+
"handle_get_device_info_rest",
3133
"handle_get_energy_request",
3234
"handle_get_reservations_request",
3335
"handle_get_tou_request",

src/nwp500/cli/__main__.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,25 @@ async def info(mqtt: NavienMqttClient, device: Any, raw: bool) -> None:
149149
await handlers.handle_device_info_request(mqtt, device, raw)
150150

151151

152+
@cli.command() # type: ignore[attr-defined]
153+
@click.option("--raw", is_flag=True, help="Output raw JSON response")
154+
@async_command
155+
async def device_info(
156+
mqtt: NavienMqttClient,
157+
device: Any,
158+
raw: bool,
159+
) -> None:
160+
"""Show basic device info from REST API (DeviceInfo model)."""
161+
ctx = click.get_current_context()
162+
api = None
163+
if ctx and hasattr(ctx, "obj") and ctx.obj is not None:
164+
api = ctx.obj.get("api")
165+
if api:
166+
await handlers.handle_get_device_info_rest(api, device, raw)
167+
else:
168+
_logger.error("API client not available")
169+
170+
152171
@cli.command() # type: ignore[attr-defined]
153172
@click.option("--raw", is_flag=True, help="Output raw JSON response")
154173
@async_command

src/nwp500/cli/handlers.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
DeviceFeature,
1212
DeviceStatus,
1313
EnergyUsageResponse,
14+
NavienAPIClient,
1415
NavienMqttClient,
1516
)
1617
from nwp500.exceptions import (
@@ -347,6 +348,38 @@ def raw_callback(topic: str, message: dict[str, Any]) -> None:
347348
_logger.error("Timed out updating reservations.")
348349

349350

351+
async def handle_get_device_info_rest(
352+
api_client: NavienAPIClient, device: Device, raw: bool = False
353+
) -> None:
354+
"""Get device info from REST API (minimal DeviceInfo fields)."""
355+
try:
356+
device_info_obj = await api_client.get_device_info(
357+
mac_address=device.device_info.mac_address,
358+
additional_value=device.device_info.additional_value,
359+
)
360+
if raw:
361+
print_json(device_info_obj.model_dump())
362+
else:
363+
# Print simple formatted output
364+
info = device_info_obj.device_info
365+
366+
install_type_str = info.install_type if info.install_type else "N/A"
367+
print("\n=== Device Info (REST API) ===\n")
368+
print(f"Device Name: {info.device_name}")
369+
mac_display = (
370+
redact_serial(info.mac_address) if info.mac_address else "N/A"
371+
)
372+
print(f"MAC Address: {mac_display}")
373+
print(f"Device Type: {info.device_type}")
374+
print(f"Home Seq: {info.home_seq}")
375+
print(f"Connected: {info.connected}")
376+
print(f"Install Type: {install_type_str}")
377+
print(f"Additional Value: {info.additional_value or 'N/A'}")
378+
print()
379+
except Exception as e:
380+
_logger.error(f"Error fetching device info: {e}")
381+
382+
350383
async def handle_get_tou_request(
351384
mqtt: NavienMqttClient, device: Device, api_client: Any
352385
) -> None:

src/nwp500/converters.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
"device_bool_from_python",
1616
"tou_override_to_python",
1717
"div_10",
18+
"enum_validator",
19+
"str_enum_validator",
1820
]
1921

2022

@@ -131,3 +133,33 @@ def validate(value: Any) -> Any:
131133
return enum_class(int(value))
132134

133135
return validate
136+
137+
138+
def str_enum_validator(enum_class: type[Any]) -> Callable[[Any], Any]:
139+
"""Create a validator for converting string to str-based Enum.
140+
141+
Args:
142+
enum_class: The str Enum class to validate against.
143+
144+
Returns:
145+
A validator function compatible with Pydantic BeforeValidator.
146+
147+
Example:
148+
>>> from enum import Enum
149+
>>> class Status(str, Enum):
150+
... ACTIVE = "A"
151+
... INACTIVE = "I"
152+
>>> validator = str_enum_validator(Status)
153+
>>> validator("A")
154+
<Status.ACTIVE: 'A'>
155+
"""
156+
157+
def validate(value: Any) -> Any:
158+
"""Validate and convert value to enum."""
159+
if isinstance(value, enum_class):
160+
return value
161+
if isinstance(value, str):
162+
return enum_class(value)
163+
return enum_class(str(value))
164+
165+
return validate

src/nwp500/enums.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
See docs/protocol/quick_reference.rst for comprehensive protocol details.
88
"""
99

10-
from enum import IntEnum
10+
from enum import Enum, IntEnum
1111

1212
# ============================================================================
1313
# Status Value Enumerations
@@ -239,6 +239,17 @@ class VolumeCode(IntEnum):
239239
VOLUME_80 = 3 # NWP500-80: 80-gallon (302.8 liters) tank capacity
240240

241241

242+
class InstallType(str, Enum):
243+
"""Installation type classification.
244+
245+
Indicates whether the device is installed for residential or commercial use.
246+
This affects warranty terms and service requirements.
247+
"""
248+
249+
RESIDENTIAL = "R" # Residential use
250+
COMMERCIAL = "C" # Commercial use
251+
252+
242253
class UnitType(IntEnum):
243254
"""Navien device/unit model types."""
244255

@@ -434,6 +445,11 @@ class FirmwareType(IntEnum):
434445
VolumeCode.VOLUME_80: "80 gallons",
435446
}
436447

448+
INSTALL_TYPE_TEXT = {
449+
InstallType.RESIDENTIAL: "Residential",
450+
InstallType.COMMERCIAL: "Commercial",
451+
}
452+
437453

438454
# ============================================================================
439455
# Error Code Enumerations

0 commit comments

Comments
 (0)