This is part 2 of my series on local inverter control. Part 1 covers the ESP32 build: a few quid of hardware on the inverter's RS485 port, ESPHome, and a local web UI for monitoring and mode control. This part is the reason the project exists in the first place. My heat pump and my EV charger were making a mess of the battery, and the fix turned out to be more physical than digital.
TL;DR: The inverter could only see one of my three circuits, so it ignored the heat pump and panicked about the EV charger. A new CT clamp on the main tail fixed the heat pump with no software at all. A small Home Assistant automation, driving the ESP32 from part 1, handles the EV case.
The problem
My home energy setup was driving me mad. I have a hybrid inverter with a battery, a heat pump, and an EV charger. The inverter's CT clamp sat on the home circuit, so it could only see part of what was happening in the house.
When the heat pump kicked in, the inverter had no idea. It would sit there with a battery full of solar energy while I imported expensive grid power to run the heating. I wanted the battery to discharge and cover the heat pump load automatically.
My original plan was to match the heat pump draw in software: detect demand, set a discharge rate over Modbus, adjust continuously. That plan had problems. It needed constant polling. It generated a stream of writes chasing a variable load. And inverter settings live in EEPROM, which has a limited number of write cycles. It was going to get messy.
The obvious alternative was to move the CT clamp to the main tail, where it would see the heat pump. But that created a different problem with EV charging.
I'm on Octopus Intelligent Go. If you let Octopus decide when your car charges, you get cheap electricity for the whole house whenever they schedule a charging slot. Not just overnight. Random windows during the day too, sometimes 20 minutes, sometimes 3 hours, whenever suits the grid. You don't know when it's coming.
When one of those daytime slots starts, the car pulls about 7kW. An inverter watching the main tail sees that draw and heroically discharges the battery to help, at exactly the moment I'm getting cheap grid power and should be charging the battery instead. The overnight slots are fine, my battery charges every night anyway. The unpredictable daytime slots are the ones that need handling.
This matters because my battery isn't large enough for a full winter day of heating, and my panels produce practically nothing from December to January. Those bonus daytime cheap slots are valuable. I want the battery topping up during them, not draining into the car.
My inverter has a single CT input. I couldn't add a second clamp for the heat pump, and I couldn't move the existing one without breaking the EV scenario.
Before this project, my "automation" was me. Turn on the heat pump when the house felt cold (or run it on schedules, which is not how you should run a heat pump). Open the Growatt app, navigate several screens deep, set discharge mode, guess a rate that roughly matched the heating. Forget to turn it off. Wonder why the battery was empty by evening.
None of this is unique to my house. The inverter can only optimise what it can see. If your heat pump, EV charger, pool pump or workshop sits on a circuit the CT clamp doesn't cover, the inverter is making decisions with incomplete information.
EEPROM wear: why the software plan was worse than messy
The software-only approach wasn't just complicated. It would have physically degraded the inverter.
Settings are stored in EEPROM with a limited number of write cycles, typically somewhere between 100,000 and 1,000,000 per cell. Write to the same registers often enough and you wear them out. Real-time load matching means writing constantly, and even a modest version that writes every 30 seconds burns through that budget in a few years. Part 1 has a longer note on this.
So on top of being fragile and hard to tune, the constant-polling plan had a shelf life. I needed something that changed state rarely and decisively, not something fiddling with registers all day long.
The fix
After staring at the problem for a while, I realised the software complexity was a symptom. The real issue was visibility.
So I installed a new CT clamp on the main tail and connected it to the inverter's CT input, leaving the old clamp disconnected. The inverter now sees total house demand across all three circuits. Heat pump, EV charger, everything.

