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
5 changes: 3 additions & 2 deletions src/nwp500/api_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ def __init__(
auth_client: NavienAuthClient,
base_url: str = API_BASE_URL,
session: aiohttp.ClientSession | None = None,
unit_system: Literal["metric", "imperial"] | None = None,
unit_system: Literal["metric", "us_customary"] | None = None,
):
"""
Initialize Navien API client.
Expand All @@ -63,7 +63,8 @@ def __init__(
provided)
unit_system: Preferred unit system:
- "metric": Celsius, LPM, Liters
- "imperial": Fahrenheit, GPM, Gallons
- "us_customary": Fahrenheit, GPM, Gallons

- None: Auto-detect from device (default)

Raises:
Expand Down
5 changes: 3 additions & 2 deletions src/nwp500/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,7 @@ def __init__(
session: aiohttp.ClientSession | None = None,
timeout: int = 30,
stored_tokens: AuthTokens | None = None,
unit_system: Literal["metric", "imperial"] | None = None,
unit_system: Literal["metric", "us_customary"] | None = None,
):
"""
Initialize the authentication client.
Expand All @@ -304,7 +304,8 @@ def __init__(
If provided and valid, skips initial sign_in.
unit_system: Preferred unit system:
- "metric": Celsius, LPM, Liters
- "imperial": Fahrenheit, GPM, Gallons
- "us_customary": Fahrenheit, GPM, Gallons

- None: Auto-detect from device (default)

Note:
Expand Down
4 changes: 2 additions & 2 deletions src/nwp500/cli/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,8 +129,8 @@ async def runner() -> int:
)
@click.option(
"--unit-system",
type=click.Choice(["metric", "imperial"], case_sensitive=False),
help="Unit system: metric (C/LPM/L) or imperial (F/GPM/gal)",
type=click.Choice(["metric", "us_customary"], case_sensitive=False),
help="Unit system: metric (C/LPM/L) or us_customary (F/GPM/gal)",
)
@click.option("-v", "--verbose", count=True, help="Increase verbosity")
@click.version_option(version=__version__)
Expand Down
27 changes: 14 additions & 13 deletions src/nwp500/mqtt/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
import logging
import uuid
from collections.abc import Callable
from typing import TYPE_CHECKING, Any, Literal, cast
from typing import TYPE_CHECKING, Any, cast

from awscrt import mqtt
from awscrt.exceptions import AwsCrtError
Expand All @@ -31,15 +31,7 @@
MqttPublishError,
TokenRefreshError,
)
from ..unit_system import set_unit_system

if TYPE_CHECKING:
from ..models import (
Device,
DeviceFeature,
DeviceStatus,
EnergyUsageResponse,
)
from ..unit_system import UnitSystemType, set_unit_system
from .command_queue import MqttCommandQueue
from .connection import MqttConnection
from .control import MqttDeviceController
Expand All @@ -52,6 +44,14 @@
PeriodicRequestType,
)

if TYPE_CHECKING:
from ..models import (
Device,
DeviceFeature,
DeviceStatus,
EnergyUsageResponse,
)

