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
52 changes: 26 additions & 26 deletions docs/DEVICE_STATUS_FIELDS.rst
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ This document lists the fields found in the ``status`` object of device status m
- bool
- None
- Indicates if the device has recently reloaded or restarted.
- Converted from integer (0 or 1) to bool
- Converted from integer (1=OFF, 2=ON) to bool
* - ``errorCode``
- integer
- None
Expand All @@ -52,22 +52,22 @@ This document lists the fields found in the ``status`` object of device status m
- bool
- None
- Indicates if the device is currently performing heating operations (True=busy, False=idle).
- Converted from integer (0 or 1) to bool
- Converted from integer (1=OFF, 2=ON) to bool
* - ``freezeProtectionUse``
- bool
- None
- Whether freeze protection is active. When tank water temperature falls below 43°F (6°C), the electric heater activates to prevent freezing.
- Converted from integer (0 or 1) to bool
- Converted from integer (1=OFF, 2=ON) to bool
* - ``dhwUse``
- bool
- None
- Domestic Hot Water (DHW) usage status - indicates if hot water is currently being drawn from the tank.
- Converted from integer (0 or 1) to bool
- Converted from integer (1=OFF, 2=ON) to bool
* - ``dhwUseSustained``
- bool
- None
- Sustained DHW usage status - indicates prolonged hot water usage.
- Converted from integer (0 or 1) to bool
- Converted from integer (1=OFF, 2=ON) to bool
* - ``dhwTemperature``
- integer
- °F
Expand All @@ -82,7 +82,7 @@ This document lists the fields found in the ``status`` object of device status m
- bool
- None
- Whether a program reservation (scheduled operation) is in use.
- Converted from integer (0 or 1) to bool
- Converted from integer (1=OFF, 2=ON) to bool
* - ``smartDiagnostic``
- integer
- None
Expand All @@ -107,7 +107,7 @@ This document lists the fields found in the ``status`` object of device status m
- bool
- None
- Whether ECO (Energy Cut Off) safety feature has been triggered. The ECO switch is a high-temperature safety limit.
- Converted from integer (0 or 1) to bool
- Converted from integer (1=OFF, 2=ON) to bool
* - ``dhwTargetTemperatureSetting``
- integer
- °F
Expand All @@ -127,22 +127,22 @@ This document lists the fields found in the ``status`` object of device status m
- integer
- °F
- Compressor discharge temperature - temperature of refrigerant leaving the compressor.
- ``raw / 10.0``
- ``(raw / 10) * 9/5 + 32`` (decicelsius to Fahrenheit)
* - ``suctionTemperature``
- integer
- °F
- Compressor suction temperature - temperature of refrigerant entering the compressor.
- ``raw / 10.0``
- ``(raw / 10) * 9/5 + 32`` (decicelsius to Fahrenheit)
* - ``evaporatorTemperature``
- integer
- °F
- Evaporator temperature - temperature where heat is absorbed from ambient air.
- ``raw / 10.0``
- ``(raw / 10) * 9/5 + 32`` (decicelsius to Fahrenheit)
* - ``ambientTemperature``
- integer
- °F
- Ambient air temperature measured at the heat pump air intake.
- ``(raw / 22.4) * 9/5 + 32``
- ``(raw / 10) * 9/5 + 32`` (decicelsius to Fahrenheit)
* - ``targetSuperHeat``
- integer
- °F
Expand All @@ -152,17 +152,17 @@ This document lists the fields found in the ``status`` object of device status m
- bool
- None
- Compressor usage status (True=On, False=Off). The compressor is the main component of the heat pump.
- Converted from integer (0 or 1) to bool
- Converted from integer (1=OFF, 2=ON) to bool
* - ``eevUse``
- bool
- None
- Electronic Expansion Valve (EEV) usage status (True=active, False=inactive). The EEV controls refrigerant flow.
- Converted from integer (0 or 1) to bool
- Converted from integer (1=OFF, 2=ON) to bool
* - ``evaFanUse``
- bool
- None
- Evaporator fan usage status (True=On, False=Off). The fan pulls ambient air through the evaporator coil.
- Converted from integer (0 or 1) to bool
- Converted from integer (1=OFF, 2=ON) to bool
* - ``currentInstPower``
- integer
- W
Expand All @@ -172,17 +172,17 @@ This document lists the fields found in the ``status`` object of device status m
- bool
- None
- Shut-off valve usage status. The valve controls refrigerant flow in the system.
- Converted from integer (0 or 1) to bool
- Converted from integer (1=OFF, 2=ON) to bool
* - ``conOvrSensorUse``
- bool
- None
- Condensate overflow sensor usage status.
- Converted from integer (0 or 1) to bool
- Converted from integer (1=OFF, 2=ON) to bool
* - ``wtrOvrSensorUse``
- bool
- None
- Water overflow/leak sensor usage status. Triggers error E799 if leak detected.
- Converted from integer (0 or 1) to bool
- Converted from integer (1=OFF, 2=ON) to bool
* - ``dhwChargePer``
- integer
- %
Expand Down Expand Up @@ -212,7 +212,7 @@ This document lists the fields found in the ``status`` object of device status m
- bool
- None
- Whether anti-legionella function is enabled.
- Converted from integer (0 or 1) to bool
- Converted from integer (1=OFF, 2=ON) to bool
* - ``antiLegionellaPeriod``
- integer
- days
Expand All @@ -222,7 +222,7 @@ This document lists the fields found in the ``status`` object of device status m
- bool
- None
- Whether the anti-legionella function is busy.
- Converted from integer (0 or 1) to bool
- Converted from integer (1=OFF, 2=ON) to bool
* - ``programReservationType``
- integer
- None
Expand All @@ -247,12 +247,12 @@ This document lists the fields found in the ``status`` object of device status m
- bool
- None
- Whether the error buzzer is enabled.
- Converted from integer (0 or 1) to bool
- Converted from integer (1=OFF, 2=ON) to bool
* - ``currentHeatUse``
- bool
- None
- Current heat usage.
- Converted from integer (0 or 1) to bool
- Converted from integer (1=OFF, 2=ON) to bool
* - ``currentInletTemperature``
- float
- °F
Expand Down Expand Up @@ -302,27 +302,27 @@ This document lists the fields found in the ``status`` object of device status m
- integer
- °F
- Current superheat value - actual temperature difference between suction and evaporator temperatures.
- ``raw / 10.0``
- ``(raw / 10) * 9/5 + 32`` (decicelsius to Fahrenheit)
* - ``heatUpperUse``
- bool
- None
- Upper electric heating element usage status (True=On, False=Off). Power: 3,755W @ 208V or 5,000W @ 240V.
- Converted from integer (0 or 1) to bool
- Converted from integer (1=OFF, 2=ON) to bool
* - ``heatLowerUse``
- bool
- None
- Lower electric heating element usage status (True=On, False=Off). Power: 3,755W @ 208V or 5,000W @ 240V.
- Converted from integer (0 or 1) to bool
- Converted from integer (1=OFF, 2=ON) to bool
* - ``scaldUse``
- bool
- None
- Scald protection active status. Displays warning when water temperature reaches levels that could cause scalding.
- Converted from integer (0 or 1) to bool
- Converted from integer (1=OFF, 2=ON) to bool
* - ``airFilterAlarmUse``
- bool
- None
- Air filter alarm usage - indicates if air filter maintenance reminder is enabled.
- Converted from integer (0 or 1) to bool
- Converted from integer (1=OFF, 2=ON) to bool
* - ``airFilterAlarmPeriod``
- integer
- hours
Expand Down
46 changes: 46 additions & 0 deletions src/nwp500/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,37 @@ def on_status(status: DeviceStatus):
_logger.error("Timed out waiting for device status response.")


