Repository navigation
docs: what the heating elements actually do - #142
Conversation
The four he*TempSetting fields do not describe when an element runs. In Electric and High Demand the on and off settings are the same value, which would be a thermostat with no hysteresis, and every he*DiffTempSetting reads 0. Something has to bridge that, and nothing in the status message says what. Measured on one unit by commanding the modes and watching, rather than read off a historical log: the upper element starts 1.0 degC below the setpoint, bracketed by a descending sweep — off at 0.3 and 0.8 degC, on within one 10-second poll at 1.0 and 1.2. 1.0 degC is the device's own storage granularity, which makes it the natural parameter. The page also covers what misled us first. Energy Saver engages the element on entry although heUpperOnTempSetting rests 33 degC below the tank, so watching that field explains nothing. Electric hands over from upper to lower in a single message, and while the lower element runs the *upper* probe falls about 1.9 degC before recovering — the zones are not independent, and anything treating "upper reached X" as monotonic will be wrong for several minutes. Element power metered at 5,219 W upper and 5,200 W lower against the 5,000 W at 240 V rating. And a mode write may not take effect while a TOU window is in force, which matters to anyone planning a mode change at all. Limits are stated: one unit, one supply voltage, ten bounded runs over two days. Electric and High Demand have positives at 1.0 degC and no negatives below it, so only Energy Saver's bracket is a bracket; and if another unit reports a non-zero he*DiffTempSetting, that field is the better answer and this page describes one firmware's default. Built clean: sphinx reports no warnings for the new or edited pages.
The page asserted one differential for every mode, measured in Energy Saver, and flagged Electric and High Demand as an assumption. The assumption was wrong. At the same 0.3 degC deficit, Electric runs the upper element and Energy Saver does not. Electric is now bracketed too — off with the tank 0.6 degC above the setpoint, on at 0.3 and 0.4 degC below it — so its boundary is inside (0, 0.3] degC, at or below the probe's own resolution. That is exactly what the thermostat reading of its settings predicts, since Electric parks heUpperOnTempSetting at the setpoint. Energy Saver's 1.0 degC is an entry effect and a different mechanism, which is why the two differ. High Demand shares Electric's settings and starts an element well above the boundary, but no run has approached it; that one stays an assumption and is listed as such.
High Demand was the one mode left as an assumption: it shares Electric's settings, so the page assumed it shares the threshold. Now measured — off at 0.2 degC short, on at 0.7 — which brackets it to (0.2, 0.7]. Against Electric's (0, 0.3] and Energy Saver's (0.8, 1.0], that settles the grouping. Electric and High Demand overlap only in (0.2, 0.3], so one threshold fits both, consistent with their identical heUpperOnTempSetting handling. Energy Saver's band intersects neither: at 0.7 degC short High Demand runs the element and Energy Saver does not. The page now gives one number per mode for a consumer that wants one — 0.3 degC for Electric and High Demand, 1.0 for Energy Saver, each the top of its bracket and so consistent with every observation. heUpperOnTempSetting was seen following the new setpoint in the same status message the High Demand mode landed in, which is the mechanism the thermostat reading rests on.
…umber A review of the companion work found this page contradicting itself. The summary and the per-mode tables give ELECTRIC and HIGH_DEMAND 0.3 degC, and two later paragraphs told a consumer to assume 1.0 degC for exactly those modes — the Energy Saver figure. Fixed. More substantially, the page called the 0.3 degC a thermostat. It is an entry threshold: every run measured what happens within a poll of switching into a mode. In High Demand's steady state this unit's history has the upper element on in 1 of 47 minutes 0.2-0.3 degC short and 168 of 169 minutes more than 2.2 degC short, which is nothing like a thermostat at the setpoint. The page says so now, and says not to use one figure for the other. Also: Electric's bracket rests on High Demand's negative rather than its own (its only negative is above the setpoint), and that negative held for 53 seconds as the tank drifted through it — both now footnoted rather than presented as equivalent exposure. The element power range covers all three measurements instead of pairing the upper mean from one run with the lower from another and dropping the third. And the cross-mode sentence notes that the Energy Saver runs had the compressor running while the others did not.
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Several documentation statements overstate the High Demand threshold and leave contradictory reference text and sample-count inconsistencies.
Get a fresh assessment by requesting another Copilot review.
Review effort: Lite
Findings: 7
Open (10)
Update contradictory thermostat threshold reference documentation · New Narrow measurement claim to entry-threshold runs · New Report High Demand observed threshold bracket · New Correct scope of comparison opening sentence · New Remove unsupported single High Demand recommendation · New Remove unsupported High Demand consumer threshold · New Correct mode-dependent threshold in protocol reference · New Correct unsupported High Demand changelog threshold · New Separate Energy Saver observation from causal explanation · New Reconcile bounded run count · New
What changed in this PR
Adds measured documentation of NWP500 heating-element behavior and links it from the protocol reference.
Changes:
- Adds heating-element thresholds, handover, coupling, power, TOU behavior, and limits.
- Registers the new explanation page.
- Updates protocol notes and changelog.
| File | Description |
|---|---|
docs/explanation/heating-elements.rst |
New measured behavior reference |
docs/explanation/index.rst |
Adds the page to the explanation index |
docs/reference/protocol/device_status.rst |
Links and summarizes findings |
CHANGELOG.rst |
Records the documentation addition |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| The NWP500 publishes four element thermostat settings - | ||
| ``heUpperOnTempSetting``, ``heUpperOffTempSetting`` and their lower-zone | ||
| pair - plus a differential for each. Read literally they do not describe | ||
| when the elements actually run: in Electric and High Demand the on and | ||
| off settings are **the same value**, and every ``he*DiffTempSetting`` | ||
| reads 0. The device nevertheless has a differential, and this page | ||
| gives the measured one. |
There was a problem hiding this comment.
Fixed. data_conversions.rst now opens the Heating Element Control Temperatures section with a warning that the field descriptions below are the protocol's own semantics and do not hold against the device, naming all three ways they fail. The heUpperOnDiffTempSetting row's "Typically 2-5°F" now reads "Nominally 2-5°F, but measured as 0 on a 9.x-firmware unit in every mode, while the device nonetheless applied a differential". Two reference pages contradicting each other was the worst outcome here.
| Everything here was measured on one unit by commanding the modes and | ||
| watching, not inferred from historical logs. That distinction matters: | ||
| two earlier values for the start threshold came from "the smallest | ||
| deficit ever seen to start an element" in a passive record, and both | ||
| were wrong, because such a figure measures where the tank happened to | ||
| sit rather than where the device decides. |
There was a problem hiding this comment.
Fixed. Narrowed to "The start thresholds here were measured on one unit by commanding the modes and watching", plus a new paragraph naming the two passages that are not from commanded runs — High Demand's steady-state re-engagement and the mid-stint behaviour it is contrasted with — so the reader can tell which is which.
| - **Mode-dependent.** ``ENERGY_SAVER`` needs **1.0 degC | ||
| (1.8 degF)** below the setpoint. ``ELECTRIC`` and ``HIGH_DEMAND`` | ||
| need about **0.3 degC (0.54 degF)**. All three are bracketed by | ||
| runs on either side. |
There was a problem hiding this comment.
Fixed, and this was the important one. The table now reports HIGH_DEMAND as (0.2, 0.7] degC rather than 0.3. Merging it with Electric came from overlapping brackets plus identical heUpperOnTempSetting handling, which is an argument for a shared threshold, not a measurement of one — and I had written it into a summary table as the latter. The Limits bullet claiming no run approached High Demand's boundary was stale from before those runs and is replaced with the real gap: where inside (0.2, 0.7] it falls. Also carried into the consuming repo's model, which was applying Electric's number to both modes (eman/dhw-sensor-apps#263).
| The start differential | ||
| ====================== | ||
|
|
||
| Two modes, two different answers. Each run holds a fixed deficit and |
There was a problem hiding this comment.
Fixed: "Three modes, three brackets, and no two of them the same width." The sentence predated the High Demand table.
| A consumer wanting one number per mode can take **0.3 degC** for | ||
| Electric and High Demand and **1.0 degC** for Energy Saver. Both are the | ||
| top of their bracket, which is the value consistent with every | ||
| observation. |
There was a problem hiding this comment.
Fixed. The guidance now says to take the top of each mode's own bracket — 0.3 / 0.7 / 1.0 degC — defined as the smallest deficit at which that mode was seen to start an element, and spells out the asymmetry: borrowing Electric's 0.3 predicts an element at 0.4 degC short that High Demand has never been seen to run, which is the expensive direction if the consumer is deciding whether hot water will be ready.
| A consumer that wants to predict when an element will run therefore | ||
| cannot read it from the status message; it has to assume the per-mode | ||
| figures above - 0.3 degC for ``ELECTRIC`` and ``HIGH_DEMAND``, 1.0 degC | ||
| for ``ENERGY_SAVER`` - and treat a tank inside the corresponding | ||
| bracket as a case where the device may go either way. |
There was a problem hiding this comment.
Fixed in the same pass — 0.3 for ELECTRIC, 0.7 for HIGH_DEMAND, 1.0 for ENERGY_SAVER, with a note that High Demand's bracket is the widest so its "may go either way" span is widest too. The "What the on-setting does and does not tell you" bullet repeated the merged figure as well; that is fixed.
| * The ``he*TempSetting`` fields do **not** describe when an element | ||
| runs: in Electric and High Demand the on and off settings are equal, | ||
| every ``he*DiffTempSetting`` reads 0, and the measured differential is | ||
| 1.0 degC below the setpoint. See :doc:`/explanation/heating-elements`. |
There was a problem hiding this comment.
Fixed. The note now gives all three brackets and says the differential is mode-dependent and measured on entry, instead of a single 1.0 degC — which was Energy Saver's number applied to every mode.
| **mode-dependent**, and all three modes are bracketed by runs on | ||
| either side: ``ELECTRIC`` and ``HIGH_DEMAND`` need about **0.3 degC** | ||
| below the setpoint, ``ENERGY_SAVER`` needs **1.0 degC**. At 0.7 degC | ||
| short, High Demand runs the element and Energy Saver does not. These | ||
| are **entry** thresholds, not thermostats: in High Demand's steady | ||
| state this unit's history has the element on in 1 of 47 minutes |
There was a problem hiding this comment.
Fixed. The changelog now gives one bracket per mode and adds that whether Electric and High Demand share a threshold is an open question, not a finding.
| - Switching into ``ENERGY_SAVER`` engages the upper element even | ||
| though ``heUpperOnTempSetting`` rests 33 degC below the tank - | ||
| an entry effect rather than that thermostat, and the reason its | ||
| differential differs from Electric's. |
There was a problem hiding this comment.
Fixed. The summary row now stops at the observation — the element engages although heUpperOnTempSetting rests 33 degC below the tank, so that field is not what drives it — and states plainly that why the modes differ is not established. Added the same to the body paragraph: the runs differ in compressor state as well as mode, and nothing here separates the two.
| One unit, one supply voltage, 2026-09-19 and 2026-09-20, thirteen | ||
| bounded runs in all. Specifically **not** established: |
There was a problem hiding this comment.
Fixed, and both numbers were wrong. It is 22: thirteen on the elements and thresholds, nine on mode writes and reservation entries under TOU (one JSONL file per run). The page and the PR body now both say so, with the split spelled out.
…d it The review is right on every count. The page measured three modes and then reported two numbers, merging Electric and High Demand onto 0.3 degC. High Demand's own runs are off at 0.2 and on at 0.7, so its bracket is (0.2, 0.7] — more than twice Electric's width, with nothing watched in between. The brackets overlap and the two modes park `heUpperOnTempSetting` identically, so they may well share a threshold; that is an argument, and the page presented it as a measurement. Corrected in the summary table, the comparison, the consumer guidance, `device_status.rst` and the changelog. Each mode now carries the smallest deficit at which it was seen to start an element, and the Limits section asks the question that would settle it: one run at 0.4 degC. Also from the review: - `data_conversions.rst` still described these fields as literal ON/OFF thermostats with a "typically 2-5°F" differential, which is exactly what this page disproves. Readers had two reference pages contradicting each other. It now carries a warning, and the differential row says the field measured 0 in every mode. - The blanket "everything here was measured by commanding the modes" was false — High Demand's steady-state figures are history. Narrowed, and the two history-derived passages are labelled. - "Two modes, two different answers" preceded three tables. - Energy Saver's entry behaviour was written as the *reason* its threshold differs from Electric's. It is an observation; the runs differ in compressor state as well as mode, as the page itself says two paragraphs later. - The run count was thirteen on the page and ten in the PR body. It is 22: thirteen on the elements, nine on TOU mode writes and reservations. Unprompted, on the same principle: the upper-zone rate was quoted as a single 1.61 degF/min when it is 1.56-1.61 depending on sampling resolution. Refs #142
The warning added with the mid-stint figures said this unit's element binary sensors "assert one-minute elements the power meter does not see", with 9 of 112 apparent starts drawing 500-750 W or less. That is wrong, and an adversarial review of the analysis behind it caught it. The elements were real. Pulling raw samples: on 2026-04-12 the flag went on at 22:21:58 and off at 22:22:18, and currentPower inside that window reads 5558 W and 5547 W, with the compressor steady at ~470 W either side. A 20-second element at full power. The "538 W" that made it look phantom was the last sample at or before the minute boundary, taken by a one-minute resample that used a backward as-of join instead of a maximum. currentPower samples at a median 30 s with 24 % of gaps over a minute, so a sub-minute burst falls between the samples such a resample keeps. What is true, and more useful, is that element runs come in two lengths. Of 128 raw upper-element runs, 31 (24 %) are under a minute, median 9 s, and together carry 0.8 kWh across nine months against 97.9 kWh for the other 97. A 20-second burst moves the upper probe about 0.1 degC, at its own resolution — which is why "the tank did not move" looked like corroboration and was not evidence either way. So the note now describes the sampling hazard rather than accusing the sensor: resample power with a maximum or work from raw edges, and filter on duration or energy when predicting recovery, because it is the sustained runs that heat the tank. No mid-stint figure changes: the review confirmed the Energy Saver and High Demand results are identical with or without the power filter. Refs #142
…aims A commanded run on 2026-09-21 answers what the recorded history on this page could not. With the setpoint fixed for almost the whole record, "the element engages at heUpperOnTempSetting" and "it engages a fixed gap below the setpoint" made the same prediction. So the setpoint was lowered to 42.0 degC and the unit left in ENERGY_SAVER. A fixed gap predicted engagement near 22 degC; it engaged three times with the upper probe at 39.8. The on-setting is what drives it — with two cautions stated: every engagement came while water moved through the tank, and readings of 40.4, 40.1 and 40.0 did not trigger, so the working threshold may sit a little under the published 40.5. Three further behaviours from the same run, each with its n: - The element runs to ~0.9 degC under the setpoint-tracking heUpperOffTempSetting, not to a fixed point. Only engagement is fixed. - Raising the setpoint in ENERGY_SAVER starts the element, through the entry rule (n = 1). A controller writing setpoints in that mode can start a 5 kW element. - Whether water movement is required, or temperature suffices, cannot be separated: all three engagements had it, one from a shower and two from a recirculation pump on a cooled loop. Two retractions. The short-element warning said a 20-second burst moves the upper probe ~0.1 degC, "below its own resolution. They heat nothing." That divided by the whole tank's heat capacity; the upper element heats the upper zone, a third of it. A 20 s burst is ~0.3 degC, and measured 41-58 s bursts moved it 0.7-1.5 degC. The counts are also restated on a clean basis — upper element only, commanded test days excluded, at the fitted 5,148 W: 115 runs, 20 under a minute. And the page said heUpperOnTempSetting rests at 40.5 degC in both HEAT_PUMP and ENERGY_SAVER. True of Energy Saver (96 % of minutes on this unit); in Heat Pump only 48.5 %, and the other values match the current setpoint in only 6 % of minutes, so it is not tracking the setpoint either. Marked as unreliable in Heat Pump mode. Refs #142
Review on #143 found four places that said the published thermostat description holds later in a stint, then gave High Demand — which re-engages ~2.1 degC *below* its ON setting — as a counterexample in the next clause. The evidence supports the ON-setting description for ENERGY_SAVER only. - The page's opening said the four fields do not describe when the elements run, which contradicted the row beneath it saying they are the rule later in a stint. It now says how far they hold by mode and moment: not on entry to any element mode; yes later in ENERGY_SAVER (history, confirmed by a commanded run); no later in HIGH_DEMAND (history only); ELECTRIC uncharacterised. - CHANGELOG, data_conversions.rst and device_status.rst said "the reference description is the rule" / "the descriptions below hold" / "later in a stint they do". Each is now scoped to the mode. - data_conversions.rst also still said the ON setting sits 33 degC below the tank in HEAT_PUMP and ENERGY_SAVER. This PR already found that true of Energy Saver and unreliable in Heat Pump; scoped to match. The fifth comment, on the changelog's short-element counts, was already addressed in 9ad7f34. Refs #142