__author__ = "Emmanuel Levijarvi"
__copyright__ = "Emmanuel Levijarvi"
__license__ = "MIT"
Expand Down Expand Up @@ -136,7 +136,7 @@ def __init__(
self,
auth_client: NavienAuthClient,
config: MqttConnectionConfig | None = None,
unit_system: Literal["metric", "imperial"] | None = None,
unit_system: UnitSystemType = None,
):
"""
Initialize the MQTT client.
Expand All @@ -146,7 +146,8 @@ def __init__(
config: Optional connection configuration
unit_system: Preferred unit system:
- "metric": Celsius, LPM, Liters
- "imperial": Fahrenheit, GPM, Gallons
- "us_customary": Fahrenheit, GPM, Gallons

- None: Auto-detect from device (default)

Raises:
Expand Down Expand Up @@ -184,7 +185,7 @@ def __init__(
set_unit_system(unit_system)

self._auth_client = auth_client
self._unit_system: Literal["metric", "imperial"] | None = unit_system
self._unit_system: UnitSystemType = unit_system
self.config = config or MqttConnectionConfig()

# Session tracking
Expand Down
11 changes: 6 additions & 5 deletions src/nwp500/mqtt/subscriptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
import json
import logging
from collections.abc import Callable
from typing import TYPE_CHECKING, Any, Literal
from typing import TYPE_CHECKING, Any

from awscrt import mqtt
from awscrt.exceptions import AwsCrtError
Expand All @@ -25,7 +25,7 @@
from ..exceptions import MqttNotConnectedError
from ..models import Device, DeviceFeature, DeviceStatus, EnergyUsageResponse
from ..topic_builder import MqttTopicBuilder
from ..unit_system import set_unit_system
from ..unit_system import UnitSystemType, set_unit_system
from .utils import redact_topic, topic_matches_pattern

if TYPE_CHECKING:
Expand Down Expand Up @@ -55,7 +55,7 @@ def __init__(
event_emitter: EventEmitter,
schedule_coroutine: Callable[[Any], None],
device_info_cache: MqttDeviceInfoCache | None = None,
unit_system: Literal["metric", "imperial"] | None = None,
unit_system: UnitSystemType = None,
):
"""
Initialize subscription manager.
Expand All @@ -67,14 +67,15 @@ def __init__(
schedule_coroutine: Function to schedule async tasks
device_info_cache: Optional MqttDeviceInfoCache for caching device
features
unit_system: Preferred unit system ("metric", "imperial", or None)
unit_system: Preferred unit system ("metric", "us_customary",
or None)
"""
self._connection = connection
self._client_id = client_id
self._event_emitter = event_emitter
self._schedule_coroutine = schedule_coroutine
self._device_info_cache = device_info_cache
self._unit_system: Literal["metric", "imperial"] | None = unit_system
self._unit_system: UnitSystemType = unit_system

# Track subscriptions and handlers
self._subscriptions: dict[str, mqtt.QoS] = {}
Expand Down
30 changes: 17 additions & 13 deletions src/nwp500/unit_system.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,19 @@

_logger = logging.getLogger(__name__)

# Type alias for unit system preference
UnitSystemType = Literal["metric", "us_customary"] | None

# Context variable to store the preferred unit system
# None means auto-detect from device
# "metric" means Celsius, "imperial" means Fahrenheit
# "metric" means Celsius, "us_customary" means Fahrenheit
_unit_system_context: contextvars.ContextVar[
Literal["metric", "imperial"] | None
Literal["metric", "us_customary"] | None
] = contextvars.ContextVar("unit_system", default=None)


def set_unit_system(
unit_system: Literal["metric", "imperial"] | None,
unit_system: Literal["metric", "us_customary"] | None,
) -> None:
"""Set preferred unit system for temperature, flow, and volume conversions.

Expand All @@ -41,12 +44,12 @@ def set_unit_system(
Args:
unit_system: Preferred unit system:
- "metric": Use Celsius, LPM, and Liters
- "imperial": Use Fahrenheit, GPM, and Gallons
- "us_customary": Use Fahrenheit, GPM, and Gallons
- None: Auto-detect from device's temperature_type (default)

Example:
>>> from nwp500 import set_unit_system
>>> set_unit_system("imperial")
>>> set_unit_system("us_customary")
>>> # All values now in F, GPM, Gallons
>>> set_unit_system(None) # Reset to auto-detect

Expand All @@ -57,13 +60,13 @@ def set_unit_system(
_unit_system_context.set(unit_system)


def get_unit_system() -> Literal["metric", "imperial"] | None:
def get_unit_system() -> Literal["metric", "us_customary"] | None:
"""Get the currently configured unit system preference.

Returns:
The current unit system preference:
- "metric": Celsius, LPM, Liters
- "imperial": Fahrenheit, GPM, Gallons
- "us_customary": Fahrenheit, GPM, Gallons
- None: Auto-detect from device (default)
"""
return _unit_system_context.get()
Expand All @@ -79,29 +82,29 @@ def reset_unit_system() -> None:


def unit_system_to_temperature_type(
unit_system: Literal["metric", "imperial"] | None,
unit_system: Literal["metric", "us_customary"] | None,
) -> TemperatureType | None:
"""Convert unit system preference to TemperatureType enum.

Args:
unit_system: Unit system preference ("metric", "imperial", or None)
unit_system: Unit system preference ("metric", "us_customary", or None)

Returns:
- TemperatureType.CELSIUS for "metric"
- TemperatureType.FAHRENHEIT for "imperial"
- TemperatureType.FAHRENHEIT for "us_customary"
- None for None (auto-detect)
"""
match unit_system:
case "metric":
return TemperatureType.CELSIUS
case "imperial":
case "us_customary":
return TemperatureType.FAHRENHEIT
case None:
return None


def is_metric_preferred(
override: Literal["metric", "imperial"] | None = None,
override: Literal["metric", "us_customary"] | None = None,
) -> bool:
"""Check if metric (Celsius) is preferred.

Expand All @@ -113,7 +116,8 @@ def is_metric_preferred(
over the context-configured unit system.

Returns:
True if metric (Celsius) is preferred, False if imperial (Fahrenheit).
True if metric (Celsius) is preferred, False if us_customary
(Fahrenheit).
"""
# If override is provided, use it
if override is not None:
Expand Down
12 changes: 6 additions & 6 deletions tests/test_unit_switching.py
Original file line number Diff line number Diff line change
Expand Up @@ -353,8 +353,8 @@ def test_unit_system_context_override_affects_field_units():
assert feature.get_field_unit("freeze_protection_temp_min") == " °C"
assert feature.get_field_unit("recirc_temperature_min") == " °C"

# Test 3: Override to imperial - should return Fahrenheit units
set_unit_system("imperial")
# Test 3: Override to us_customary - should return Fahrenheit units
set_unit_system("us_customary")
assert feature.get_field_unit("dhw_temperature_min") == " °F"
assert feature.get_field_unit("freeze_protection_temp_min") == " °F"
assert feature.get_field_unit("recirc_temperature_min") == " °F"
Expand Down Expand Up @@ -478,8 +478,8 @@ def test_unit_system_context_override_with_flow_rate_units():
set_unit_system("metric")
assert status.get_field_unit("current_dhw_flow_rate") == " LPM"

# Test 3: Override to imperial - should return GPM units
set_unit_system("imperial")
# Test 3: Override to us_customary - should return GPM units
set_unit_system("us_customary")
assert status.get_field_unit("current_dhw_flow_rate") == " GPM"

# Clean up
Expand Down Expand Up @@ -601,8 +601,8 @@ def test_unit_system_context_override_with_volume_units():
set_unit_system("metric")
assert status.get_field_unit("cumulated_dhw_flow_rate") == " L"

# Test 3: Override to imperial - should return Gallons units
set_unit_system("imperial")
# Test 3: Override to us_customary - should return Gallons units
set_unit_system("us_customary")
assert status.get_field_unit("cumulated_dhw_flow_rate") == " gal"

# Clean up
Expand Down