Repository navigation
MQTT
Advanced, and only relevant if you want Home Assistant. Nothing on this page is needed to run the controller and its own web UI — see Get started.
MQTT is how the controller and Home Assistant talk to each other. Neither one ever connects to the other directly — a broker sits in the middle, and that indirection is the point.
MQTT is a lightweight publish/subscribe messaging protocol. Instead of calling each other, programs publish messages to named topics, and any program that cares subscribes to those topics. A broker routes messages between them.
The practical consequence is decoupling. The controller does not know Home Assistant exists; it publishes what it measured. Home Assistant does not know the controller exists; it subscribes to what it wants and publishes commands back. Either can restart, crash or be upgraded without the other needing to reconnect to anything new.
solax-controller ──publish──▶ ┌──────────┐ ──▶ Home Assistant
(telemetry) │ mosquitto│ (subscribes)
│ broker │
(subscribes) ◀──────────────│ │ ◀──── (publishes commands)
solax-controller └──────────┘ Home Assistant
The controller publishes telemetry on a fixed interval and subscribes to command topics for the things you can change at runtime — the charge mode, the battery hold switch, and the numeric targets. That is why changing the mode in Home Assistant takes effect without restarting anything.
The controller uses Home Assistant MQTT Discovery. On connect it publishes configuration messages under the discovery prefix describing each entity it offers; Home Assistant reads those and creates the device and all its entities automatically. You do not hand-configure sensors.
What appears is a single device with a row of charging buttons — Charge from solar, Charge from forecast, Charge fast, Activate target and Charge off — a read-only Charge mode sensor saying which one is driving, a Battery discharge hold switch, the settable controls those buttons apply (minimum battery SOC, SOC resume margin, and the target and fast-charge entities described below), a set of sensors for solar, battery, grid, EV, targeted- and fast-charge figures, binary sensors for whether the car is connected and charging, and a Stop service button.
Charging is started by a button, not by picking a mode from a select. A select is a statement of intent that waits for the world to agree with it — chosen while the charger sat in
Greenit did nothing at all, which was the single most common "it isn't working" report this project produced. A button writes the charger's use-mode as part of pressing it, so something happens. Charge mode is now a sensor that reports what a button did, and it moves on its own when a self-ending mode finishes.
That last one is published with entity_category: config, so it lands in the device's
Configuration section rather than on the auto-generated dashboard card — the right amount of
friction for a control that cannot be undone from here. Only an exact PRESS payload on its topic
acts, so a stray retained message cannot take the controller off the air. Pressing it shuts the
service down gracefully; since the service is what speaks MQTT, nothing in Home Assistant can start
it again — that needs a shell on the host. See
Deployment.
Charge fast used to be a button and nothing else: press it, and the car charged at the maximum current until it was full. It still does exactly that if you leave the four entities beside it alone — but they let you say two more things.
| Entity | What it is for |
|---|---|
| Fast basis |
Full (the default — the car decides), Energy, or Soc. Picks which of the next two boxes is read. |
| Fast energy | How many kWh to deliver before stopping. Read under Energy. |
| Fast target SOC | What state of charge to stop at. Read under Soc. |
| Fast departure | When you need the car. Empty means charge straight away. |
Nothing happens when you change them. Press Charge fast to apply them, the same way Activate target applies the targeted controls — an amount typed and not pressed is not a charge.
How much. Under Energy or Soc the charge stops at the figure you gave rather than when the car
is full. Whichever comes first wins: a car that reaches its own limit at 12 kWh of the 20 you asked
for ends there and says so. A battery target needs the car's usable capacity configured
(Ev:Vehicles:0:BatteryCapacityKWh) and a reading from the car; it is converted to kWh once, when you
press the button, and a later reading from the car does not move a charge already half delivered.
When. Give it a departure and the charge is held back, starting as late as it can and still finish 15 minutes before you go. Watch Fast start for the computed time.
Why you would want that: the pack. A lithium cell ages faster the longer it is held at a high state of charge, so the points above 80% are worth buying as late as possible. The charge itself takes two or three hours; it is the sitting at 95% overnight that does the damage. Pressing the button at 22:00 for an 07:00 departure turns nine hours up there into about twenty minutes.
It changes when, never how — when it runs, it runs flat out. Anything that paces a charge or waits for cheaper sun is the targeted mode, and the two are separate on purpose.
A few things worth knowing:
-
A departure needs an amount. With the basis on
Fullthere is nothing to work back from, so the press is refused and logged rather than quietly charging at once. - The start time moves. It is recomputed every poll: later as energy goes in, and earlier the moment the car turns out to draw less than the charger offers. Before the car draws anything the figure is the installation's maximum and the log says so.
- Not enough time? It charges flat out from now and reports how far short it will fall.
- Nothing survives a restart — not the mode, the amount, or the departure. A charge deferred to 04:00 and lost to an evening restart is discovered at 07:00 with a car that never charged. Set it again after restarting the stack.
Fast delivered, Fast target and Fast start report progress while a limited charge runs. All three are absent when the mode is charging with no amount set, which is how the dashboard shows "the car decides".
A fast charge arms the discharge hold when it starts charging, and releases it when the charge ends for any reason — you pressed Charge off, the car finished, the amount was delivered, the car was unplugged.
In between, the Battery discharge hold switch is yours: turn it off and the pack is allowed to help, while the car keeps charging at maximum. Two consequences worth knowing:
- A hold you had switched on before starting is off when the charge ends. The mode releases the hold on the way out rather than remembering what the switch said an hour ago.
- A charge deferred to a departure does not arm the hold while it waits — only when it actually starts charging. Otherwise a charge scheduled for 04:00 would lock the pack out of serving the house from the moment you pressed the button.
The controller also publishes to an availability topic. If it stops, Home Assistant marks the whole device unavailable rather than leaving the last values on screen looking current. A dashboard that has gone grey is telling you the controller is down.
This is also the acknowledgement you get for the Stop service button: a button has no state to report, so the device simply going unavailable a moment after the press is how you know it worked.
The integration is disabled by default. Non-secret settings live in appsettings.json;
credentials do not.
| Setting | Default | What it does |
|---|---|---|
Enabled |
false |
Turn the whole MQTT integration on |
BrokerHost |
localhost |
Broker hostname. In the Compose stack this is the service name mosquitto, not localhost. |
BrokerPort |
1883 |
Standard unencrypted MQTT port |
DiscoveryPrefix |
homeassistant |
Topic root Home Assistant watches for discovery messages |
BaseTopic |
solax |
Topic root for this controller's own telemetry and commands |
DeviceId |
solax_controller |
Stable id for the device in Home Assistant |
DeviceName |
SolaX Local Controller |
Display name on the device page |
StatusInterval |
00:00:15 |
How often telemetry is published |
Credentials are secrets and must come from the environment rather than appsettings.json:
HomeAssistant__Enabled=true
HomeAssistant__BrokerHost=mosquitto
HomeAssistant__Username=<user>
HomeAssistant__Password=<pass>Note
Authenticated, and not on your LAN. In the deploy stack the broker has no published port — only the controller and Home Assistant reach it, over the Compose network. Authentication is required regardless of that, so a later decision to expose it does not silently open the control topics to anyone on the network. Port 1883 carries no encryption, which is exactly why the broker is kept off the LAN.
When something is not appearing in Home Assistant, subscribe to both topic roots and watch what is actually being published. This is the fastest way to tell "the controller is not publishing" apart from "Home Assistant is not listening".
# against the development stack in dev/homeassistant/
docker exec -it solax-dev-mosquitto \
mosquitto_sub -t 'homeassistant/#' -t 'solax/#' -vA ready-to-run broker and Home Assistant for local development live in dev/homeassistant/ — that
stack uses an anonymous broker and is deliberately separate from the production one. Do not point one
at the other.
Previous: Deployment · Next: Solcast
Runs entirely on your LAN · Full configuration reference in README.md · Pi preparation and backup in deploy/README.md
- Home
- Get started — build & run
- Configuration
- Solcast
- Weather
- Troubleshooting
Advanced
- Docker
- Platforms
- Deployment — Pi, two workflows
- MQTT
Images: ghcr.io/mpospisil/solax-controller