Custom integration for the SmartPID M5 PRO (M5Stack, two-channel) thermostat controller. The device has no MQTT auto-discovery, so this integration publishes Home Assistant MQTT discovery configs on its behalf. Home Assistant's built-in MQTT integration then creates the entities and binds them to the SmartPID's own topics.
This integration is built for dual-boiler espresso machines, where the SmartPID M5 PRO's two channels each control one boiler:
- CH1 → brew boiler (Brühkessel) — the group-head temperature, typically around 90–96 °C. Hence the default setpoint limit of 0–98 °C.
- CH2 → steam/service boiler (Dampfkessel) — for steam and hot water, typically around 120–125 °C. Hence the default limit of 0–128 °C.
The per-channel temperature-history chart (with its setpoint ±2 °C tolerance band) is meant for exactly this: judging how tightly each boiler holds its target, which is what determines shot consistency.
I run this with my La Marzocco Linea Classic (a dual-boiler machine) retrofitted with a SmartPID M5 PRO. The defaults and the dashboard are tuned for that setup, but the limits are configurable for any dual-boiler machine.
-
You enter the 14-character device
<id>hash (e.g.a1b2c3d4e5f6a7) in the config flow. That is the only variable part of the topics —smartpidM5/prois fixed for the PRO model. Both the topic path and the deviceidentifiers/unique_idare derived from it. -
On setup the integration publishes all discovery configs retained to
homeassistant/<component>/smartpidM5_pro_<id>/<object>/config. -
Remove / Republish discovery topics — two buttons on the device page. Remove clears every discovery config by publishing an empty payload with
retain=True(the spec-compliant way to delete a retained message); Republish re-sends them. A normal setup/reload always re-publishes, and removing the integration entirely clears everything viaasync_remove_entry.Upgrading from ≤ 0.5.x: cleanup used to be a persistent option checkbox. Because it was re-evaluated on every startup, an entry left with it enabled would wipe its own discovery topics again on each restart/reinstall — the entities then showed up but stayed unavailable / un-enableable. It is now a one-shot button, and the stale option is stripped automatically on first load.
All three must be in place before you start:
- An MQTT broker, reachable by both Home Assistant and the SmartPID. The recommended setup is to run the broker directly on the Home Assistant host via the Mosquitto broker add-on (Settings → Add-ons → Add-on Store → Mosquitto broker). Home Assistant's MQTT integration (already required below) then connects to it — so device, broker and HA all live on the same host and network.
- MQTT integration configured and connected
(Settings → Devices & services → MQTT). Its discovery prefix must be the
default
homeassistant(MQTT → Configure → the prefix field). If you use a different prefix, changeDISCOVERY_PREFIXinconst.pyto match. - HACS installed and working (Settings → Devices & services → HACS).
The integration only listens; the SmartPID itself must publish to your broker. In the SmartPID M5 PRO's own configuration (its on-device / Wi-Fi setup):
- Set the MQTT broker address to your broker — i.e. the IP address of the
Home Assistant host if you run Mosquitto there (e.g.
192.168.1.50), port1883for a standard unencrypted connection. - Set the MQTT username and password to match your broker's credentials — required if the broker enforces authentication (the Mosquitto add-on does by default, using a Home Assistant user you create for it). Leave them empty only if the broker explicitly allows anonymous access.
If the address or credentials are wrong, the device connects to nothing and no entities will ever receive data — this is the single most common cause of a "working" integration with no values.
Follow the steps in this order.
Every topic contains a device-specific <id> such as a1b2c3d4e5f6a7. To read it:
- Settings → Devices & services → MQTT → Configure.
- Under Listen to a topic, enter
smartpidM5/pro/#and press Start listening. - Power on the SmartPID. Incoming topics look like
smartpidM5/pro/a1b2c3d4e5f6a7/dynamic/CH1. - The 14 characters between
smartpidM5/pro/and the next/are your ID.
- Open HACS.
- Top-right ⋮ menu → Custom repositories.
- Repository:
https://github.com/secuspec/homeassistant-smartpid-md5Type:Integration→ Add. (The repository must be public — HACS cannot access private repositories.) - Close the dialog, search HACS for “SmartPID M5 PRO”, open it → Download.
- Restart Home Assistant (Settings → System → top-right ⋮ → Restart).
HACS places the files in
custom_components/smartpid_md5/for you — no manual file copying.
- Settings → Devices & services → Add integration → search “SmartPID M5 PRO”.
- Enter the 14-character device ID from Step 1 (and, optionally, a device name) → Submit.
- The entities appear immediately. Optionally set the setpoint limits under Configure (see Configurable setpoint limits below).
Copy the custom_components/smartpid_md5/ folder into
<config>/custom_components/, restart Home Assistant, then do Step 3.
| Entity | Type | Source / command |
|---|---|---|
| Temperature | sensor | dynamic/CHx → temp |
| Setpoint (readback) | sensor | dynamic/CHx → SP |
| Power | sensor (%) | dynamic/CHx → pwm |
| Mode | sensor | dynamic/CHx → mode |
| Run Mode | sensor | dynamic/CHx → runmode |
| Countdown / Countup | sensor (s) | dynamic/CHx |
| Setpoint | number | command {"CHx SP": <value>} |
| Profile | select (1–10) | command {"CHx profile": <n>} |
| Run | switch | on {"CHx profile":1,"start":"standard"}, off {"stop":true} |
Plus device-level diagnostics (IP, SSID, Serial) from status and the last
events/standard / events/advanced event.
Every entity gets a deterministic entity id (<domain>.smartpid_<slug>, e.g.
number.smartpid_ch1_setpoint), pinned via the MQTT discovery default_entity_id
key so the bundled dashboard can reference stable ids. This is unique for a
single SmartPID device; a second device would collide and HA would suffix the
ids. (Older integration releases used the object_id key, which current Home
Assistant ignores — see the migration note under Confirm the entity IDs.)
Settings → Devices & Services → SmartPID M5 PRO → Configure exposes the per-channel setpoint range:
| Option | Default |
|---|---|
| CH1 minimum / maximum | 0 / 98 °C |
| CH2 minimum / maximum | 0 / 128 °C |
These bound the setpoint number entity, so both the dashboard slider and the
numeric input box honor them; the input field won't submit an out-of-range value.
(The bound is enforced in the frontend against the entity's min/max; the
device firmware accepts a wider range.) Changing the limits re-publishes the
discovery configs automatically.
dashboards/smartpid-dashboard.yaml is a ready-made Lovelace dashboard titled
“La Marzocco Linea Smartpid M5Pro” (freely editable). Each channel gets its
own section: current-temperature tile, a setpoint slider, a typeable
numeric setpoint field, run/mode/power, and a continuous temperature-history
chart with a setpoint ±2 °C tolerance band for reading stability.
HACS cannot install a dashboard config. The HACS “Dashboard” type only installs frontend cards (JavaScript), not a finished dashboard. So the integration and the ApexCharts card install via HACS with no file copying, but the dashboard itself is pasted once into the UI (Step 3 below). This is a HACS limitation, not an oversight.
The history charts need it.
- Open HACS and search for “ApexCharts Card” (it is in the default HACS store — no custom repository needed).
- Open it → Download → Restart Home Assistant (or reload resources).
If your dashboards run in YAML mode, also add the resource manually: Settings → Dashboards → ⋮ → Resources →
/hacsfiles/apexcharts-card/apexcharts-card.js, type JavaScript Module. In the default (UI/storage) mode HACS registers it automatically.
The dashboard references fixed entity IDs. After adding the integration, open Developer Tools → States and confirm these ten exist exactly:
sensor.smartpid_ch1_temp sensor.smartpid_ch2_temp
number.smartpid_ch1_setpoint number.smartpid_ch2_setpoint
switch.smartpid_ch1_run switch.smartpid_ch2_run
sensor.smartpid_ch1_mode sensor.smartpid_ch2_mode
sensor.smartpid_ch1_pwm sensor.smartpid_ch2_pwm
If instead you see names like sensor.smartpid_m5_pro_ch1_temperature, or a _2
suffix, the entities were first registered by an integration release ≤ 0.6.0,
which pinned ids via the old object_id discovery key that current Home Assistant
ignores — so HA fell back to name-derived ids and stored them in the registry.
Since 0.7.0 the ids are pinned via default_entity_id, but that only applies
to entities HA registers fresh: existing registry entries keep their old ids.
Migration — do this once after updating:
- Settings → Devices & Services → SmartPID M5 PRO → the device → ⋮ → Delete.
- Add integration → re-add with the same 14-character device ID
(Installation Step 3). HA now registers the entities with the correct
smartpid_<slug>ids. - Re-check the ten ids above.
(Alternatively, rename each entity's id by hand under Settings → Entities, or edit the dashboard YAML to match your existing ids — but deleting and re-adding is cleaner.)
- Settings → Dashboards → Add dashboard → New dashboard from scratch, give it any title → Create.
- Open it → top-right ✏️ Edit → ⋮ → Raw configuration editor.
- Select all, delete, then paste the full contents of
dashboards/smartpid-dashboard.yaml→ Save.
The dashboard’s own title: line pre-fills “La Marzocco Linea Smartpid
M5Pro”; rename it freely.
- Two payload shapes.
dynamic/CHxcarriesSP,mode,pwm,countdown,countuponly in run mode; in monitor mode those fields are absent. All optional fields usedefault('')in the value template, so they read as unknown until the device is running. stopis global. The device's stop command halts the whole controller, so turning either channel's Run switch off stops both channels.- No
relayfield on PRO. (An earlier init script keyed a relay switch onvalue_json.relay, which only exists on the MINI model — it never populated on the PRO.) The Run switch derives state fromrunmodeinstead;pwmshows heating power. - Availability is not modeled. The
statustopic is published on-demand only ({"status": true}command) and the firmware documents no MQTT LWT, so there is no reliable connected/disconnected signal. If you want a staleness indicator, addexpire_afterto the temperature entities — but only if your device publishesdynamicdata periodically while idle. - Temperature unit is assumed °C. The device also reports
unit, but MQTT discovery unit is static. Change it indiscovery.pyif you run in °F.