async def handle_status_raw_request(mqtt: NavienMqttClient, device: Device):
"""Request device status once and print raw MQTT data (no conversions)."""
future = asyncio.get_running_loop().create_future()

# Subscribe to the raw MQTT topic to capture data before conversion
def raw_callback(topic: str, message: dict):
if not future.done():
# Extract and print the raw status portion
if "response" in message and "status" in message["response"]:
print(
json.dumps(
message["response"]["status"], indent=2, default=_json_default_serializer
)
)
future.set_result(None)
elif "status" in message:
print(json.dumps(message["status"], indent=2, default=_json_default_serializer))
future.set_result(None)

# Subscribe to all device messages
await mqtt.subscribe_device(device, raw_callback)

_logger.info("Requesting device status (raw)...")
await mqtt.request_device_status(device)

try:
await asyncio.wait_for(future, timeout=10)
except asyncio.TimeoutError:
_logger.error("Timed out waiting for device status response.")


async def handle_device_info_request(mqtt: NavienMqttClient, device: Device):
"""
Request comprehensive device information via MQTT and print it.
Expand Down Expand Up @@ -504,6 +535,9 @@ async def async_main(args: argparse.Namespace):
_logger.info("Getting updated status after temperature change...")
await asyncio.sleep(2) # Brief pause for device to process
await handle_status_request(mqtt, device)
elif args.status_raw:
# Raw status request (no conversions)
await handle_status_raw_request(mqtt, device)
elif args.status:
# Status-only request
await handle_status_request(mqtt, device)
Expand Down Expand Up @@ -549,6 +583,12 @@ def parse_args(args):
action="store_true",
help="Fetch and print the current device status. Can be combined with control commands.",
)
parser.add_argument(
"--status-raw",
action="store_true",
help="Fetch and print the raw device status as received from MQTT "
"(no conversions applied).",
)

# Primary action modes (mutually exclusive)
group = parser.add_mutually_exclusive_group()
Expand Down Expand Up @@ -635,6 +675,12 @@ def setup_logging(loglevel):
def main(args):
"""Wrapper for the asynchronous main function."""
args = parse_args(args)

# Validate that --status and --status-raw are not used together
if args.status and args.status_raw:
print("Error: --status and --status-raw cannot be used together.", file=sys.stderr)
return 1

# Set default log level for libraries
setup_logging(logging.WARNING)
# Set user-defined log level for this script
Expand Down
64 changes: 45 additions & 19 deletions src/nwp500/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,24 @@
_logger = logging.getLogger(__name__)


def _decicelsius_to_fahrenheit(raw_value: float) -> float:
"""
Convert a raw decicelsius value to Fahrenheit.