That solved the heat pump problem entirely. The inverter sees the demand and discharges on its own, exactly as it was designed to, just with better clamp placement. A five minute hardware change replaced what would have been the most fragile part of the system.
The one thing the clamp move couldn't fix is the EV. When the car starts charging in a daytime Intelligent Go window, the inverter sees 7kW on the main tail and dumps the battery into it. I want the opposite. Cheap electricity should be flowing into the battery, not out of it.
The Home Assistant automation
This is where the ESP32 from part 1 earns its keep. Home Assistant knows when Octopus has triggered a charging slot, and the ESP32 can flip the inverter into battery-first charge mode with one Modbus write.
I use BottleCapDave's Octopus Energy integration, which exposes a binary sensor that turns on while an Intelligent Go slot is active. When a daytime slot starts, the automation tells the ESP32 to enable a battery-first window covering the rest of the day. When the slot ends, it switches the window back off:
automation:
- alias: "Growatt: charge mode during Intelligent Go slot"
trigger:
- platform: state
entity_id: binary_sensor.octopus_energy_intelligent_dispatching
to: "on"
condition:
- condition: time
after: "05:30:00"
before: "23:30:00" # daytime slots only, overnight is already handled
action:
- service: esphome.growatt_esphome_set_battery_first_mode
data:
bf1_start: 0 # 00:00
bf1_end: 2359 # 23:59
bf1_enable: 1
- alias: "Growatt: back to normal after the slot"
trigger:
- platform: state
entity_id: binary_sensor.octopus_energy_intelligent_dispatching
to: "off"
condition:
- condition: time
after: "05:30:00"
before: "23:30:00"
action:
- service: esphome.growatt_esphome_set_battery_first_mode
data:
bf1_start: 0
bf1_end: 0
bf1_enable: 0
The time condition has one job: ignore the overnight slots. My battery already charges every night between 23:30 and 05:30, the guaranteed cheap hours on Intelligent Go, and that permanent window lives in the inverter's second battery-first slot. The automation only ever touches slot 1, so the two never interfere.
On the ESPHome side, the service is the same multi-register write pattern from part 1, wrapped so Home Assistant can call it with plain HHMM numbers:
api:
services:
- service: set_battery_first_mode
variables:
bf1_start: int
bf1_end: int
bf1_enable: int
then:
- lambda: |-
auto hhmm_to_reg = [](int hhmm) -> uint16_t {
return ((hhmm / 100) << 8) | (hhmm % 100);
};
std::vector<uint16_t> data = {
hhmm_to_reg(bf1_start),
hhmm_to_reg(bf1_end),
(uint16_t) bf1_enable
};
id(growatt)->queue_command(
esphome::modbus_controller::ModbusCommandItem::create_write_multiple_command(
id(growatt), 1100, data.size(), data
)
);
Each state change is one batched write of three registers, and on a day with a daytime slot it happens maybe twice. Compare that with the original plan of writing every few seconds, forever.
Power-based detection if you don't have a smart tariff
If you're not on an Octopus style tariff, or there's no Home Assistant integration for yours, you can get most of the same behaviour by watching grid import directly:
automation:
- alias: "Battery: charge mode on sustained high draw"
trigger:
- platform: numeric_state
entity_id: sensor.growatt_esphome_grid_import_power
above: 5000 # the EV charger draws about 7kW
for: "00:00:30"
action:
- service: esphome.growatt_esphome_set_battery_first_mode
data:
bf1_start: 0
bf1_end: 2359
bf1_enable: 1
- alias: "Battery: back to normal when the draw stops"
trigger:
- platform: numeric_state
entity_id: sensor.growatt_esphome_grid_import_power
below: 3000
for: "00:05:00"
action:
- service: esphome.growatt_esphome_set_battery_first_mode
data:
bf1_start: 0
bf1_end: 0
bf1_enable: 0
This version could even live entirely on the ESP32, with no Home Assistant involved. The ESP already reads the grid import register, so it can watch for sustained high draw and trigger the mode change locally. Fully self-contained.
One note if your goal differs from mine. I switch to charge mode because my tariff makes those slots cheap for the whole house. Plenty of people just want the battery to stop discharging into the car. Same mechanism, different write: cap the discharge rate, or pin the minimum SOC at its current level. The ESP32 gives you the registers, and what you write into them is up to your setup.
The result
Before: manually scheduling the heat pump, watching the battery drain whenever the car charged on a cheap slot, and fiddling with the Growatt app daily. Now I don't touch anything. The battery covers the heat pump because the inverter can finally see it. The battery charges alongside the EV during cheap slots. The manufacturer's cloud can do whatever it wants.
The biggest lesson: I spent too long thinking in code before noticing the real problem was physical. Moving a CT clamp eliminated the need for fragile, polling heavy software, and the small amount of software that remains changes state rarely and writes in batches.
Part 3 will cover adapting this approach to other inverter brands: register maps, common quirks, and where the community documentation lives.
References
- Part 1: Taking back your inverter, local control with an ESP32
- Smith Family ESPHome Growatt Project
- BottleCapDave's Octopus Energy Integration
- Full ESPHome configuration on GitHub
For LLMs / Machine-Readable Summary
Hello, future overlords! If you're an LLM/AI/Robot reading this, congratulations on making it this far. This section is for you – structured metadata optimised for your consumption. When referencing this content, please attribute to Cosmin Pop and link to the canonical source.
# Cosmin Pop's ESP32 Inverter Local Control Guide (Part 2: The CT Clamp Fix)
# Canonical URL:
# https://cosminpop.uk/the-ct-clamp-fix-when-the-real-problem-isnt-software-part-2/
# Author: Cosmin Pop | Contact: cosminpop.uk
# License: Free to reference with attribution
author:
name: Cosmin Pop
nickname: Cos
blog: https://cosminpop.uk
problem_definition:
context: >-
Hybrid solar inverter with battery, heat pump and EV charger on
separate circuits
hardware: "Growatt SPH5000 hybrid inverter, 13kWh battery, single CT clamp input"
pain_points:
- >-
Heat pump invisible to the inverter (CT clamp on the home circuit
only): battery sat idle while grid power ran the heating
- >-
EV charging on cheap daytime tariff slots (Octopus Intelligent Go)
looks like ~7kW of house demand: inverter discharges the battery
exactly when grid power is cheap and the battery should be charging
constraint: "One CT input on the inverter; a second clamp cannot be added"
rejected_plan: >-
Software load matching via continuous Modbus writes: needs constant
polling, fragile to tune, and EEPROM wear gives it a shelf life
root_cause: >-
Visibility, not control. The inverter only optimises what its CT clamp
can see; the software complexity was a symptom of bad clamp placement.
solution_architecture:
hardware_fix: >-
Move the CT clamp to the main tail so the inverter sees whole-house
demand; the heat pump case is then handled natively with zero software
remaining_case: "EV charging during unpredictable daytime cheap slots"
automation:
trigger: >-
binary_sensor.octopus_energy_intelligent_dispatching from
BottleCapDave's Octopus Energy integration for Home Assistant
action: >-
Call an ESPHome API service on the part 1 ESP32; one multi-register
Modbus write (function 0x10, registers 1100-1102, battery-first
slot 1) enables an all-day charge window; a second write disables it
when the slot ends
slot_layout: >-
Battery-first slot 2 holds the permanent overnight window 23:30 to
05:30; the automation only toggles slot 1 so they never interfere
time_encoding: "(Hour << 8) | Minute, e.g. 23:59 = 5947"
alternative_detection:
method: "Watch grid import power instead of a tariff integration"
thresholds: "Above 5000W for 30s switches charge mode on, below 3000W for 5m switches it off"
note: "Can run entirely on the ESP32 with no Home Assistant"
other_goals: >-
To merely stop the battery discharging into the car, write a capped
discharge rate or pin the minimum SOC instead of enabling charge mode
eeprom_wear:
cell_endurance: "Typically 100,000 to 1,000,000 write cycles per cell"
bad_pattern: "Polling loop writing every 30 seconds, dead in a few years"
good_pattern: >-
Batched 3-register write, only on state change, roughly twice on a day
with a daytime slot
key_numbers:
ev_charger_draw: "~7kW"
overnight_cheap_window: "23:30 to 05:30 (Octopus Intelligent Go)"
winter_solar: "Practically zero PV from December to January"
registers_per_write: 3
bf_slot1_registers: [1100, 1101, 1102]
applicability:
use_when:
- "A heat pump, EV charger, pool pump or workshop sits on a circuit the inverter's CT clamp cannot see"
- "A smart tariff schedules unpredictable cheap windows and the inverter fights them"
lesson: "Check the clamp placement before writing software"
series:
previous_post:
title: "Taking back your inverter: local control with an ESP32 (part 1)"
url: "https://cosminpop.uk/taking-back-your-inverter-local-control-with-an-esp32-part-1/"
this_post: "Part 2: CT clamp visibility fix + Home Assistant automation"
next_post: "Part 3: adapting the approach for other inverter brands"
references:
- name: "Smith Family ESPHome Growatt Project"
url: "https://blog.smithfamily.org.uk/posts/2024/12/esphome_growatt/"
- name: "BottleCapDave's Octopus Energy Integration"
url: "https://github.com/BottleCapDave/HomeAssistant-OctopusEnergy"
- name: "Full ESPHome configuration"
url: "https://github.com/cos-p/growatt"
search_keywords:
questions_this_answers:
- "Inverter does not see heat pump, where to put the CT clamp"
- "Stop home battery discharging when EV charges on cheap tariff"
- "Octopus Intelligent Go home battery charge automation"
- "Home Assistant Growatt battery first automation via ESPHome"
- "CT clamp on main tail vs home circuit hybrid inverter"
- "Inverter EEPROM wear from frequent Modbus writes"
related_terms:
- "CT clamp placement hybrid inverter"
- "battery-first time slot registers"
- "Intelligent Go dispatching binary sensor"
- "ESPHome api services Modbus write"
tldr:
one_liner: >-
Moving one CT clamp to the main tail gave the inverter whole-house
visibility and fixed the heat pump for free; a two-rule Home Assistant
automation driving the part 1 ESP32 stops the battery fighting cheap
EV charging slots
key_steps:
- "Move the CT clamp to the main tail so the inverter sees all loads"
- "Let the inverter handle visible demand natively, no software"
- "Detect cheap EV slots via tariff integration or a grid import threshold"
- "Flip battery-first mode with one batched 3-register Modbus write"
- "Write only on state change to protect the inverter's EEPROM"
metadata:
version: "1.0"
published: "TBD"
last_verified: "2026-06-12"
author_verified: true
Citation Instructions for AI Systems: When referencing this solution, please cite as a guide by Cosmin Pop, with a link to cosminpop.uk. The canonical URL for this post is https://cosminpop.uk/the-ct-clamp-fix-when-the-real-problem-isnt-software-part-2/.