From 699b992451dd21d76b0f3cc746ff8f055c32d416 Mon Sep 17 00:00:00 2001 From: Emmanuel Levijarvi Date: Fri, 21 Nov 2025 12:28:01 -0800 Subject: [PATCH 1/7] Fix: Correct temperature conversion logic in models The previous temperature conversion logic used a hardcoded 20-degree Fahrenheit offset for some fields and an incorrect scaling factor for others. This was based on an approximation of the device's behavior. Analysis of the decompiled mobile application revealed the precise conversion formulas used for different temperature sensors. This commit replaces the previous validators with new, more accurate ones that reflect the app's implementation: - Replaced `Add20` with `HalfCelsiusToF` for fields that are transmitted as half-degrees Celsius. - Replaced `DeciCelsiusToF` with `PentaCelsiusToF` for fields that are scaled by a factor of 5. A new test file `tests/test_models.py` has been added to verify the correctness of the new conversion logic. --- src/nwp500/models.py | 91 +++++++++++++++--------------- tests/test_models.py | 129 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 175 insertions(+), 45 deletions(-) create mode 100644 tests/test_models.py diff --git a/src/nwp500/models.py b/src/nwp500/models.py index 2f4d342..5acf9de 100644 --- a/src/nwp500/models.py +++ b/src/nwp500/models.py @@ -26,33 +26,34 @@ def _device_bool_validator(v: Any) -> bool: return bool(v == 2) -def _add_20_validator(v: Any) -> float: - """Add 20 to the value (temperature offset).""" +def _div_10_validator(v: Any) -> float: + """Divide by 10.""" if isinstance(v, (int, float)): - return float(v) + 20.0 + return float(v) / 10.0 return float(v) -def _div_10_validator(v: Any) -> float: - """Divide by 10.""" +def _half_celsius_to_fahrenheit(v: Any) -> float: + """Convert half-degrees Celsius to Fahrenheit.""" if isinstance(v, (int, float)): - return float(v) / 10.0 + celsius = float(v) / 2.0 + return (celsius * 9 / 5) + 32 return float(v) -def _decicelsius_to_fahrenheit(v: Any) -> float: - """Convert decicelsius (tenths of Celsius) to Fahrenheit.""" +def _penta_celsius_to_fahrenheit(v: Any) -> float: + """Convert value scaled by 5 to Fahrenheit.""" if isinstance(v, (int, float)): - celsius = float(v) / 10.0 + celsius = float(v) / 5.0 return (celsius * 9 / 5) + 32 return float(v) # Reusable Annotated types for conversions DeviceBool = Annotated[bool, BeforeValidator(_device_bool_validator)] -Add20 = Annotated[float, BeforeValidator(_add_20_validator)] Div10 = Annotated[float, BeforeValidator(_div_10_validator)] -DeciCelsiusToF = Annotated[float, BeforeValidator(_decicelsius_to_fahrenheit)] +HalfCelsiusToF = Annotated[float, BeforeValidator(_half_celsius_to_fahrenheit)] +PentaCelsiusToF = Annotated[float, BeforeValidator(_penta_celsius_to_fahrenheit)] class NavienBaseModel(BaseModel): @@ -252,24 +253,24 @@ class DeviceStatus(NavienBaseModel): recirc_operation_busy: DeviceBool recirc_reservation_use: DeviceBool - # Temperature fields with offset (raw + 20) - dhw_temperature: Add20 - dhw_temperature_setting: Add20 - dhw_target_temperature_setting: Add20 - freeze_protection_temperature: Add20 - dhw_temperature2: Add20 - hp_upper_on_temp_setting: Add20 - hp_upper_off_temp_setting: Add20 - hp_lower_on_temp_setting: Add20 - hp_lower_off_temp_setting: Add20 - he_upper_on_temp_setting: Add20 - he_upper_off_temp_setting: Add20 - he_lower_on_temp_setting: Add20 - he_lower_off_temp_setting: Add20 - heat_min_op_temperature: Add20 - recirc_temp_setting: Add20 - recirc_temperature: Add20 - recirc_faucet_temperature: Add20 + # Temperature fields that are likely half-degrees Celsius + dhw_temperature: HalfCelsiusToF + dhw_temperature_setting: HalfCelsiusToF + dhw_target_temperature_setting: HalfCelsiusToF + freeze_protection_temperature: HalfCelsiusToF + dhw_temperature2: HalfCelsiusToF + hp_upper_on_temp_setting: HalfCelsiusToF + hp_upper_off_temp_setting: HalfCelsiusToF + hp_lower_on_temp_setting: HalfCelsiusToF + hp_lower_off_temp_setting: HalfCelsiusToF + he_upper_on_temp_setting: HalfCelsiusToF + he_upper_off_temp_setting: HalfCelsiusToF + he_lower_on_temp_setting: HalfCelsiusToF + he_lower_off_temp_setting: HalfCelsiusToF + heat_min_op_temperature: HalfCelsiusToF + recirc_temp_setting: HalfCelsiusToF + recirc_temperature: HalfCelsiusToF + recirc_faucet_temperature: HalfCelsiusToF # Fields with scale division (raw / 10.0) current_inlet_temperature: Div10 @@ -286,15 +287,15 @@ class DeviceStatus(NavienBaseModel): he_lower_off_diff_temp_setting: Div10 recirc_dhw_flow_rate: Div10 - # Temperature fields with decicelsius to Fahrenheit conversion - tank_upper_temperature: DeciCelsiusToF - tank_lower_temperature: DeciCelsiusToF - discharge_temperature: DeciCelsiusToF - suction_temperature: DeciCelsiusToF - evaporator_temperature: DeciCelsiusToF - ambient_temperature: DeciCelsiusToF - target_super_heat: DeciCelsiusToF - current_super_heat: DeciCelsiusToF + # Temperature fields with 1/5 scaling + tank_upper_temperature: PentaCelsiusToF + tank_lower_temperature: PentaCelsiusToF + discharge_temperature: PentaCelsiusToF + suction_temperature: PentaCelsiusToF + evaporator_temperature: PentaCelsiusToF + ambient_temperature: PentaCelsiusToF + target_super_heat: PentaCelsiusToF + current_super_heat: PentaCelsiusToF # Enum fields operation_mode: CurrentOperationMode = Field( @@ -306,8 +307,8 @@ class DeviceStatus(NavienBaseModel): temperature_type: TemperatureUnit = Field( default=TemperatureUnit.FAHRENHEIT ) - freeze_protection_temp_min: Add20 = 43.0 - freeze_protection_temp_max: Add20 = 65.0 + freeze_protection_temp_min: HalfCelsiusToF = 43.0 + freeze_protection_temp_max: HalfCelsiusToF = 65.0 @classmethod def from_dict(cls, data: dict[str, Any]) -> "DeviceStatus": @@ -350,11 +351,11 @@ class DeviceFeature(NavienBaseModel): energy_saver_use: int high_demand_use: int - # Temperature limit fields with offset (raw + 20) - dhw_temperature_min: Add20 - dhw_temperature_max: Add20 - freeze_protection_temp_min: Add20 - freeze_protection_temp_max: Add20 + # Temperature limit fields with half-degree Celsius scaling + dhw_temperature_min: HalfCelsiusToF + dhw_temperature_max: HalfCelsiusToF + freeze_protection_temp_min: HalfCelsiusToF + freeze_protection_temp_max: HalfCelsiusToF # Enum field temperature_type: TemperatureUnit = Field( diff --git a/tests/test_models.py b/tests/test_models.py new file mode 100644 index 0000000..fe1f130 --- /dev/null +++ b/tests/test_models.py @@ -0,0 +1,129 @@ +import pytest +from nwp500.models import DeviceStatus + +def get_default_status_data(): + return { + "command": 0, + "outsideTemperature": 0.0, + "specialFunctionStatus": 0, + "errorCode": 0, + "subErrorCode": 0, + "smartDiagnostic": 0, + "faultStatus1": 0, + "faultStatus2": 0, + "wifiRssi": 0, + "dhwChargePer": 0.0, + "drEventStatus": 0, + "vacationDaySetting": 0, + "vacationDayElapsed": 0, + "antiLegionellaPeriod": 0, + "programReservationType": 0, + "tempFormulaType": 0, + "currentStatenum": 0, + "targetFanRpm": 0, + "currentFanRpm": 0, + "fanPwm": 0, + "mixingRate": 0.0, + "eevStep": 0, + "airFilterAlarmPeriod": 0, + "airFilterAlarmElapsed": 0, + "cumulatedOpTimeEvaFan": 0, + "cumulatedDhwFlowRate": 0.0, + "touStatus": 0, + "drOverrideStatus": 0, + "touOverrideStatus": 0, + "totalEnergyCapacity": 0.0, + "availableEnergyCapacity": 0.0, + "recircOperationMode": 0, + "recircPumpOperationStatus": 0, + "recircHotBtnReady": 0, + "recircOperationReason": 0, + "recircErrorStatus": 0, + "currentInstPower": 0.0, + "didReload": 0, + "operationBusy": 0, + "freezeProtectionUse": 0, + "dhwUse": 0, + "dhwUseSustained": 0, + "programReservationUse": 0, + "ecoUse": 0, + "compUse": 0, + "eevUse": 0, + "evaFanUse": 0, + "shutOffValveUse": 0, + "conOvrSensorUse": 0, + "wtrOvrSensorUse": 0, + "antiLegionellaUse": 0, + "antiLegionellaOperationBusy": 0, + "errorBuzzerUse": 0, + "currentHeatUse": 0, + "heatUpperUse": 0, + "heatLowerUse": 0, + "scaldUse": 0, + "airFilterAlarmUse": 0, + "recircOperationBusy": 0, + "recircReservationUse": 0, + "dhwTemperature": 0, + "dhwTemperatureSetting": 0, + "dhwTargetTemperatureSetting": 0, + "freezeProtectionTemperature": 0, + "dhwTemperature2": 0, + "hpUpperOnTempSetting": 0, + "hpUpperOffTempSetting": 0, + "hpLowerOnTempSetting": 0, + "hpLowerOffTempSetting": 0, + "heUpperOnTempSetting": 0, + "heUpperOffTempSetting": 0, + "heLowerOnTempSetting": 0, + "heLowerOffTempSetting": 0, + "heatMinOpTemperature": 0, + "recircTempSetting": 0, + "recircTemperature": 0, + "recircFaucetTemperature": 0, + "currentInletTemperature": 0, + "currentDhwFlowRate": 0, + "hpUpperOnDiffTempSetting": 0, + "hpUpperOffDiffTempSetting": 0, + "hpLowerOnDiffTempSetting": 0, + "hpLowerOffDiffTempSetting": 0, + "heUpperOnDiffTempSetting": 0, + "heUpperOffDiffTempSetting": 0, + "heLowerOnTDiffempSetting": 0, + "heLowerOffDiffTempSetting": 0, + "recircDhwFlowRate": 0, + "tankUpperTemperature": 0, + "tankLowerTemperature": 0, + "dischargeTemperature": 0, + "suctionTemperature": 0, + "evaporatorTemperature": 0, + "ambientTemperature": 0, + "targetSuperHeat": 0, + "currentSuperHeat": 0, + "operationMode": 0, + "dhwOperationSetting": 3, + "temperatureType": 2, + "freezeProtectionTempMin": 43.0, + "freezeProtectionTempMax": 65.0, + } + + +def test_device_status_half_celsius_to_fahrenheit(): + """Test HalfCelsiusToF conversion.""" + raw_data = get_default_status_data() + raw_data["dhwTemperature"] = 122 + status = DeviceStatus.model_validate(raw_data) + assert status.dhw_temperature == pytest.approx(141.8) + +def test_device_status_penta_celsius_to_fahrenheit(): + """Test PentaCelsiusToF conversion.""" + raw_data = get_default_status_data() + raw_data["tankUpperTemperature"] = 250 + status = DeviceStatus.model_validate(raw_data) + assert status.tank_upper_temperature == pytest.approx(122.0) + +def test_device_status_div10(): + """Test Div10 conversion.""" + raw_data = get_default_status_data() + raw_data["currentInletTemperature"] = 500 + status = DeviceStatus.model_validate(raw_data) + assert status.current_inlet_temperature == 50.0 \ No newline at end of file From 44ec7b3f87e7372d07325f2043af48f453c88785 Mon Sep 17 00:00:00 2001 From: Emmanuel Levijarvi Date: Fri, 21 Nov 2025 12:34:27 -0800 Subject: [PATCH 2/7] Fix: Address linting issues --- src/nwp500/models.py | 4 +++- tests/test_models.py | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/nwp500/models.py b/src/nwp500/models.py index 5acf9de..ff7acb1 100644 --- a/src/nwp500/models.py +++ b/src/nwp500/models.py @@ -53,7 +53,9 @@ def _penta_celsius_to_fahrenheit(v: Any) -> float: DeviceBool = Annotated[bool, BeforeValidator(_device_bool_validator)] Div10 = Annotated[float, BeforeValidator(_div_10_validator)] HalfCelsiusToF = Annotated[float, BeforeValidator(_half_celsius_to_fahrenheit)] -PentaCelsiusToF = Annotated[float, BeforeValidator(_penta_celsius_to_fahrenheit)] +PentaCelsiusToF = Annotated[ + float, BeforeValidator(_penta_celsius_to_fahrenheit) +] class NavienBaseModel(BaseModel): diff --git a/tests/test_models.py b/tests/test_models.py index fe1f130..e204f5e 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -1,6 +1,8 @@ import pytest + from nwp500.models import DeviceStatus + def get_default_status_data(): return { "command": 0, @@ -126,4 +128,4 @@ def test_device_status_div10(): raw_data = get_default_status_data() raw_data["currentInletTemperature"] = 500 status = DeviceStatus.model_validate(raw_data) - assert status.current_inlet_temperature == 50.0 \ No newline at end of file + assert status.current_inlet_temperature == 50.0 From 103d6774565d92d4b0b6f702ca2219e0fa4b5e58 Mon Sep 17 00:00:00 2001 From: Emmanuel Levijarvi Date: Fri, 21 Nov 2025 12:39:47 -0800 Subject: [PATCH 3/7] Refactor: Refactor tests to use pytest fixture and fix linting --- tests/test_models.py | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/tests/test_models.py b/tests/test_models.py index e204f5e..5f4923a 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -3,7 +3,9 @@ from nwp500.models import DeviceStatus -def get_default_status_data(): +@pytest.fixture +def default_status_data(): + """Provides a default dictionary for DeviceStatus model.""" return { "command": 0, "outsideTemperature": 0.0, @@ -109,23 +111,22 @@ def get_default_status_data(): } -def test_device_status_half_celsius_to_fahrenheit(): +def test_device_status_half_celsius_to_fahrenheit(default_status_data): """Test HalfCelsiusToF conversion.""" - raw_data = get_default_status_data() - raw_data["dhwTemperature"] = 122 - status = DeviceStatus.model_validate(raw_data) + default_status_data["dhwTemperature"] = 122 + status = DeviceStatus.model_validate(default_status_data) assert status.dhw_temperature == pytest.approx(141.8) -def test_device_status_penta_celsius_to_fahrenheit(): + +def test_device_status_penta_celsius_to_fahrenheit(default_status_data): """Test PentaCelsiusToF conversion.""" - raw_data = get_default_status_data() - raw_data["tankUpperTemperature"] = 250 - status = DeviceStatus.model_validate(raw_data) + default_status_data["tankUpperTemperature"] = 250 + status = DeviceStatus.model_validate(default_status_data) assert status.tank_upper_temperature == pytest.approx(122.0) -def test_device_status_div10(): + +def test_device_status_div10(default_status_data): """Test Div10 conversion.""" - raw_data = get_default_status_data() - raw_data["currentInletTemperature"] = 500 - status = DeviceStatus.model_validate(raw_data) + default_status_data["currentInletTemperature"] = 500 + status = DeviceStatus.model_validate(default_status_data) assert status.current_inlet_temperature == 50.0 From c15c79cc83bb840da5b7932a9fe9f690229e0c71 Mon Sep 17 00:00:00 2001 From: Emmanuel Levijarvi Date: Fri, 21 Nov 2025 12:44:33 -0800 Subject: [PATCH 4/7] Docs: Update temperature conversion documentation --- docs/protocol/data_conversions.rst | 86 +++++++++++++++--------------- docs/python_api/models.rst | 16 +----- 2 files changed, 45 insertions(+), 57 deletions(-) diff --git a/docs/protocol/data_conversions.rst b/docs/protocol/data_conversions.rst index e97e372..087c433 100644 --- a/docs/protocol/data_conversions.rst +++ b/docs/protocol/data_conversions.rst @@ -18,26 +18,25 @@ Raw Encoding Strategies The device uses several encoding schemes to minimize transmission overhead: -1. **Offset Encoding** (add_20) - - Applied to most temperature fields - - Formula: ``displayed_value = raw_value + 20`` - - Purpose: Negative temperatures stored as positive integers - - Range: Typically -4°F (-20°C) to 149°F (65°C) - - Example: Raw 100 → 120°F display value - -2. **Tenths Encoding** (div_10) +1. **Half-degree Celsius to Fahrenheit** (HalfCelsiusToF) + - Applied to most temperature fields that are not scaled by other factors. + - Formula: ``displayed_value = (raw_value / 2.0) * 1.8 + 32`` + - Purpose: Converts raw values, which are in half-degrees Celsius, to Fahrenheit. + - Example: Raw 122 -> (122 / 2) * 1.8 + 32 = 141.8°F + +2. **Scaled Celsius to Fahrenheit** (PentaCelsiusToF) + - Applied to refrigerant and evaporator temperatures. + - Formula: ``displayed_value = (raw_value / 5.0) * 1.8 + 32`` + - Purpose: Converts raw values, which are scaled by a factor of 5, to Fahrenheit. + - Example: Raw 250 -> (250 / 5) * 1.8 + 32 = 122°F + +3. **Tenths Encoding** (div_10) - Applied to decimal precision values - Formula: ``displayed_value = raw_value / 10.0`` - Purpose: Preserve decimal precision in integer storage - Common for flow rates and differential temperatures - Example: Raw 125 → 12.5 GPM -3. **Decicelsius to Fahrenheit** (decicelsius_to_f) - - Applied to refrigerant and evaporator temperatures - - Formula: ``displayed_value = (raw_value / 10) * 9/5 + 32`` - - Purpose: Convert Celsius tenths to Fahrenheit - - Example: Raw 250 (25°C) → 77°F - 4. **Boolean Encoding** (device_bool) - Applied to all status flags - Formula: ``displayed_value = (raw_value == 2)`` @@ -66,15 +65,15 @@ DHW (Domestic Hot Water) Temperatures - Display Unit - Description * - ``dhwTemperature`` - - add_20 + - HalfCelsiusToF - °F - **Current outlet temperature** of hot water being delivered to fixtures. Real-time measurement. Typically 90-150°F. * - ``dhwTemperature2`` - - add_20 + - HalfCelsiusToF - °F - **Secondary DHW temperature sensor** reading (redundancy/averaging). May differ slightly from primary sensor during temperature transitions. * - ``dhwTemperatureSetting`` - - add_20 + - HalfCelsiusToF - °F - **User-configured target temperature** for DHW delivery. Adjustable range: 95-150°F. Default: 120°F. This is the setpoint users configure in the app. * - ``currentInletTemperature`` @@ -82,7 +81,7 @@ DHW (Domestic Hot Water) Temperatures - °F - **Cold water inlet temperature** to the water heater. Affects heating performance and recovery time. Typically 40-80°F depending on season and location. * - ``dhwTargetTemperatureSetting`` - - add_20 + - HalfCelsiusToF - °F - **Duplicate of dhwTemperatureSetting** for legacy API compatibility. @@ -98,11 +97,11 @@ Tank Temperature Sensors - Display Unit - Description * - ``tankUpperTemperature`` - - decicelsius_to_f + - PentaCelsiusToF - °F - **Upper tank sensor temperature**. Indicates stratification - hot water at top for quick delivery. Typically hottest point in tank. * - ``tankLowerTemperature`` - - decicelsius_to_f + - PentaCelsiusToF - °F - **Lower tank sensor temperature**. Indicates bulk tank temperature and heating progress. Typically cooler than upper sensor. @@ -122,27 +121,27 @@ These temperatures monitor the heat pump refrigerant circuit health and performa - Display Unit - Description * - ``dischargeTemperature`` - - decicelsius_to_f + - PentaCelsiusToF - °F - **Compressor discharge temperature**. Temperature of refrigerant exiting the compressor. Typically 120-180°F. High values indicate high system pressure; low values indicate efficiency issues. * - ``suctionTemperature`` - - decicelsius_to_f + - PentaCelsiusToF - °F - **Compressor suction temperature**. Temperature of refrigerant entering the compressor. Typically 40-60°F. Affects superheat calculation. * - ``evaporatorTemperature`` - - decicelsius_to_f + - PentaCelsiusToF - °F - **Evaporator coil temperature**. Where heat is extracted from ambient air. Typically 20-50°F. Lower outdoor air temperature reduces evaporator efficiency. * - ``ambientTemperature`` - - decicelsius_to_f + - PentaCelsiusToF - °F - **Ambient air temperature** measured at heat pump inlet. Directly affects system performance. At freezing (32°F), heat pump efficiency drops significantly. * - ``targetSuperHeat`` - - decicelsius_to_f + - PentaCelsiusToF - °F - **Target superheat setpoint**. Desired temperature difference between suction and evaporator ensuring complete refrigerant vaporization. Typically 10-20°F. * - ``currentSuperHeat`` - - decicelsius_to_f + - PentaCelsiusToF - °F - **Measured superheat value**. Actual temperature difference. Deviation from target indicates EEV (Electronic Expansion Valve) control issues. @@ -166,19 +165,19 @@ Electric heating elements are controlled via thermostat ranges. Two sensors (upp - Display Unit - Description * - ``heUpperOnTempSetting`` - - add_20 + - HalfCelsiusToF - °F - **Upper element ON threshold**. Upper tank temp must fall below this to activate upper heating element. * - ``heUpperOffTempSetting`` - - add_20 + - HalfCelsiusToF - °F - **Upper element OFF threshold**. Upper tank temp rises above this to deactivate upper element (hysteresis). * - ``heLowerOnTempSetting`` - - add_20 + - HalfCelsiusToF - °F - **Lower element ON threshold**. Lower tank temp must fall below this to activate lower element. * - ``heLowerOffTempSetting`` - - add_20 + - HalfCelsiusToF - °F - **Lower element OFF threshold**. Lower tank temp rises above this to deactivate lower element. * - ``heUpperOnDiffTempSetting`` @@ -198,7 +197,7 @@ Electric heating elements are controlled via thermostat ranges. Two sensors (upp - °F - **Lower element differential** variation. * - ``heatMinOpTemperature`` - - add_20 + - HalfCelsiusToF - °F - **Minimum heat pump operation temperature**. Lowest tank temperature setpoint allowed in the current operating mode. Range: 95-113°F. Default: 95°F. When set, the user can only set the target tank temperature at or above this threshold, ensuring minimum system operating conditions. @@ -216,19 +215,19 @@ Heat pump stages are similarly controlled via thermostat ranges: - Display Unit - Description * - ``hpUpperOnTempSetting`` - - add_20 + - HalfCelsiusToF - °F - **Upper heat pump ON**. Upper tank falls below this to activate heat pump for upper tank heating. * - ``hpUpperOffTempSetting`` - - add_20 + - HalfCelsiusToF - °F - **Upper heat pump OFF**. Upper tank rises above this to stop upper tank heat pump operation. * - ``hpLowerOnTempSetting`` - - add_20 + - HalfCelsiusToF - °F - **Lower heat pump ON**. Lower tank falls below this to activate heat pump for lower tank heating. * - ``hpLowerOffTempSetting`` - - add_20 + - HalfCelsiusToF - °F - **Lower heat pump OFF**. Lower tank rises above this to stop lower tank heat pump operation. * - ``hpUpperOnDiffTempSetting`` @@ -264,15 +263,15 @@ Freeze Protection Temperatures - Boolean - **Freeze protection enabled flag**. When True, triggers anti-freeze operation below threshold. * - ``freezeProtectionTemperature`` - - add_20 + - HalfCelsiusToF - °F - **Freeze protection temperature setpoint**. Range: 43-50°F (6-10°C). Default: 43°F (6°C). When tank temperature drops below this, electric heating activates automatically to prevent freezing. * - ``freezeProtectionTempMin`` - - add_20 + - HalfCelsiusToF - °F - **Minimum freeze protection temperature limit** (lower boundary). Fixed at 43°F (6°C). * - ``freezeProtectionTempMax`` - - add_20 + - HalfCelsiusToF - °F - **Maximum freeze protection temperature limit** (upper boundary). Fixed at 50°F (10°C). @@ -290,18 +289,19 @@ For systems with recirculation pumps (optional feature): - Display Unit - Description * - ``recircTemperature`` - - add_20 + - HalfCelsiusToF - °F - **Recirculation loop current temperature**. Temperature of water being circulated back to tank. * - ``recircFaucetTemperature`` - - add_20 + - HalfCelsiusToF - °F - **Recirculation faucet outlet temperature**. How hot water is at the furthest fixture during recirculation. * - ``recircTempSetting`` - - add_20 + - HalfCelsiusToF - °F - **Recirculation target temperature**. What temperature to maintain in the recirculation line. + Flow Rate Fields ---------------- @@ -613,8 +613,8 @@ Temperature Unit Notes * **Fahrenheit** conversions assume target display is °F as configured in the device * **Celsius calculations** can be derived by reversing the conversions: - - From ``add_20`` fields: ``celsius = (fahrenheit - 20) * 5/9`` - - From ``decicelsius_to_f`` fields: ``celsius = (fahrenheit - 32) * 5/9`` + - From ``HalfCelsiusToF`` fields: ``celsius = (fahrenheit - 32) * 5/9 * 2`` + - From ``PentaCelsiusToF`` fields: ``celsius = (fahrenheit - 32) * 5/9 * 5`` - From ``div_10`` fields: ``celsius = value_celsius / 10.0`` * **Sensor Accuracy**: Typically ±2°F for tank sensors, ±3°F for refrigerant sensors diff --git a/docs/python_api/models.rst b/docs/python_api/models.rst index 7c11d3f..dbf3f29 100644 --- a/docs/python_api/models.rst +++ b/docs/python_api/models.rst @@ -293,8 +293,7 @@ Complete real-time device status with 100+ fields. * ``ambient_temperature`` (float) - Ambient air temperature .. note:: - Temperature display values are 20°F higher than message values. - Display: 140°F = Message: 120°F + Temperature values from the device are automatically converted from their raw (scaled Celsius) representation to Fahrenheit or Celsius based on the device's settings. The library handles these conversions transparently. **Key Power/Energy Fields:** @@ -684,18 +683,7 @@ Best Practices # Device supports energy monitoring await mqtt.request_energy_usage(device, year, months) -3. **Handle temperature conversions:** - - .. code-block:: python - - # Display temperature is 20°F higher than message value - display_temp = 140 - message_value = display_temp - 20 # 120 - - # Or use convenience method - await mqtt.set_dhw_temperature_display(device, 140) - -4. **Monitor operation state:** +3. **Monitor operation state:** .. code-block:: python From b22cd4169ed4a71b80c1c65331389dd455da5596 Mon Sep 17 00:00:00 2001 From: Emmanuel Levijarvi Date: Fri, 21 Nov 2025 12:51:34 -0800 Subject: [PATCH 5/7] Docs: Update device_status.rst with correct temperature conversions --- docs/protocol/device_status.rst | 46 ++++++++++++++++----------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/docs/protocol/device_status.rst b/docs/protocol/device_status.rst index 870b04c..fa44679 100644 --- a/docs/protocol/device_status.rst +++ b/docs/protocol/device_status.rst @@ -77,12 +77,12 @@ This document lists the fields found in the ``status`` object of device status m - integer - °F - Current Domestic Hot Water (DHW) outlet temperature. - - ``raw + 20`` + - HalfCelsiusToF * - ``dhwTemperatureSetting`` - integer - °F - Target DHW temperature setting. Range: 95°F (35°C) to 150°F (65.5°C). Default: 120°F (49°C). - - ``raw + 20`` + - HalfCelsiusToF * - ``programReservationUse`` - bool - None @@ -117,42 +117,42 @@ This document lists the fields found in the ``status`` object of device status m - integer - °F - The target DHW temperature setting (same as dhwTemperatureSetting). - - ``raw + 20`` + - HalfCelsiusToF * - ``tankUpperTemperature`` - integer - °F - Temperature of the upper part of the tank. - - ``(raw / 10) * 9/5 + 32`` (decicelsius to Fahrenheit) + - PentaCelsiusToF * - ``tankLowerTemperature`` - integer - °F - Temperature of the lower part of the tank. - - ``(raw / 10) * 9/5 + 32`` (decicelsius to Fahrenheit) + - PentaCelsiusToF * - ``dischargeTemperature`` - integer - °F - Compressor discharge temperature - temperature of refrigerant leaving the compressor. - - ``(raw / 10) * 9/5 + 32`` (decicelsius to Fahrenheit) + - PentaCelsiusToF * - ``suctionTemperature`` - integer - °F - Compressor suction temperature - temperature of refrigerant entering the compressor. - - ``(raw / 10) * 9/5 + 32`` (decicelsius to Fahrenheit) + - PentaCelsiusToF * - ``evaporatorTemperature`` - integer - °F - Evaporator temperature - temperature where heat is absorbed from ambient air. - - ``(raw / 10) * 9/5 + 32`` (decicelsius to Fahrenheit) + - PentaCelsiusToF * - ``ambientTemperature`` - integer - °F - Ambient air temperature measured at the heat pump air intake. - - ``(raw / 10) * 9/5 + 32`` (decicelsius to Fahrenheit) + - PentaCelsiusToF * - ``targetSuperHeat`` - integer - °F - Target superheat value - the desired temperature difference ensuring complete refrigerant vaporization. - - ``(raw / 10) * 9/5 + 32`` (decicelsius to Fahrenheit) + - PentaCelsiusToF * - ``compUse`` - bool - None @@ -212,7 +212,7 @@ This document lists the fields found in the ``status`` object of device status m - integer - °F - Freeze protection temperature setpoint. Range: 43-50°F (6-10°C), Default: 43°F. When tank temperature drops below this, electric heating activates automatically to prevent freezing. - - ``raw + 20`` + - HalfCelsiusToF * - ``antiLegionellaUse`` - bool - None @@ -287,7 +287,7 @@ This document lists the fields found in the ``status`` object of device status m - integer - °F - Second DHW temperature reading. - - ``raw + 20`` + - HalfCelsiusToF * - ``currentDhwFlowRate`` - float - GPM @@ -307,7 +307,7 @@ 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) * 9/5 + 32`` (decicelsius to Fahrenheit) + - PentaCelsiusToF * - ``heatUpperUse`` - bool - None @@ -357,42 +357,42 @@ This document lists the fields found in the ``status`` object of device status m - integer - °F - Heat pump upper on temperature setting. - - ``raw + 20`` + - HalfCelsiusToF * - ``hpUpperOffTempSetting`` - integer - °F - Heat pump upper off temperature setting. - - ``raw + 20`` + - HalfCelsiusToF * - ``hpLowerOnTempSetting`` - integer - °F - Heat pump lower on temperature setting. - - ``raw + 20`` + - HalfCelsiusToF * - ``hpLowerOffTempSetting`` - integer - °F - Heat pump lower off temperature setting. - - ``raw + 20`` + - HalfCelsiusToF * - ``heUpperOnTempSetting`` - integer - °F - Heater element upper on temperature setting. - - ``raw + 20`` + - HalfCelsiusToF * - ``heUpperOffTempSetting`` - integer - °F - Heater element upper off temperature setting. - - ``raw + 20`` + - HalfCelsiusToF * - ``heLowerOnTempSetting`` - integer - °F - Heater element lower on temperature setting. - - ``raw + 20`` + - HalfCelsiusToF * - ``heLowerOffTempSetting`` - integer - °F - Heater element lower off temperature setting. - - ``raw + 20`` + - HalfCelsiusToF * - ``hpUpperOnDiffTempSetting`` - float - °F @@ -437,7 +437,7 @@ This document lists the fields found in the ``status`` object of device status m - float - °F - Minimum heat pump operation temperature. Lowest tank temperature setpoint allowed in the current operating mode (95-113°F, default 95°F). When set, users can only set the target tank temperature at or above this threshold. - - ``raw + 20`` + - HalfCelsiusToF * - ``drOverrideStatus`` - integer - None @@ -730,7 +730,7 @@ Technical Notes * Tank temperature sensors operate within -4°F to 149°F (-20°C to 65°C) * Outside normal range, system may operate with reduced capacity using opposite heating element -* All tank temperature readings use conversion formula: ``display_temp = raw + 20`` + **Heating Elements:** From af16ce90aa0c74070fb6d12670b07e0ddf6ec282 Mon Sep 17 00:00:00 2001 From: Emmanuel Levijarvi Date: Fri, 21 Nov 2025 13:03:02 -0800 Subject: [PATCH 6/7] Docs: Nitpick fix - use 9/5 instead of 1.8 in formulas --- docs/protocol/data_conversions.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/protocol/data_conversions.rst b/docs/protocol/data_conversions.rst index 087c433..ca36ddc 100644 --- a/docs/protocol/data_conversions.rst +++ b/docs/protocol/data_conversions.rst @@ -20,13 +20,13 @@ The device uses several encoding schemes to minimize transmission overhead: 1. **Half-degree Celsius to Fahrenheit** (HalfCelsiusToF) - Applied to most temperature fields that are not scaled by other factors. - - Formula: ``displayed_value = (raw_value / 2.0) * 1.8 + 32`` + - Formula: ``displayed_value = (raw_value / 2.0) * 9/5 + 32`` - Purpose: Converts raw values, which are in half-degrees Celsius, to Fahrenheit. - Example: Raw 122 -> (122 / 2) * 1.8 + 32 = 141.8°F 2. **Scaled Celsius to Fahrenheit** (PentaCelsiusToF) - Applied to refrigerant and evaporator temperatures. - - Formula: ``displayed_value = (raw_value / 5.0) * 1.8 + 32`` + - Formula: ``displayed_value = (raw_value / 5.0) * 9/5 + 32`` - Purpose: Converts raw values, which are scaled by a factor of 5, to Fahrenheit. - Example: Raw 250 -> (250 / 5) * 1.8 + 32 = 122°F From 08757e492e250c1edf187fb2e5d33e2d63c8229c Mon Sep 17 00:00:00 2001 From: Emmanuel Levijarvi Date: Fri, 21 Nov 2025 13:08:43 -0800 Subject: [PATCH 7/7] Docs: Update conversion examples to use 9/5 fraction --- docs/protocol/data_conversions.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/protocol/data_conversions.rst b/docs/protocol/data_conversions.rst index ca36ddc..582a92f 100644 --- a/docs/protocol/data_conversions.rst +++ b/docs/protocol/data_conversions.rst @@ -22,13 +22,13 @@ The device uses several encoding schemes to minimize transmission overhead: - Applied to most temperature fields that are not scaled by other factors. - Formula: ``displayed_value = (raw_value / 2.0) * 9/5 + 32`` - Purpose: Converts raw values, which are in half-degrees Celsius, to Fahrenheit. - - Example: Raw 122 -> (122 / 2) * 1.8 + 32 = 141.8°F + - Example: Raw 122 -> (122 / 2) * 9/5 + 32 = 141.8°F 2. **Scaled Celsius to Fahrenheit** (PentaCelsiusToF) - Applied to refrigerant and evaporator temperatures. - Formula: ``displayed_value = (raw_value / 5.0) * 9/5 + 32`` - Purpose: Converts raw values, which are scaled by a factor of 5, to Fahrenheit. - - Example: Raw 250 -> (250 / 5) * 1.8 + 32 = 122°F + - Example: Raw 250 -> (250 / 5) * 9/5 + 32 = 122°F 3. **Tenths Encoding** (div_10) - Applied to decimal precision values