The NWP500 publishes four element thermostat settings and a differential for
each. Read literally they do not describe when the elements run: in Electric and
High Demand the ON and OFF settings are the same value, in Heat Pump and
Energy Saver the ON setting rests at the 40.5 degC device minimum while the
element still runs, and every
he*DiffTempSettingreads 0.This adds
docs/explanation/heating-elements.rstwith the measured behaviour,and fixes the reference pages that said otherwise.
The start differential is mode-dependent
Each mode has a run on either side of its bracket, except Electric's lower edge
— its only negative sits above the setpoint.
ELECTRICHIGH_DEMANDENERGY_SAVERElectric and High Demand overlap in (0.2, 0.3] and handle
heUpperOnTempSettingidentically, so they may share one threshold — but High Demand has never
been watched between 0.2 and 0.7, so that is an inference and the page says so.
A consumer should take the top of each mode's own bracket.
These are entry thresholds, not thermostats. They measure what happens
within a poll of switching into a mode. In High Demand's steady state this
unit's history has the element on in 1 of 47 minutes 0.2–0.3 degC short and
168 of 169 more than 2.2 degC short.
Also measured
heUpperOnTempSettingrests 33 degC below the tank. Why its bracket sits higher than Electric's is
not established — those runs had the compressor running and the others did
not.
runs, then recovers. Nothing should treat the upper zone as monotonic.
then coasts 1.3 degC past it.
5,000 W @ 240 V rating.
minutes, nothing logged, twice — and is not discarded either: it landed one
poll after TOU was switched off. Confirm mode writes by reading them back.
Reservation entries fire normally in a window (docs: reservations are NOT suspended by TOU — measured on the device (schedule-operation.rst) #141).
Reference pages corrected
data_conversions.rstdescribed these fields as literal ON/OFF thermostats witha "typically 2–5°F" differential, contradicting the new page. It now carries a
warning and the differential row records that the field measured 0 in every
mode.
device_status.rst's note no longer collapses the result to one number.Scope
One unit, one supply voltage, 2026-09-19 and 2026-09-20, 22 bounded runs —
thirteen on the elements and thresholds, nine on mode writes and reservations
under TOU. Not established: where inside (0.2, 0.7] High Demand's boundary
falls; what engages the element later in an Energy Saver stint; anything past
six minutes; whether any of it is firmware-dependent.
Docs build clean (no new Sphinx warnings against baseline).
Refs #141