09: The power estimate and how it was calibrated
The firmware publishes the A/C’s power, voltage and current from bytes the unit already sends on the bus, and Home Assistant’s Energy dashboard can use the result. This page says what the numbers are, how far to trust them, and how the one constant behind them was measured. Read it before you rely on the kWh figure, or if you want to calibrate against your own meter.
What is published
| Entity | Source |
|---|---|
| Power (W) | 4.15 * raw^2, where raw is status byte 55. Unknown above the hub’s max_power |
| Voltage (V) | status byte 50, in whole volts as the unit reports it |
| Current (A) | derived: P / (V * 0.95). Unknown when the power is |
| Energy today (kWh) | ESPHome’s total_daily_energy on the Power sensor, in packages/hisense-ac.yaml |
The values are published when they change and at least once a minute. Power and current read 0
while the compressor is off. The energy sensor integrates on the node with method: left (the
value between two updates is the older one) and resets at midnight. Home Assistant’s long-term
statistics hold the history.
The model is active_power_mw() and active_current_ma() in
firmware/esphome/components/hisense_ac/hisense_telemetry.h, pure functions with host tests
(test_esphome_codec_parity.cpp holds them equal to the reference in
firmware/test/reference-driver/power_estimate.h for every byte value the reference does not
clamp, and test_esphome_map_gaps.cpp covers their bounds).
The limit: max_power
An estimate above the hub’s max_power option (watts, default 5000, 500 W to 50 kW) is not
published as a number. Power goes out as unknown, and so does current, since it is computed from
the power. power_reading() in the same header makes that decision, with its own host tests.
The estimate is the square of one byte, and nothing but the frame checksum guards that byte: a value of 255 reads as 270 kW. Before this limit the firmware clamped the estimate at 50 kW and published it, and the energy sensor summed it. 5 kW is generous for a single-split residential unit (the highest point in the calibration below is 934 W, raw 15). With the default, raw 34 (4797 W) is the highest value published and raw 35 (5084 W) the first that is not.
What an unknown stretch does to the energy figure: ESPHome’s total_daily_energy ignores an
unknown value, so nothing is added while it lasts, and the next reading adds the whole gap at
the last power published before it (method: left). A long run of bad bytes during a change of
load is therefore counted at the old load. An unknown value is published once and then with the
once-a-minute refresh, not with every status frame.
How far to trust it
It is an estimate, not a meter. These inverter splits almost never carry a revenue-grade meter, and the vendor’s cloud figure is an estimate too: either a value the indoor MCU reports (it senses bus or compressor current for inverter control) or a model from compressor frequency and mode.
- The constant comes from two non-zero points on one unit. The quadratic fits both to under 1 %, which supports the shape, but a third steady point was never taken.
- The power factor is assumed (0.95, typical for an inverter with active PFC). At deep part load the real power factor is lower.
- Byte 50 reads about 6 % below a panel meter (about 220 against 234 to 236 V). The power figure does not depend on it. The derived current does.
- The only plausibility check on byte 55 is
max_power(above). A wrong byte that still gives an estimate under the limit is published as a reading. - No comparison against the vendor app’s numbers is on record.
The calibration
Reference: a PD3-63VA panel meter on the phase the A/C is on (L3). Three points, matched by time
(photo EXIF against the logger’s [HH:MM:SS]). Power is V * dI(L3 minus baseline) * PF(0.95):
| Time | L3 A | dI (A/C) | P (W) | byte 55 | byte 42 (Hz) |
|---|---|---|---|---|---|
| 22:07:36 | 1.1 | 0.0 | 0 | 0 | 0 |
| 22:12:32 | 2.3 | 1.2 | 268 | 8 | 31 |
| 22:10:48 | 5.3 | 4.2 | 934 | 15 | 68 |
Byte 55 tracks the square root of power, not amps. A linear fit gives incompatible slopes (33 against 62 W per count). A quadratic through the origin fits both load points to under 1 %:
P_watts = 4.15 * raw55^2 (PF 0.95 folded in)
raw 8 -> 266 W (meter 268) raw 15 -> 934 W (meter 934)
The derived current, for comparison: raw 15 gives 4.47 A (meter 4.2), raw 8 gives 1.27 A
(meter 1.2). Byte 60 gives the same curve (about 0.66 * raw60^2) as a cross-check. The tap that
logged the frames has since been removed, so a fourth point is deferred.
The bytes behind it
From the three-state correlation in
reverse-engineering/docs/03-rs485-ac-protocol.md
(cool at 60 Hz, off at 0 Hz, heat at 50 Hz, mapped 2026-07-07):
| Byte | Field | Confidence | Use |
|---|---|---|---|
| 50 | supply voltage (V) | high: 218 V loaded, 222 V idle (sags under load), consistent with 230 V mains | the Voltage sensor |
| 55 (= 145) | first labelled “phase or bus current A”: zero when the compressor is off, tracks load. The calibration showed it follows the square root of power | calibrated above | the Power sensor |
| 56 (= 144) | “phase or bus current B” | medium | not used |
| 60 | “active current or power reading”, on the same curve as byte 55 | medium | cross-check only |
| 42 | compressor frequency, actual (Hz) | confirmed | the Compressor frequency sensor |
| 41, 43 | compressor frequency, target (Hz) | confirmed | not used |
| 45 | outdoor (condenser) coil temperature (°C) | confirmed (reverses between cool and heat) | the Coil temperature sensor |
What was planned and not built
- A frequency model as a second path (
P = a * comp_freq + b * fan_level + P_standby), which is what most community inverter A/C integrations use. It would not depend on byte 55 and could bound it. It was never implemented, so there is no cross-check at run time. - A fitting script (
tools/fit_power.py) for a least-squares fit from recorded pairs. It was never written. The constant above was fitted by hand. - Coefficients that can be changed without a rebuild. The constant is compiled in
(
POWER_MW_PER_COUNT2). - A lifetime energy counter on the node. The reference
power_estimate.hcarries a pure, host-tested integrator pair (hisense_energy_add,hisense_energy_mwh) that nothing calls: a lifetime counter would need persistence to survive a restart.total_daily_energyand Home Assistant’s statistics do the job.
On the retired Matter builds (tag matter-final) the same three values were attributes of
ElectricalPowerMeasurement on endpoint 1, and energy was left to a Riemann-sum helper in Home
Assistant. That cluster is delegate-based in connectedhomeip, which made it the most awkward
endpoint of those builds.
Calibrating against your own meter
You need a reference that reads the A/C alone: a clamp meter on the A/C’s live conductor, or a panel meter on its circuit with the baseline subtracted. A clamp that reads true power (W) is best. One that reads only amps gives apparent power, and you have to assume a power factor.
Safety. This means working at the mains lead of the A/C. Clamp around a single insulated conductor, never bare metal. If the terminal block is not safely reachable, clamp at the isolator or the plug lead. Do not open anything live that you are not qualified to open.
- Log byte 55 next to the meter. With the node’s log at
VERY_VERBOSEthe raw status frames are in the log. With a tap on the bus,reverse-engineering/tools/decode_ac_frames.py --port <tap>decodes them, andfirmware/esp32-recon/shows them on a console. - Hold three or four steady states and note the meter at each: off, a low cool (about 40 Hz), a hard cool or turbo (about 69 Hz), heat (about 50 Hz).
- Fit
kinP = k * raw^2through the origin and compare with 4.15. - A different constant goes into
POWER_MW_PER_COUNT2inhisense_telemetry.hand into the referencepower_estimate.h, in one commit, or the parity test fails. Say in the commit what it was measured with.