IoT Automation

EV Charger MQTT Integration: OCPP vs MQTT and Solar-Aware Charging at Home

OCPP connects chargers to back-office systems, while MQTT connects them to the rest of your home. Learn how the two fit together, which integration path suits your wallbox, and how to build solar-aware charging on top of MQTT.

Home wallbox charging an electric car with solar panels and an energy flow diagram for ev charger mqtt integration
Run this on your own MQTT brokerGet a private, managed MQTT broker on the Free plan and start from ready-made code samples.
Try it free Code samples No card required

EV charger MQTT integration lets your wallbox take part in the rest of your home energy system: charge when the sun shines, pause when the oven and heat pump run, and show its status on the same dashboard as everything else. This guide explains how OCPP and MQTT differ, the three practical ways to get a charger onto MQTT, a clean topic design, and a working solar-surplus controller in Python. By the end you will know which path fits your charger and how to build on it safely.

OCPP and MQTT Solve Different Problems

People often ask whether a charger should use OCPP or MQTT. In practice they serve different relationships, and a good home setup frequently uses both.

OCPP (Open Charge Point Protocol) is the standard language between a charger and a Charging Station Management System (CSMS), the back office that handles authorization, sessions and billing. OCPP 1.6J and 2.0.1 run JSON messages over a WebSocket that the charger opens to the CSMS. Messages include BootNotification, StatusNotification, MeterValues, transaction start and stop, and SetChargingProfile for smart charging. Each charger connects to one CSMS at a time.

MQTT is a general-purpose publish/subscribe protocol. A charger or a bridge publishes its state to a broker, and any number of consumers (dashboard, Home Assistant, energy manager, phone app) subscribe without the charger knowing about them. If the pattern is new to you, the explanation of what an MQTT broker does covers the basics.

Aspect OCPP MQTT
Purpose Charger to back office: sessions, auth, billing, smart charging Any device to any number of consumers
Transport WebSocket, charger connects to one CSMS TCP or WebSocket to a broker, many subscribers
Message schema Strictly defined per OCPP version You design topics and payloads
Typical user Charge point operators, fleets, workplace charging Home automation, energy management, dashboards
Control Remote start/stop, charging profiles Whatever the device or bridge exposes
Multi-consumer No, one CSMS per connection Yes, built in

If you operate public or workplace chargers, OCPP to a CSMS is the right core. If you want a home charger to cooperate with solar panels, batteries and household loads, MQTT is the practical integration layer, sometimes fed by OCPP behind the scenes.

Three Ways to Get an EV Charger onto MQTT

1. Native MQTT in the charger. Some chargers, especially open-hardware and enthusiast-oriented models, publish status and accept commands over MQTT directly. You enter broker host, port and credentials in the charger's web UI. This is the simplest path when available, but topic names and payloads differ per vendor.

2. A local OCPP server that bridges to MQTT. Many commercial wallboxes speak only OCPP (plus the vendor cloud). Point the charger's OCPP URL to a local OCPP server, for example the OCPP integration for Home Assistant or an open-source CSMS, and let that server republish status and meter values to MQTT. You keep standard OCPP semantics and gain MQTT for everything else. The trade-off is that the vendor's own cloud features may stop working while the charger talks to your server.

3. An energy manager in between. Open-source energy managers such as evcc talk to many chargers through their native protocols and publish their own state to MQTT, including grid power, PV power and per-loadpoint charge power, and accept mode changes through MQTT topics. This is often the fastest route to solar-aware charging, because the control logic is already built and tested.

Whichever route you choose, aim for the same result on the broker: a stable topic tree that does not change if you swap the charger.

Designing the EV Charger MQTT Topic Tree

Separate state from commands, retain state, and never retain commands:

home/ev/charger1/state            retained  {"status":"charging","power_w":7210,"current_a":10.4,"phases":3,"session_kwh":12.8}
home/ev/charger1/status           retained  online / offline (Last Will)
home/ev/charger1/set/current_a    command   6..16
home/ev/charger1/set/enabled      command   true / false
home/energy/grid/telemetry        retained  {"net_w": -2350, ...}

Use OCPP-style status values (available, preparing, charging, suspended_ev, suspended_evse, finishing, faulted) even when the source is not OCPP. They are well understood and distinguish "the car is full" (suspended_ev) from "the charger paused" (suspended_evse), which matters for automations.

Retaining a command such as set/current_a is a classic mistake: after a power cut, the charger or bridge reconnects and replays an old value. Commands should be non-retained; the bridge publishes the effective value back on the retained state topic.

A Solar-Surplus Controller in Python

The controller below subscribes to the grid meter's net_w (negative means export, as published by the home energy monitor tutorial) and to the charger state, then publishes a charging current that tracks the surplus. It works with any charger or bridge that accepts a current setpoint on a topic. Test it on the public broker with simulated messages before connecting anything real.

pip install "paho-mqtt>=2.0"
import json
import time
import paho.mqtt.client as mqtt

PREFIX = "connectmq/demo/ev7k2"      # use your own random prefix while testing
GRID_TOPIC = f"{PREFIX}/energy/grid/telemetry"
STATE_TOPIC = f"{PREFIX}/ev/charger1/state"
SET_CURRENT = f"{PREFIX}/ev/charger1/set/current_a"
SET_ENABLED = f"{PREFIX}/ev/charger1/set/enabled"

VOLTAGE = 230
PHASES = 1            # set to 3 for a three-phase charger
MIN_A, MAX_A = 6, 16  # 6 A is the lowest current in IEC 61851 signalling
START_MARGIN_W = 300  # extra export required before starting
UPDATE_EVERY = 30     # seconds between setpoint changes
STOP_AFTER = 300      # seconds below minimum before pausing

