Part 4 of the series "Smart Home in Practice". Part 3 ended with the question of how you know what hides behind register 12 when no datasheet matches. Here is the answer - and it works for any Modbus device, not just heat pumps.
Modbus is a wonderfully simple protocol: a master asks "give me register 12", the slave answers with a number. What the number means is written elsewhere - in the datasheet, and if that is missing or simply wrong, you are facing a device that answers politely and reveals nothing. That is exactly where I stood with the heat pump from Part 3: it answers Modbus requests, but follows none of the published register maps - not even with an offset.
What followed became a small discipline of its own. The tools for it are now part of the firmware and run as buttons and switches directly in Home Assistant.
The iron rule: read yes, write blindly never
Before any scanner runs, one rule that outlives everything else: an unknown register is never written to in order to find out what it does. On a temperature sensor that would not matter. On a heating system, unknown coils may hold things like emergency stop, emergency operation, or a disinfection cycle that heats the hot water tank to 70 degrees for hours - in LG's official map, exactly these functions sit on neighbouring positions.
Add a subtlety that is easily missed: reading back proves nothing. If you write a 1 into a coil, read back a 0 and conclude "did not work", you may just as well have hit an edge-triggered bit - one that performs its action and resets itself. Without logging the bus's answer in detail (exception? acknowledgement? what does the read-back return, and when?), such a test is worthless and potentially dangerous.
Step 1: probing the address space
The first scanner answers the question of where anything is at all. It stubbornly polls addresses and notes which ones answer and which are rejected with exception 2 ("illegal data address"). Even the error message is a signal: a device sending exception 2 exists and talks - it merely rejects that address. Total silence, on the other hand, means: wrong baud rate, wrong address, wrong protocol or wrong wiring.
The deep scan across all 65,536 addresses per register type found 32 answering points on this heat pump, spread across a few islands. But it has a weakness that would have cost me two registers: for time reasons such a scan polls in groups - and if a device rejects a whole group as soon as a single register in it does not exist, the existing neighbours vanish along with it.
Hence the second pass: individual polls across the nearby address space. It found five registers the grouped scan had missed - among them, of all things, silent mode. The lesson as a mnemonic: grouped requests find things fast, individual requests find everything.
| Approach | Strength | Weakness |
|---|---|---|
| Deep scan in groups | whole address space in acceptable time | hides registers when the group has a hole |
| Individual polls | finds every answering register | too slow for the whole address space |
| Passive listening | completely risk-free | only shows what is spoken anyway |
Step 2: watching registers change
Now you know where things are - but not what they are. The strongest tool for that is strikingly simple: an observation mode. Switched on, the firmware polls all known registers faster than usual and reports every change in plain text: which point, old value, new value, cumulative list since switch-on.
The workflow is manual labour with instant reward: switch observation on, walk to the control panel, change exactly one thing - hot water setpoint from 48 to 50 -, walk back and read off which register moved. Second-precise timestamps make the attribution unambiguous. That is how an anonymous holding register turned out to be the writable operating mode, and a coil turned out to be silent mode - both of which the published maps declared unreachable through this port.
Two details make the mode fit for daily use: it has no write access whatsoever to the bus, and it switches itself off after 30 minutes - a forgotten observation mode can neither misconfigure the machine nor load the bus permanently.
One trick saves extra time: some control panels show the same raw values in their service menus that are also on the bus. If the display literally says "refrigerant 12000" and one register constantly returns 12000, the attribution comes for free.
Step 3: physics as the witness
For registers that cannot be changed at the control panel, the most convincing line of evidence remains: the value has to behave physically right. A practical example: two registers were suspected of being refrigerant circuit pressures. If you compute the refrigerant's boiling temperature from the suspected pressure and compare it with an independently measured temperature, the two must agree across every operating state - not once, but monotonically over hours. If they do, the mapping is proven; Part 5 shows the full calculation.
Conversely, physics also unmasks misreadings: a register rumoured to be the "compressor frequency" held a constant 10 to 15 kelvin above outdoor air across nine hours of standstill. A frequency would be zero with the compressor stopped - a housing temperature would not.
The pitfall that nearly cost weeks: block grouping
The most unpleasant lesson of the whole mapping has nothing to do with the device but with one's own tooling. ESPHome automatically merges neighbouring registers into block requests, and that grouping changes with every edit of the register list. On this unit, that shifted answers between neighbouring registers: one register returned 100 percent physically impossible values - up to 451 degrees - across three consecutive firmware states, and plausible ones again in the fourth. The jumps sat exactly on the firmware boundaries, not on operating states.
The insidious part: precisely during one of the corrupted phases, a display comparison produced a misattribution that survived for weeks. Two rules follow. First, in ESPHome every register gets a force_new_range: true - the individual requests cost bus load, but they can no longer shift. Second: a time series spanning several firmware states is not a dataset. Limit any analysis to one state, or you end up correlating artefacts.
When nothing answers at all: it may be the wrong protocol
Not every RS485 terminal speaks Modbus. The line that used to hold this unit's old cloud gateway stayed completely silent through 988 Modbus requests across 247 addresses and four baud rates - no exception, nothing. That silence is a measurement in itself: that line runs LGAP, LG's proprietary protocol with its own frame format and checksum rule, for which a community project with a protocol analysis exists. For that territory, the rule from above applies in sharpened form: the project documents how control attempts over LGAP have genuinely misconfigured a machine. Observe, do not replay.
Even banal mistakes have a clear signature there: swapped wires produce not answers but a single null byte per request - and the RS485 transceiver's activity LED glows permanently instead of blinking.
Where it ends up
After scanners, observation and physics cross-checks, 34 data points are mapped on this heat pump, five of them writable - each documented with its line of evidence in the register map in the repository. The honest part of the balance: a remainder stays open, a few registers answer and keep quiet about their meaning. They remain what they are - observed unknowns. None of them gets written to until an observation gives them away.
Part 5 then turns the mapped registers into the thing the whole effort was for: a refrigerant circuit diagnosis that shows more than the vendor app ever did.
Sources
- GitHub: lg-therma-v-esphome-modbus - firmware with scanners, observation mode and documented register map
- ESPHome: Modbus Controller - block grouping and
force_new_range - Modbus Organization: modbus.org - protocol specifications, function codes and exception responses
- GitHub: jourdant/esphome-lgap - community analysis of LG's proprietary LGAP protocol