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
34 changes: 21 additions & 13 deletions docs/guides/advanced_features_explained.rst
Original file line number Diff line number Diff line change
Expand Up @@ -441,24 +441,32 @@ Factors Affecting Stratification
- Target: Warm tank from bottom, allow sufficient top recovery
- Result: 140°F achieved in 45 min (slower due to DR, but cold ambient expected)

Formula Confirmation
====================
Temperature Conversion Reference
================================

**Formula**:
The NWP500 uses **half-degrees Celsius** encoding for temperature fields.

The temperature conversion formula is:
**Conversion Formulas**:

.. code-block:: text

Formula: displayed_value = raw_value + 20
Half-degrees Celsius to Fahrenheit:
fahrenheit = (raw_value / 2.0) * 9/5 + 32

Application Evidence:
- Application handled by NaviLink
- Implementation in device status messages
- Fields: dhwTemperature, tankUpperTemperature, tankLowerTemperature, etc.
- Conversion: Applied uniformly to all add_20 type fields in device status
- Raw value range: 0-130 (representing -4°F to 150°F)
- Display range: 20-150°F
Examples:
- Raw 70 → (70 / 2.0) * 9/5 + 32 = 95°F
- Raw 98 → (98 / 2.0) * 9/5 + 32 ≈ 120°F
- Raw 120 → (120 / 2.0) * 9/5 + 32 = 140°F
- Raw 132 → (132 / 2.0) * 9/5 + 32 ≈ 150°F

Inverse (Fahrenheit to raw):
raw_value = (fahrenheit - 32) * 5/9 * 2

**Field Types**:

- **HalfCelsiusToF**: Most temperature fields (DHW, setpoints, freeze protection)
- **DeciCelsiusToF**: Sensor readings (tank sensors, refrigerant circuit)
- Formula: ``fahrenheit = (raw_value / 10.0) * 9/5 + 32``

**Related Documentation**:

Expand Down Expand Up @@ -489,7 +497,7 @@ Summary and Recommendations
See Also
--------

* :doc:`../protocol/data_conversions` - Temperature field conversions (add_20, decicelsius_to_f)
* :doc:`../protocol/data_conversions` - Temperature field conversions (HalfCelsiusToF, DeciCelsiusToF)
* :doc:`../protocol/device_status` - Complete device status field reference
* :doc:`scheduling_features` - Reservation and TOU integration points
* :doc:`../python_api/models` - DeviceStatus model field definitions
56 changes: 31 additions & 25 deletions docs/guides/reservations.rst
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ Here's a simple example that sets up a weekday morning reservation:
hour=6,
minute=30,
mode_id=4, # High Demand
param=120 # 140°F display (120 + 20)
param=120 # 140°F (half-degrees Celsius: 60°C × 2)
)

# Send to device
Expand Down Expand Up @@ -131,21 +131,22 @@ Field Descriptions

``param`` (integer, required)
Mode-specific parameter value. For temperature modes (1-4), this is the
target water temperature with a **20°F offset**:
target water temperature encoded in **half-degrees Celsius**:

* Display temperature = ``param + 20``
* Message value = Display temperature - 20
* Conversion formula: ``fahrenheit = (param / 2.0) * 9/5 + 32``
* Inverse formula: ``param = (fahrenheit - 32) * 5/9 * 2``

**Temperature Examples:**

* 120°F display → ``param = 100``
* 130°F display → ``param = 110``
* 140°F display → ``param = 120``
* 150°F display → ``param = 130``
* 95°F display → ``param = 70`` (35°C × 2)
* 120°F display → ``param = 98`` (48.9°C × 2)
* 130°F display → ``param = 110`` (54.4°C × 2)
* 140°F display → ``param = 120`` (60°C × 2)
* 150°F display → ``param = 132`` (65.6°C × 2)

For non-temperature modes (Vacation, Power Off), the param value is
typically ignored but should be set to a valid temperature offset
(e.g., ``100``) for consistency.
typically ignored but should be set to a valid temperature value
(e.g., ``98`` for 120°F) for consistency.

Helper Functions
================
Expand All @@ -168,7 +169,7 @@ Use ``build_reservation_entry()`` to create properly formatted entries:
hour=6,
minute=30,
mode_id=4, # High Demand
param=120 # 140°F (120 + 20)
param=120 # 140°F (half-degrees Celsius: 60°C × 2)
)
# Returns: {'enable': 1, 'week': 62, 'hour': 6, 'min': 30,
# 'mode': 4, 'param': 120}
Expand All @@ -180,7 +181,7 @@ Use ``build_reservation_entry()`` to create properly formatted entries:
hour=8,
minute=0,
mode_id=3, # Energy Saver
param=100 # 120°F (100 + 20)
param=98 # ~120°F (half-degrees Celsius: 48.9°C × 2)
)

# You can also use day indices (0=Sunday, 6=Saturday)
Expand All @@ -190,7 +191,7 @@ Use ``build_reservation_entry()`` to create properly formatted entries:
hour=18,
minute=0,
mode_id=1, # Heat Pump Only
param=110 # 130°F (110 + 20)
param=110 # ~130°F (half-degrees Celsius: 54.4°C × 2)
)