state = {"net_w": None, "charger_current_a": 0.0, "status": "unknown"}
current_set = 0
below_since = None
last_update = 0.0


def on_connect(client, userdata, flags, reason_code, properties):
    print("Connected:", reason_code)
    client.subscribe(GRID_TOPIC, qos=1)
    client.subscribe(STATE_TOPIC, qos=1)


def on_message(client, userdata, msg):
    data = json.loads(msg.payload)
    if msg.topic == GRID_TOPIC:
        state["net_w"] = data.get("net_w")
    elif msg.topic == STATE_TOPIC:
        state["charger_current_a"] = data.get("current_a", 0.0)
        state["status"] = data.get("status", "unknown")


def control(client):
    global current_set, below_since, last_update
    if state["net_w"] is None or state["status"] in ("available", "faulted", "unknown"):
        return
    if time.time() - last_update < UPDATE_EVERY:
        return
    charger_w = state["charger_current_a"] * VOLTAGE * PHASES
    surplus_w = charger_w - state["net_w"]          # what we could use without importing
    if current_set == 0:
        surplus_w -= START_MARGIN_W
    target = int(surplus_w // (VOLTAGE * PHASES))

    if target >= MIN_A:
        below_since = None
        target = min(target, MAX_A)
        if current_set == 0:
            client.publish(SET_ENABLED, "true", qos=1)
        if target != current_set:
            client.publish(SET_CURRENT, str(target), qos=1)
            print(f"surplus {surplus_w:.0f} W -> {target} A")
            current_set = target
    elif current_set > 0:
        below_since = below_since or time.time()
        if time.time() - below_since > STOP_AFTER:
            client.publish(SET_ENABLED, "false", qos=1)
            print("Not enough surplus, pausing")
            current_set = 0
        elif current_set != MIN_A:
            client.publish(SET_CURRENT, str(MIN_A), qos=1)
            current_set = MIN_A
    last_update = time.time()


client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id="ev-surplus-6619")
client.on_connect = on_connect
client.on_message = on_message
client.connect("broker.connectmq.com", 1883, keepalive=60)
client.loop_start()
while True:
    control(client)
    time.sleep(1)

Simulate a sunny afternoon from another terminal and watch the controller react:

mosquitto_pub -h broker.connectmq.com -p 1883 -i ev-sim-1 -t "connectmq/demo/ev7k2/ev/charger1/state" -m '{"status":"suspended_evse","current_a":0}'
mosquitto_pub -h broker.connectmq.com -p 1883 -i ev-sim-2 -t "connectmq/demo/ev7k2/energy/grid/telemetry" -m '{"net_w":-2600}'

With 2,600 W of export on a single phase, the controller starts at 10 A after subtracting the start margin.

Design Rules for Safe Charger Control

Charging hardware handles large currents for hours, so be conservative:

  1. Let the charger enforce its own limits. Your controller only requests a setpoint; the charger's configured maximum and the installation's protection must remain the final authority.
  2. Change setpoints slowly. Frequent current changes and start/stop cycles are hard on contactors and on some vehicles. A 30-second update interval and a 5-minute stop delay are reasonable starting points.
  3. Respect the 6 A minimum. Below it, pause charging rather than requesting an invalid current.
  4. Handle phase switching carefully. Switching between one and three phases requires the charger to support it and should never happen while current flows.
  5. Fail safe. If the grid meter stops reporting, stop adjusting and fall back to a fixed or paused state. Use the meter's retained Last Will status to detect this.
  6. Never control chargers over a public broker. Anyone could publish to your command topic. The public area is for simulation only.

Moving to a Private Broker

When the simulation behaves, connect the real charger or bridge and the controller to a private broker with TLS and per-client credentials. A managed MQTT broker such as ConnectMQ keeps that broker online without a server to maintain, and because your charger, meter reader and controller only make outbound connections, nothing needs to be exposed on your router. In the ConnectMQ portal, create a broker, issue credentials for each client and connect on port 8883:

client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id="YOUR_CLIENT_ID")
client.username_pw_set("YOUR_USERNAME", "YOUR_PASSWORD")
client.tls_set()
client.connect("broker.connectmq.com", 8883, keepalive=60)

Then pull in production data from solar inverter monitoring and storage from home battery monitoring to decide whether the car or the battery gets the surplus first. The IoT automation guide shows how these energy flows fit into a wider home setup, and the cloud MQTT broker guide helps you choose where to host it.

Connect Your Charger to ConnectMQ

ConnectMQ gives your charger bridge, energy manager and dashboards a private broker with TLS, WebSocket access for browser apps and a live topic explorer to verify every state change. Create a free Serverless broker at app.connectmq.com, no credit card required.

Frequently Asked Questions

Should my home charger use OCPP or MQTT?

Use whatever the charger supports natively for its core connection, and use MQTT as the integration layer for home automation. Many setups run OCPP to a local server and republish to MQTT.

Can I use OCPP and the vendor cloud at the same time?

Usually not, because a charger connects to a single OCPP backend. Pointing it to your own server typically replaces the vendor backend, so check which features you would lose.

What is the minimum charging current?

The IEC 61851 control pilot signalling used by AC chargers has a minimum of 6 A. Below that, the controller should pause charging instead of lowering the current further.

How fast should a surplus controller react?

Every 30 to 60 seconds is a sensible default. Faster reactions mostly chase passing clouds and cause unnecessary switching.

Related articles

Zigbee2MQTT Setup Guide: From Coordinator to Dashboard

Install Zigbee2MQTT with Docker, configure the coordinator and MQTT connection, pair and rename devices, control them over MQTT and view their states on a dashboard, testing first on a free public broker.

7 min read

All articles →

Your private MQTT broker is a few clicks away

Create an account, get your broker credentials and connect your first device — no servers to install, patch or scale.