Args:
raw_value: Raw value in decicelsius (tenths of degrees Celsius)

Returns:
Temperature in Fahrenheit

Example:
>>> _decicelsius_to_fahrenheit(250) # 25.0°C
77.0
"""
celsius = raw_value / 10.0
return (celsius * 9 / 5) + 32


class OperationMode(Enum):
"""Enumeration for the operation modes of the device.

Expand Down Expand Up @@ -306,6 +324,11 @@ def from_dict(cls, data: dict):
)

# Convert integer-based booleans
# The device uses a non-standard encoding for boolean values:
# 0 = Not applicable/disabled (rarely used)
# 1 = OFF/Inactive/False
# 2 = ON/Active/True
# This applies to ALL boolean fields in the device status
bool_fields = [
"didReload",
"operationBusy",
Expand All @@ -329,9 +352,11 @@ def from_dict(cls, data: dict):
"scaldUse",
"airFilterAlarmUse",
]

# Convert using the device's encoding: 0 or 1=false, 2=true
for field_name in bool_fields:
if field_name in converted_data:
converted_data[field_name] = bool(converted_data[field_name])
converted_data[field_name] = converted_data[field_name] == 2

# Convert temperatures with 'raw + 20' formula
add_20_fields = [
Expand All @@ -353,15 +378,11 @@ def from_dict(cls, data: dict):
if field_name in converted_data:
converted_data[field_name] += 20

# Convert fields with 'raw / 10.0' formula
# Convert fields with 'raw / 10.0' formula (non-temperature fields)
div_10_fields = [
"dischargeTemperature",
"suctionTemperature",
"evaporatorTemperature",
"targetSuperHeat",
"currentInletTemperature",
"currentDhwFlowRate",
"currentSuperHeat",
"hpUpperOnDiffTempSetting",
"hpUpperOffDiffTempSetting",
"hpLowerOnDiffTempSetting",
Expand All @@ -375,23 +396,28 @@ def from_dict(cls, data: dict):
if field_name in converted_data:
converted_data[field_name] /= 10.0

# Special conversion for ambientTemperature
if "ambientTemperature" in converted_data:
raw_temp = converted_data["ambientTemperature"]
# Based on observed data: raw 503.6 corresponds to 72.5°F
# Convert raw value to Celsius first (raw / 22.4 ≈ Celsius)
# Then convert Celsius to Fahrenheit
celsius = raw_temp / 22.4
converted_data["ambientTemperature"] = (celsius * 9 / 5) + 32

# Special conversion for tank temperatures (decicelsius to Fahrenheit)
tank_temp_fields = ["tankUpperTemperature", "tankLowerTemperature"]
for field_name in tank_temp_fields:
if field_name in converted_data:
raw_temp = converted_data[field_name]
# Convert from decicelsius (raw / 10.0) to Celsius, then to Fahrenheit
celsius = raw_temp / 10.0
converted_data[field_name] = (celsius * 9 / 5) + 32
converted_data[field_name] = _decicelsius_to_fahrenheit(converted_data[field_name])

# Special conversion for dischargeTemperature (decicelsius to Fahrenheit)
if "dischargeTemperature" in converted_data:
converted_data["dischargeTemperature"] = _decicelsius_to_fahrenheit(
converted_data["dischargeTemperature"]
)

# Special conversion for heat pump temperatures (decicelsius to Fahrenheit)
heat_pump_temp_fields = [
"suctionTemperature",
"evaporatorTemperature",
"ambientTemperature",
"currentSuperHeat",
]
for field_name in heat_pump_temp_fields:
if field_name in converted_data:
converted_data[field_name] = _decicelsius_to_fahrenheit(converted_data[field_name])

# Convert enum fields with error handling for unknown values
if "operationMode" in converted_data:
Expand Down