diff --git a/CHANGELOG.rst b/CHANGELOG.rst index a99ab2f..5c16256 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -78,6 +78,21 @@ Documentation priority table and Important Notes are corrected, and a new section gives the measurements and their limits. ``heating-elements.rst`` now links to it instead of to the issue. (#141) +- **tank-energy: what selects the second branch, and where usable_energy + under-reads.** ``tank-energy.rst`` left open what selects + ``full_recovery_energy``'s second branch. On one NWP500-65 over 50,819 + ``HEAT_PUMP`` minutes, ``hpUpperOnTempSetting`` separates the branches + with no exceptions: 104.9 degF on the primary, raised to about the + setpoint during the afternoon TOU window on the secondary. + ``energy_to_setpoint`` shifts by the same 2 degC. Because of that, + ``usable_energy`` is not always robust to the branch. In the window it + caps at ``full_recovery_energy`` once ``energy_to_setpoint`` hits 0, + up to about 3.6 degF short of the setpoint, and under-reads by up to + 561 Wh. The page and the ``usable_energy`` docstring now say so and + recommend the thermistors in that case. The ``full_recovery_energy`` + and ``energy_to_setpoint`` descriptions in the model, the protocol + reference, the API reference and ``track-energy.rst`` now note the + window too. (#140) Version 9.4.2 (2026-09-17) ========================== diff --git a/docs/explanation/tank-energy.rst b/docs/explanation/tank-energy.rst index f63d1a5..04207fe 100644 --- a/docs/explanation/tank-energy.rst +++ b/docs/explanation/tank-energy.rst @@ -50,6 +50,8 @@ Their **difference** is a state of charge, and is exposed as The setpoint cancels. What remains is the tank's heat above the device's minimum operating temperature - close enough to the lowest useful shower temperature that it is a good estimate of what you can actually draw. +The exception is a Time-of-Use window near the setpoint, where it can +under-read by up to about 560 Wh; see `Drawable energy`_. How the fields behave @@ -64,8 +66,10 @@ Both fields fit a single two-parameter model: where ``k`` is the tank's heat capacity and ``reference_temperature`` is the device's own minimum setpoint, ``dhwTemperatureMin`` (40.5 degC / -104.9 degF) - though only about two thirds of the time, see -`Two branches`_. +104.9 degF) - except during a Time-of-Use window that raises +``hpUpperOnTempSetting``. Then both fields measure to a target 2 degC +below the setpoint, which puts ``full_recovery_energy``'s reference +2 degC higher; see `Two branches`_. Two consequences follow, and both matter: @@ -277,13 +281,34 @@ about the quantum. On that branch, full_recovery_energy = k * (setpoint - dhwTemperatureMin) -The secondary branch behaves identically with a reference 2 degC higher, -and **what selects between them is unknown**. The device's -``hpUpperOnTempSetting`` correlates with the choice - 104.9 degF -when the primary is active, 143.4 degF when the secondary is - which -would fit the device computing recovery cost from its own turn-on -threshold, but only 22 paired samples were available and that is a lead -rather than a finding. +The secondary branch behaves identically with a reference 2 degC higher. + +**What selects it is the upper heat-pump turn-on setting,** +``hpUpperOnTempSetting``. On one 240 V NWP500-65 +in ``HEAT_PUMP`` mode, over 50,819 one-minute samples from 2026-08-14 to +2026-09-19, the setting separates the branches with no exceptions: + +.. list-table:: + :header-rows: 1 + + * - ``hpUpperOnTempSetting`` + - Minutes + - Reference implied by ``setpoint - full_recovery_energy / 156`` + * - 104.9 degF + - 40,578 + - 104.88 degF (p5-p95 104.88-104.90) + * - Raised to about the setpoint + - 10,241 + - **108.49 degF** (p5-p95 108.49-108.50) + +The raised setting is the device's afternoon Time-of-Use window. It +appears around the 14:00-16:00 period boundaries and falls back around +19:00-21:00. ``energy_to_setpoint`` moves with it: inside the window it +reads ``156 * (setpoint - tank_mean) - 556`` Wh, where the -556 Wh is the +2 degC offset, still within 12 Wh. Both fields are referenced 2 degC +lower during the window. That matches the 1.9 degC shortfall in +:doc:`tou-recovery-cap`, so the fields appear to measure against the +device's TOU-reduced target. .. warning:: Because of this, do not derive the tank's heat capacity from a live @@ -387,14 +412,33 @@ delivered, and once the tank falls below its setting it simply passes through, so water stays usable down to the temperature you actually want at the tap. -Despite ``full_recovery_energy`` being bimodal (see `Two branches`_), -the difference is robust, because both fields shift together. Checked +``full_recovery_energy`` is bimodal (see `Two branches`_), but both +fields shift together, so the difference is usually unaffected. Checked against the tank thermistors over 12275 samples, the tank temperature implied by ``usable_energy`` agrees with the thermistor mean to a standard deviation of **0.57 degF**, with 97.5 % of samples inside 2 degF. -If you need a different floor - a bath at 100 degF, or energy above the +**There is one exception: near the setpoint during a TOU window.** In +that window ``energy_to_setpoint`` is referenced 2 degC lower, so it +reaches its floor of 0 while the tank is still up to about 3.6 degF +short of the setpoint. From there ``usable_energy`` equals +``full_recovery_energy`` and stops rising as the tank heats. On the unit +above: + +* 3,682 minutes fell inside the window with ``energy_to_setpoint`` at 0. + In every one of them, ``usable_energy`` equalled + ``full_recovery_energy``. +* The shortfall against ``156 * (tank_mean - 104.9)`` had a median of + 224 Wh and a maximum of 561 Wh, about one branch offset (3.6 degF). +* At 8 of 64 branch flips at a fixed setpoint, ``usable_energy`` jumped by + the full offset within 5 seconds. + +Outside the window, ``usable_energy`` matched the two-probe mean to a +standard deviation of 0.25 degF. If ``hpUpperOnTempSetting`` is raised +and ``energy_to_setpoint`` reads 0, use the thermistors instead. + +For that case, or if you need a different floor - a bath at 100 degF, or energy above the cold inlet - compute it from the thermistors instead. On a 65-gallon tank the heat capacity is 156 Wh per degF: diff --git a/docs/how-to/track-energy.rst b/docs/how-to/track-energy.rst index 22fb058..183c382 100644 --- a/docs/how-to/track-energy.rst +++ b/docs/how-to/track-energy.rst @@ -172,14 +172,19 @@ Monitor the tank's heating deficit: | **Units:** Watt-hours | **Description:** Energy the device still has to add to reach the setpoint. It *falls* as the tank heats and reaches zero when charged. - Despite the protocol name, it is the inverse of available energy. + During a TOU window that raises ``hp_upper_on_temp_setting``, it + reaches zero about 2 degC short of the setpoint, so the tank is not yet + fully charged. Despite the protocol name, it is the inverse of + available energy. | **Field:** ``full_recovery_energy`` (protocol: ``totalEnergyCapacity``) | **Type:** ``float`` | **Units:** Watt-hours | **Description:** Cost of a full recovery to the *current setpoint*, - measured from the device reference temperature of 104.9 degF. It moves - with the setpoint, so it is not a fixed tank capacity. + measured from the device reference temperature of 104.9 degF, or + 2 degC higher during a TOU window that raises + ``hp_upper_on_temp_setting``. It moves with the setpoint, so it is not + a fixed tank capacity. See :doc:`../explanation/tank-energy`. Temperature Monitoring ---------------------- diff --git a/docs/reference/protocol/data_conversions.rst b/docs/reference/protocol/data_conversions.rst index f4fa49b..c2339de 100644 --- a/docs/reference/protocol/data_conversions.rst +++ b/docs/reference/protocol/data_conversions.rst @@ -376,11 +376,11 @@ Power and Energy Fields * - ``totalEnergyCapacity`` - ``x 4`` (see note) - Wh - - **Cost of a full recovery** to the *current setpoint*, measured from the device reference temperature (``dhwTemperatureMin``, 104.9 degF). Exposed as ``full_recovery_energy``. This is **not** a fixed tank capacity: it moves with the setpoint, by about 140 Wh per 0.5 degC on a 65-gallon tank. + - **Cost of a full recovery** to the *current setpoint*, measured from the device reference temperature (``dhwTemperatureMin``, 104.9 degF). Exposed as ``full_recovery_energy``. This is **not** a fixed tank capacity: it moves with the setpoint, by about 140 Wh per 0.5 degC on a 65-gallon tank. During a TOU window that raises ``hpUpperOnTempSetting``, the reference is 2 degC higher (about 108.5 degF); see :ref:`two branches`. * - ``availableEnergyCapacity`` - ``x 4`` (see note) - Wh - - **Energy still needed to reach the setpoint** - a heating deficit, despite the name. Exposed as ``energy_to_setpoint``. It *falls* as the tank heats and reaches zero when the tank is fully charged, so it is the inverse of available energy. + - **Energy still needed to reach the setpoint** - a heating deficit, despite the name. Exposed as ``energy_to_setpoint``. It *falls* as the tank heats and reaches zero when the tank is fully charged, so it is the inverse of available energy. During a TOU window that raises ``hpUpperOnTempSetting``, it reaches zero about 2 degC short of the setpoint. .. note:: **Energy quantum.** The two energy fields are raw counts in a fixed diff --git a/docs/reference/protocol/device_status.rst b/docs/reference/protocol/device_status.rst index cd8425c..2d93e2a 100644 --- a/docs/reference/protocol/device_status.rst +++ b/docs/reference/protocol/device_status.rst @@ -451,12 +451,12 @@ This document lists the fields found in the ``status`` object of device status m * - ``totalEnergyCapacity`` - integer - Wh - - Energy needed for a full recovery to the current setpoint, from the device reference temperature (104.9 degF). Tracks the setpoint rather than being a fixed tank size. Python name: ``full_recovery_energy``. + - Energy needed for a full recovery to the current setpoint, from the device reference temperature (104.9 degF). Tracks the setpoint rather than being a fixed tank size. During a TOU window that raises ``hpUpperOnTempSetting``, the reference is 2 degC higher. Python name: ``full_recovery_energy``. - ``x 4`` (energy quantum) * - ``availableEnergyCapacity`` - integer - Wh - - Energy still **needed** to reach the setpoint - a heating deficit, not available energy. Falls to zero as the tank charges. Python name: ``energy_to_setpoint``. + - Energy still **needed** to reach the setpoint - a heating deficit, not available energy. Falls to zero as the tank charges; during a TOU window that raises ``hpUpperOnTempSetting``, about 2 degC short of the setpoint. Python name: ``energy_to_setpoint``. - ``x 4`` (energy quantum) DHW Operation Setting Modes diff --git a/docs/reference/python_api/models.rst b/docs/reference/python_api/models.rst index 0d8a83f..486affe 100644 --- a/docs/reference/python_api/models.rst +++ b/docs/reference/python_api/models.rst @@ -257,8 +257,8 @@ Complete real-time device status with 100+ fields. **Power/Energy Fields:** * ``current_inst_power`` (float) - Current power consumption (Watts) - * ``full_recovery_energy`` (float) - Energy needed to heat the whole tank from the device reference temperature to the current setpoint (Wh). Tracks the setpoint; not a fixed tank size. - * ``energy_to_setpoint`` (float) - Energy still **needed** to reach the setpoint (Wh). A deficit: it falls as the tank heats and reaches zero when fully charged. + * ``full_recovery_energy`` (float) - Energy needed to heat the whole tank from the device reference temperature to the current setpoint (Wh). Tracks the setpoint; not a fixed tank size. The reference is 2 degC higher during a TOU window that raises ``hp_upper_on_temp_setting``. + * ``energy_to_setpoint`` (float) - Energy still **needed** to reach the setpoint (Wh). A deficit: it falls as the tank heats and reaches zero when fully charged, or about 2 degC short of that during a TOU window that raises ``hp_upper_on_temp_setting``. * ``dhw_charge_per`` (float) - DHW charge percentage .. warning:: diff --git a/src/nwp500/models/status.py b/src/nwp500/models/status.py index 5bf4a8f..4655194 100644 --- a/src/nwp500/models/status.py +++ b/src/nwp500/models/status.py @@ -240,7 +240,9 @@ class DeviceStatus(NavienBaseModel): "the current setpoint, in Watt-hours. This is NOT a fixed tank " "size: it tracks the setpoint, rising about 140 Wh per 0.5 degC " "of setpoint increase. Use it as the cost of a full recovery, " - "not as the tank's total heat content." + "not as the tank's total heat content. During a TOU window that " + "raises hp_upper_on_temp_setting, the reference is 2 degC " + "higher (about 108.5 degF)." ), json_schema_extra={ "unit_of_measurement": "Wh", @@ -252,9 +254,10 @@ class DeviceStatus(NavienBaseModel): description=( "Energy still NEEDED to bring the tank up to the setpoint, in " "Watt-hours - a heating deficit, not stored energy. It falls as " - "the tank heats and reaches zero at the setpoint. Despite the " - "protocol name 'availableEnergyCapacity' it is the inverse of " - "available energy." + "the tank heats and reaches zero at the setpoint - or, during a " + "TOU window that raises hp_upper_on_temp_setting, about 2 degC " + "short of it. Despite the protocol name 'availableEnergyCapacity' " + "it is the inverse of available energy." ), json_schema_extra={ "unit_of_measurement": "Wh", @@ -958,12 +961,19 @@ def usable_energy(self) -> float: estimate of what can actually be drawn - water colder than that still holds heat, but not heat you can wash with. - Robust in practice: ``full_recovery_energy`` is bimodal, taking - one of two values 2 degC apart at a fixed setpoint, but both - fields shift together so the difference is unaffected. Checked - against the tank thermistors over 12275 samples, the implied tank + Usually robust: ``full_recovery_energy`` is bimodal, taking one + of two values 2 degC apart at a fixed setpoint, but both fields + shift together so the difference is unaffected. Checked against + the tank thermistors over 12275 samples, the implied tank temperature agrees to a standard deviation of 0.57 degF. + The exception is a Time-of-Use window, when + ``hp_upper_on_temp_setting`` is raised to about the setpoint. + There ``energy_to_setpoint`` reaches 0 while the tank is still up + to about 2 degC short. This value then equals + ``full_recovery_energy`` and under-reads by up to about 560 Wh on + a 65-gallon tank. In that case, use the thermistors. + Returns: Drawable energy in Watt-hours, clamped at zero. """