Encoding Week Bitfields
Expand Down Expand Up @@ -334,13 +335,15 @@ Request the current reservation schedule from the device:
hour = entry.get("hour", 0)
minute = entry.get("min", 0)
mode = entry.get("mode", 0)
display_temp = entry.get("param", 0) + 20
# Convert from half-degrees Celsius to Fahrenheit
raw_param = entry.get("param", 0)
display_temp = (raw_param / 2.0) * 9/5 + 32

print(f"\nEntry {idx}:")
print(f" Time: {hour:02d}:{minute:02d}")
print(f" Days: {', '.join(days)}")
print(f" Mode: {mode}")
print(f" Temp: {display_temp}°F")
print(f" Temp: {display_temp:.1f}°F")

await mqtt.subscribe(response_topic, on_reservation_response)

Expand Down Expand Up @@ -508,14 +511,15 @@ Automatically enable vacation mode during a trip:
Important Notes
===============

Temperature Offset
------------------
Temperature Encoding
--------------------

The ``param`` field uses a **20°F offset** from the display temperature:
The ``param`` field uses **half-degrees Celsius** encoding:

* If you want the display to show 140°F, use ``param=120``
* If you see ``param=100`` in a response, it means 120°F display
* This offset applies to all temperature-based modes (Heat Pump, Electric,
* Formula: ``fahrenheit = (param / 2.0) * 9/5 + 32``
* If you want the display to show 140°F, use ``param=120`` (which is 60°C × 2)
* If you see ``param=98`` in a response, it means ~120°F display
* This encoding applies to all temperature-based modes (Heat Pump, Electric,
Energy Saver, High Demand)

Device Limits
Expand Down Expand Up @@ -589,7 +593,7 @@ Full working example with error handling and response monitoring:
hour=6,
minute=30,
mode_id=4, # High Demand
param=120 # 140°F
param=120 # 140°F (half-degrees Celsius: 60°C × 2)
),
# Weekday day
NavienAPIClient.build_reservation_entry(
Expand All @@ -599,7 +603,7 @@ Full working example with error handling and response monitoring:
hour=9,
minute=0,
mode_id=3, # Energy Saver
param=100 # 120°F
param=98 # ~120°F (half-degrees Celsius: 48.9°C × 2)
),
# Weekend morning
NavienAPIClient.build_reservation_entry(
Expand All @@ -608,7 +612,7 @@ Full working example with error handling and response monitoring:
hour=8,
minute=0,
mode_id=3, # Energy Saver
param=110 # 130°F
param=110 # ~130°F (half-degrees Celsius: 54.4°C × 2)
),
]

Expand Down Expand Up @@ -637,9 +641,11 @@ Full working example with error handling and response monitoring:
days = decode_week_bitfield(
entry["week"]
)
# Convert from half-degrees Celsius to Fahrenheit
temp_f = (entry['param'] / 2.0) * 9/5 + 32
print(f"Entry {idx}: {entry['hour']:02d}:"
f"{entry['min']:02d} - Mode {entry['mode']} - "
f"{entry['param'] + 20}°F - "
f"{temp_f:.1f}°F - "
f"{', '.join(days)}")

response_received.set()
Expand Down
21 changes: 13 additions & 8 deletions docs/guides/scheduling_features.rst
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,9 @@ Each reservation entry controls one scheduled action:
"hour": 6, # 0-23 (24-hour format)
"min": 30, # 0-59
"mode": 3, # 1=Heat Pump, 2=Electric, 3=Energy Saver, 4=High Demand
"param": 120 # Temperature offset (raw value, add 20 to display)
# 120 raw = 140°F display
"param": 120 # Temperature in half-degrees Celsius
# Formula: fahrenheit = (param / 2.0) * 9/5 + 32
# 120 = 60°C × 2 = 140°F
}

**Week Bitfield Encoding**:
Expand All @@ -107,15 +108,19 @@ The ``week`` field uses 7 bits for days of week:

**Temperature Parameter Encoding**:

The ``param`` field stores temperature with an offset of 20°F:
The ``param`` field stores temperature in **half-degrees Celsius**:

.. code-block:: text

Display Temperature → Raw Parameter Value
95°F → 75 (95-20)
120°F → 100 (120-20)
140°F → 120 (140-20)
150°F → 130 (150-20)
Conversion: fahrenheit = (param / 2.0) * 9/5 + 32
Inverse: param = (fahrenheit - 32) * 5/9 * 2

Temperature Examples:
95°F → 70 (35°C × 2)
120°F → 98 (48.9°C × 2)
130°F → 110 (54.4°C × 2)
140°F → 120 (60°C × 2)
150°F → 132 (65.6°C × 2)

**Mode Selection Strategy**:

Expand Down
16 changes: 9 additions & 7 deletions docs/protocol/device_features.rst
Original file line number Diff line number Diff line change
Expand Up @@ -103,12 +103,12 @@ The DeviceFeature data contains comprehensive device capabilities, configuration
- int
- °F
- Minimum DHW temperature setting: 95°F (35°C) - safety and efficiency lower limit
- ``raw + 20``
- HalfCelsiusToF
* - ``dhwTemperatureMax``
- int
- °F
- Maximum DHW temperature setting: 150°F (65.5°C) - scald protection upper limit
- ``raw + 20``
- HalfCelsiusToF
* - ``smartDiagnosticUse``
- int
- Boolean
Expand Down Expand Up @@ -143,12 +143,12 @@ The DeviceFeature data contains comprehensive device capabilities, configuration
- int
- °F
- Minimum freeze protection threshold: 43°F (6°C) - factory default activation temperature
- ``raw + 20``
- HalfCelsiusToF
* - ``freezeProtectionTempMax``
- int
- °F
- Maximum freeze protection threshold: typically 65°F - user-adjustable upper limit
- ``raw + 20``
- HalfCelsiusToF
* - ``mixingValueUse``
- int
- Boolean
Expand Down Expand Up @@ -292,15 +292,17 @@ Temperature Range Validation

The reported temperature ranges align with official specifications and use the same conversion patterns as DeviceStatus fields:

* **DHW Range**: 95°F to 150°F (factory default: 120°F for safety) - uses ``raw + 20`` conversion
* **Freeze Protection**: Activates at 43°F, prevents tank freezing - uses ``raw + 20`` conversion
* **DHW Range**: 95°F to 150°F (factory default: 120°F for safety) - uses HalfCelsiusToF conversion
* **Freeze Protection**: Activates at 43°F, prevents tank freezing - uses HalfCelsiusToF conversion
* **Anti-Legionella**: Heats to 140°F at programmed intervals (requires mixing valve)
* **Scald Protection**: Built-in limits with recommendation for thermostatic mixing valves

**Conversion Pattern Consistency**: Temperature fields in DeviceFeature use the same ``raw + 20``
**Conversion Pattern Consistency**: Temperature fields in DeviceFeature use the same HalfCelsiusToF
conversion formula as corresponding fields in DeviceStatus, ensuring consistent temperature
handling across all device data structures.

**HalfCelsiusToF Formula**: ``fahrenheit = (raw_value / 2.0) * 9/5 + 32``

Usage Example
-------------

Expand Down
4 changes: 2 additions & 2 deletions docs/protocol/firmware_tracking.rst
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ The following table tracks known fields that have been introduced in firmware up
- Description
* - ``heatMinOpTemperature``
- Controller: 184614912, WiFi: 34013184
- ``raw + 20``
- HalfCelsiusToF
- Minimum heat pump operation temperature. Lowest tank temperature setpoint allowed in the current operating mode (95-113°F, default 95°F).

Reporting New Fields
Expand Down Expand Up @@ -123,7 +123,7 @@ Example entry in ``constants.py``:
"newFieldName": {
"introduced_in": "controller: 123, panel: 456, wifi: 789",
"description": "What this field represents",
"conversion": "raw + 20", # or "raw / 10.0", "bool (1=OFF, 2=ON)", etc.
"conversion": "HalfCelsiusToF", # or "DeciCelsiusToF", "bool (1=OFF, 2=ON)", etc.
},
}

Expand Down
7 changes: 4 additions & 3 deletions docs/protocol/mqtt_protocol.rst
Original file line number Diff line number Diff line change
Expand Up @@ -245,8 +245,9 @@ DHW Temperature
}

.. important::
Temperature is 20°F less than display value. For 140°F display,
send 120°F.
Temperature values are encoded in **half-degrees Celsius**.
Use formula: ``fahrenheit = (param / 2.0) * 9/5 + 32``
For 140°F, send ``param=120`` (which is 60°C × 2).

Anti-Legionella
---------------
Expand Down Expand Up @@ -349,7 +350,7 @@ Status Response
**Field Conversions:**

* Boolean fields: 1=false, 2=true
* Temperature fields: Add 20 to get display value
* Temperature fields: Use HalfCelsiusToF formula: ``fahrenheit = (raw / 2.0) * 9/5 + 32``
* Enum fields: Map integers to enum values

See :doc:`device_status` for complete field reference.
Expand Down
2 changes: 1 addition & 1 deletion docs/python_api/constants.rst
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,7 @@ Some fields were introduced in specific firmware versions:
"heatMinOpTemperature": {
"introduced_in": "Controller: 184614912, WiFi: 34013184",
"description": "Minimum heat pump operation temperature",
"conversion": "raw + 20"
"conversion": "HalfCelsiusToF"
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/nwp500/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -483,7 +483,7 @@ class DeviceStatus(NavienBaseModel):
description="Recirculation reservation usage status"
)

# Temperature fields with offset (raw + 20)
# Temperature fields - encoded in half-degrees Celsius
dhw_temperature: HalfCelsiusToF = Field(
description="Current Domestic Hot Water (DHW) outlet temperature",
json_schema_extra={
Expand Down