Tank Energy¶
The NWP500 reports two energy figures, totalEnergyCapacity and
availableEnergyCapacity. Both names mislead, and before v10.0 this
library also scaled them wrongly. This page explains what they
actually measure and shows the evidence for the correction.
The short version¶
Protocol field |
What the name suggests |
What it actually is |
|---|---|---|
|
Energy available in the tank |
Energy still needed to reach the setpoint. It falls as the tank heats and hits zero when the tank is fully charged - the exact inverse of the name. |
|
Fixed tank capacity |
Cost of a full recovery to the current setpoint. It moves whenever the setpoint moves. |
Both are raw counts of 4 Wh each, not Watt-hours. Library versions before 10.0 multiplied by 10, overstating tank energy by 2.5x.
Both are also measured from the setpoint, so both describe potential rather than content: move the setpoint and both change while the water in the tank does not. Neither is a state of charge.
Their difference is a state of charge, and is exposed as
DeviceStatus.usable_energy:
usable_energy = full_recovery_energy - energy_to_setpoint
= k * (tank_temperature - 104.9 degF)
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.
How the fields behave¶
Both fields fit a single two-parameter model:
energy_to_setpoint = k * (setpoint - tank_mean_temperature)
full_recovery_energy = k * (setpoint - reference_temperature)
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.
Two consequences follow, and both matter:
energy_to_setpointis a deficit. Code that treats it as stored energy has the signal backwards: it is largest when the tank is coldest.full_recovery_energyis not a constant. Raising the setpoint by 0.5 degC raises it by 35 counts, about 140 Wh, on a 65-gallon tank. Seven months of history on one device shows sixteen distinct values as the setpoint was adjusted.
The evidence¶
Deficit, not stored energy¶
During a heating recovery on a 65-gallon unit at a 140.9 degF setpoint, with the tank warming and no draws. Each row is a mean over the samples in that temperature bin, so the counts are not whole numbers:
Mean tank temp |
Setpoint minus tank |
mean |
|
|---|---|---|---|
119.5 degF |
21.4 degF |
881.6 |
54.5 % |
127.4 degF |
13.5 degF |
530.0 |
69.5 % |
135.1 degF |
5.8 degF |
227.5 |
85.7 % |
The field falls as the tank fills with heat. Regressed against mean tank temperature over two weeks of five-minute samples, the slope is negative with an R-squared of 0.93 and a zero crossing at the setpoint.
The last two rows also check out against the tank’s heat capacity of
156 Wh/degF: 227.5 counts x 4 Wh = 910 Wh against 5.8 degF x 156 =
905 Wh, and 530.0 counts = 2120 Wh against 13.5 degF x 156 = 2106 Wh.
The coldest row runs about 6 % high, which is the stratification error
in using (upper + lower) / 2 as the mean tank temperature - it is
worst when the tank is least mixed.
The 4 Wh quantum¶
totalEnergyCapacity is a whole-tank quantity, so its slope against
the setpoint measures the quantum without needing any assumption about
how the tank stratifies.
The device does not report a single totalEnergyCapacity per setpoint -
see Two branches below - so the table lists the most common value at
each setpoint, which covers 68 % of samples. Values are raw counts as
they arrive on the wire:
Setpoint |
|
Setpoint |
|
|---|---|---|---|
140.0 degF |
1369 |
144.5 degF |
1545 |
140.9 degF |
1404 |
145.4 degF |
1580 |
141.8 degF |
1439 |
146.3 degF |
1615 |
142.7 degF |
1475 |
147.2 degF |
1650 |
143.6 degF |
1510 |
148.1 degF |
1685 |
An arithmetic sequence: least squares gives R-squared 0.99999 and a slope of 70.25 raw counts per Kelvin of whole-tank temperature rise. The endpoints alone give the same figure: (1685 - 1369) / 4.5 K = 70.2.
The slope is the robust part of this. The second branch, fitted separately, gives 38.98 counts/degF against the primary’s 39.06 - the same figure to within 0.2 %. Two independent populations agreeing on the slope is stronger evidence for the quantum than either alone.
Converting that to Watt-hours needs a water mass, and this is where care
is required: a “65 gallon” tank does not hold 65 gallons of water. The
nameplate is an upper bound - the vendor’s own app hard-codes it, mapping
volumeCode 1/2/3 to 189.2 L, 246.0 L and 302.8 L (see What the
vendor app does with these fields) - and the water actually in the tank
must come in under it. Rather than assume nominal volume and derive an
odd-looking quantum, assume the quantum is a round number - every other
conversion in this protocol is (half-degrees, tenths) - and see which one
implies a sensible volume:
Candidate quantum |
Implied water volume |
Plausible? |
|---|---|---|
4 Wh (1/250 kWh) |
241.7 L / 63.9 gal |
Yes - slightly under nominal, as expected |
1/240 kWh (4.167 Wh) |
251.8 L / 66.5 gal |
No - more than the nameplate |
10 kJ |
167.8 L / 44.3 gal |
No |
15 kJ |
251.7 L / 66.5 gal |
No |
4 Wh per count is the only round candidate implying a volume below the nameplate, which is the only physically sensible direction. The library uses 4.0.
A second, noisier method agrees. Across 183 individual heating
recoveries, dividing each tank sensible-heat gain (from the two
thermistors and the nominal mass) by the device’s reported change gives a
median of 4.11 Wh/count, p10 3.47 and p90 4.45. That route depends on
(upper + lower) / 2 approximating the true mean tank temperature, so
it is far less precise, but it is an independent confirmation and it does
not use electrical input at all.
The efficiency cross-check¶
A third check rules out the old scale on its own. Integrating
currentInstPower over each recovery gives the electrical energy in,
and dividing the device’s reported energy gain by it gives an implied
coefficient of performance:
Scale used |
Implied COP (median) |
Verdict |
|---|---|---|
4 Wh/count (corrected) |
2.89 |
Normal for a heat pump water heater |
10 Wh/count (pre-10.0) |
7.02 |
Physically impossible |
A heat pump water heater in a 72 degF room runs at a COP of roughly 2 to 4. A COP of 7 would mean the device generated energy it never consumed.
This argument is worth stating separately because it needs no tank volume, specific heat or stratification model - only the device’s own reported energy and its own reported power. It cannot tell you what the quantum is, but it rules out the pre-10.0 value regardless of anything assumed elsewhere on this page.
Two branches¶
totalEnergyCapacity is not a function of the setpoint alone. At a
fixed setpoint it takes one of two values, flipping between them several
times a day. Over four months at nine setpoints:
Branch |
Share |
Slope |
Zero crossing |
|---|---|---|---|
Primary |
68 % |
39.06 counts/degF |
104.95 degF |
Secondary |
32 % |
38.98 counts/degF |
108.48 degF |
The two are parallel, separated by a constant 140-141 counts - exactly 2 degC of setpoint - at every setpoint measured.
The primary branch’s zero crossing matches the device’s
dhwTemperatureMin of 104.9 degF to within a twentieth of a degree -
140.0 - 1369 / 39.06 = 104.95 degF - using only the device’s own two
numbers: no tank mass, no specific heat, no thermistors, no assumption
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.
Warning
Because of this, do not derive the tank’s heat capacity from a live
full_recovery_energy reading: landing on the wrong branch gives an
error of about 9 %. Use the slope, which is stable across both
branches, or compute stored energy from the thermistors directly.
What dhwChargePer does¶
dhwChargePer is a fourth signal and does not reconcile with the other
two. On one device, energy_to_setpoint / full_recovery_energy
implies 30 % charged while dhwChargePer reads 59 %; over two weeks
the two differ by a mean of 48 points with 32 points of scatter.
It is nonetheless the number Navien shows its own users: the NaviLink app prints it unmodified as a percentage labelled “DHW Charge”, with no client-side arithmetic of any kind. Whatever it means, it is computed on the device, and a user comparing the app against this library will see the app’s figure and not the ratio above.
Treat it as an opaque vendor heuristic rather than a defined fraction of
anything. For a charge figure with defined meaning, use
usable_energy.
What the vendor app does with these fields¶
Nothing. Navien’s own NaviLink app never reads either field.
Decompiling the current release - version 2.03.00, versionCode 141,
published March 2026 - gives 8,101 Java sources, and neither
totalEnergyCapacity nor availableEnergyCapacity appears in any of
them. Neither string appears in the raw dex string pool either, which
rules out the names having been lost to obfuscation. The app’s status
model, KDResponseMgppStatus.Status, declares about 140 fields -
including dhwChargePer, tankUpperTemperature,
tankLowerTemperature, currentInstPower and mixingRate - and
neither energy field is among them. The app requests no field subset, so
the device sends both and the app discards them on deserialization.
As a control, dhwChargePer and tankUpperTemperature are present
in the dex strings, so the absence of the other two is a real result and
not a broken search.
This matters for reading the rest of this page. There is no vendor label, no vendor scale factor and no vendor formula to check the conclusions above against - the evidence here is the only account of these two fields that exists. It also explains why the protocol names are so misleading: nothing Navien ships ever has to act on them.
The app does corroborate the surrounding facts this page leans on:
What the app does |
What it confirms |
|---|---|
|
The nominal volume the quantum candidates are judged against is the vendor’s own figure, not an assumption of ours |
|
|
The status screen labels |
The vendor UI does not treat |
Drawable energy¶
DeviceStatus.usable_energy is the difference of the two fields, and
is the one number here that describes the tank’s state rather than its
distance from a target:
usable_energy = full_recovery_energy - energy_to_setpoint
Raising the setpoint inflates both inputs equally, so the result does not move - which is what makes it a state of charge and the two raw fields not.
The implied reference is dhw_temperature_min, 104.9 degF. A shower
runs around 105 degF, so heat below that reference is real but not
useful, and excluding it is the behaviour you want. Note that a mixing
valve does not change this floor: it caps how hot water can be
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
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 cold inlet - compute it from the thermistors instead. On a 65-gallon tank the heat capacity is 156 Wh per degF:
drawable_Wh = 156 * (tank_mean_temperature - your_floor_degF)
Migrating from before v10.0¶
Old |
New |
|---|---|
|
|
|
|
Treating the value as available energy |
It is a deficit; invert the logic. |
Treating the value as a percentage |
It never was one |
The old attribute names are removed rather than aliased, so a
rename that is missed fails immediately with AttributeError instead
of silently returning a number 2.5x too large.
If you logged these values historically, the stored series needs rescaling by 0.4 to be comparable with values from v10.0 onward.
See also¶
Energy Monitoring Guide - Monitoring energy and power
Data Conversions and Units Reference - Protocol field conversions