- Python 97.4%
- Shell 2.6%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
The publisher fired ~465 times per hour on a live host and wrote roughly 930 recorder rows per hour just for last_triggered, which buried every other entry in the logbook. The three voltage sensors alone accounted for ~345 runs/h: they jitter by tenths of a volt around the clock, while evcc only displays them. Drop them from the trigger list - they are still published on the 30 s cycle. The remaining ~120 runs/h are the deliberate republish cycle and cannot be optimised away, so document the logbook/recorder exclude filters instead. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
| custom_components/anker_v1 | ||
| examples | ||
| scripts | ||
| .gitignore | ||
| LICENSE | ||
| README.md | ||
⚡ ha-anker-solix-v1
A Home Assistant custom integration that reads — and optionally controls — an Anker SOLIX V1 Smart EV Charger (A5191) over its local Modbus TCP interface. No cloud account, no Anker app in the loop, no polling of Anker's servers.
This project was developed with the assistance of Claude (Anthropic).
Mirror: https://opencommit.eu/HWE70/ha-anker-solix-v1 · Primary: https://git.highwire.de/HWE70/ha-anker-solix-v1
🤔 Why this exists
The V1 is normally driven either through the Anker app or through OCPP. Both hide what the charger is actually doing — in particular how many phases it is using, which the charger decides on its own and never reports over OCPP. When a charging session unexpectedly runs single-phase, OCPP can tell you the current per phase but not whether the charger switched to one phase or the vehicle simply stopped drawing on the other two.
Modbus exposes both: register 20087 is the charger's own phase mode, and
21005 is the configured phase setting. Recording them alongside the
per-phase currents makes that question answerable from history instead of
requiring someone to be watching at the right moment.
✨ Features
- 🔌 Local polling only — Modbus TCP straight to the charger, nothing leaves the LAN.
- 📦 No external dependencies — the Modbus client is ~200 lines of
standard library. Nothing to pin, nothing to collide with the pymodbus
version Home Assistant's own
modbusintegration already loaded. - 🔍 Full state visibility — phase mode, charging status, control pilot state, charging mode, OCPP/cloud connectivity, load and solar balancing.
- 📊 Per-phase measurements — current, power and voltage on each phase, plus line-to-line voltages and a derived Active phases sensor.
- 🎛️ Optional control — charging release, current limit and phase setting, all opt-in (see below).
- 🌍 English and German translations.
📋 Requirements
Modbus TCP must be enabled on the charger first. In the Anker app: device → Settings → Integrations → Modbus TCP. Until that switch is on, port 502 is closed and setup will fail with "no answer on this address".
The charger serves at most two Modbus clients at once. If an evcc instance or another tool already holds both slots, this integration cannot connect.
🚀 Installation
Copy custom_components/anker_v1/ into your Home Assistant config/custom_components/
directory and restart Core, then add the integration under
Settings → Devices & Services → Add Integration → Anker SOLIX V1.
The repo ships a deploy script for hosts reachable over SSH:
./scripts/deploy-hassio.sh hassio-pb
It stages the files through /tmp, moves them into place with sudo (the
SSH add-on's user does not own custom_components/ on every host) and
restarts Core through the ha CLI rather than docker restart, so it also
works where the SSH add-on runs in protection mode.
HACS is not an option here: HACS only supports GitHub repositories.
⚙️ Configuration
| Option | Default | Notes |
|---|---|---|
| IP address | — | Give the charger a DHCP reservation; the config entry is pinned to the address. |
| Port | 502 | |
| Modbus unit ID | 1 | Verified value for the A5191. |
| Polling interval | 10 s | Minimum 5 s. Keep in mind the two-client limit. |
| Enable control | off | Adds the switch, current limit and phase setting entities. |
Polling interval and control access can be changed later via the integration's options without re-adding the device.
⚠️ Writing while OCPP is connected
Control is off by default, deliberately.
The charger accepts Modbus commands while an OCPP backend (evcc, for instance) is also controlling it — the Modbus server and the OCPP client run in parallel, and the register map even reports OCPP connection state. But Anker does not document which side wins when the two disagree. A current limit written over Modbus and a charging profile pushed over OCPP are two controllers steering the same device.
Recommended sequence, mirroring the staged approach in the sibling
ha-automations repo:
- Run read-only across several charging sessions. Watch the OCPP connected binary sensor — if parallel Modbus polling ever destabilised the OCPP link, it shows up there.
- Only then enable control, and start with a single current-limit write below what the OCPP backend is asking for. Observe which value the charger actually applies.
The watchdog
Register 21003 is a watchdog timeout (120 s from the factory). If no
Modbus write arrives within that window, the charger falls back to its own
control. That is a safety feature — it stops a crashed automation from
leaving the wallbox pinned at a limit forever — but it also means a value
written once does not stay applied indefinitely. Anything that needs to hold
a limit must keep writing it.
📟 Register map status
Register numbers come from Anker SOLIX V1 Smart EV Charger Modbus Protocol V1.0.0 (30-11-2025). Three things in that document do not match the hardware, both established by reading back from a live A5191 on 2026-08-30:
| Vendor document says | Actually |
|---|---|
| Register numbers, function code unspecified | 20xxx are input registers (FC4), 21xxx are holding registers (FC3). Using the wrong function code returns exception code 2 for every address in the range. |
21001 in a column reading "W", gain 10 |
Amps, gain 10 — the charger returned 160 while the OCPP backend was offering exactly 16.0 A. |
Per-phase currents 20059–20061 at gain 100 |
Gain 10. Verified under load against power and voltage: raw 145 with P = 3413 W at U = 235.4 V is 14.5 A, not 1.45 A. Reading them at gain 100 makes every current a factor of ten too small. |
Also worth knowing:
- The holding range ends at
21005. Reading21006, or any block that extends past it, returns exception code 2 — so read the control registers as a 6-register block, not 10. 20056–20058are line-to-line voltages (~414 V), not documented in the register list this integration was built from.- The charger's voltage measurement sits upstream of the contactor: it reports ~240 V on all three phases even with no vehicle plugged in. Voltage therefore tells you nothing about which phases are switched through to the car — use the per-phase currents and the Active phases sensor for that.
21000(start/stop) reads back0regardless of what was last written, so the charging switch derives its state from the status and control pilot registers instead.20087(phase mode) reads a constant3while no vehicle is connected, which makes it look like a static capability field. It is not — during a session it tracks the actual mode and follows21005within 19–100 s. Registers carrying an operating state cannot be assessed while idle.
All register gains are now verified against live measurements; nothing in the map is taken on faith from the vendor table alone.
Device identity
Undocumented ASCII fields, found by dumping the full readable range. Two characters per register, NUL-padded. They feed the device registry entry (model, serial number, firmware) and are read once at setup:
| Registers | Content | Example |
|---|---|---|
20001–20003 |
Model | A5191 |
20011–20019 |
Serial number | AT2D... (masked) |
20023–20026 |
Firmware version | 1.0.6.1 |
20029–20032 |
Hardware version | 1.0.0.0 |
The serial number is the same string the charger uses as its OCPP station id.
❓ RFID
The charger exposes no RFID data over Modbus. The readable range was
dumped register by register and contains no tag identifier; there is no
second register bank either (probing 19000–24000 in 50-step increments finds
nothing outside 20000–20100 and 21000–21005).
RFID arrives over OCPP instead: the charger sends the card UID as the
idTag in StartTransaction. In evcc it shows up as the session
identifier (/api/sessions) and on the loadpoint as vehicleIdentity —
a real card reads as a UID like 22848AB3, while a session started without
a card gets the charger's own pseudo tag, J_ followed by the serial
number.
🧩 Entities
Sensors — Phase mode · Charging status · Control pilot · Charging mode · Phase setting · OCPP status · Active phases · Charging power · Power L1/L2/L3 · Current L1/L2/L3 · Offered current · Voltage L1/L2/L3 · Voltage L1-L2/L2-L3/L3-L1 · Session energy · Session duration · Rated power
Binary sensors — Vehicle connected · Charging · Load balancing · Solar balancing · OCPP connected · Cloud (MQTT) connected
Controls (only when enabled) — Charging (switch) · Current limit (number) · Modbus watchdog (number) · Phase setting (select)
🔌 Using it from evcc instead of OCPP
examples/evcc/ contains a complete setup that lets evcc
drive the charger through this integration rather than over OCPP — a Home
Assistant package (MQTT bridge in both directions, plus the watchdog
keepalive) and the matching evcc charger definition.
The point is phase control: the V1 has no OCPP-controlled phase switching and
decides the count by an undocumented automatic. Over Modbus, register 21005
turns that into an input, so evcc decides and applies its own hysteresis. The
trade-off is RFID — see the example's README.
🎛️ Dashboard cards
The device page's controls cannot be placed on a dashboard — Home Assistant
has no device card. examples/lovelace/ rebuilds that
group as a section for a sections view, or as a plain entities card.
📄 License
MIT — see LICENSE.