Hi everyone ![]()
After spending quite some time digging into Shelly payloads, I’m presenting you an external Shelly integration for Gladys, in version 1.0.0.
Repository: GitHub - Terdious/gladys-shelly · GitHub
Documentation: French · English
In a nutshell
Relays, smart plugs, and Shelly energy meters, locally first, without account and without cloud. A typical installation requires a completely empty configuration form: you install, you run a scan, you add your devices.
Are you already controlling your Shelly devices via a bridge?
Pierre-Gilles just published his position on the subject — Experience report: why I now recommend external integrations — and I fully share it.
If your Shelly devices currently go through an intermediate layer, you can switch them here. What you concretely gain: nothing to maintain between Gladys and your devices, and above all 100% of what the hardware can do, without going through a standard that first needs to know how to express it. A Pro 3EM comes out with its 24 features — the three phases separately, active and apparent power, voltage, current, neutral current, and the eight energy meters for consumed and reinjected energy.
Just one precaution: remove them from the old bridge before adding them here, otherwise you will see each Shelly device twice.
What it can do
Local, and in real-time. The integration speaks the RPC Gen2+ protocol directly to your devices, on a WebSocket that the device itself feeds: a relay switched at the wall switch reports back in about one second, not at the next poll.
All generations. Gen2 and later (Plus, Pro, Mini, Gen3, Gen4) and Gen1 (Shelly 1, 1PM, 2.5, Plug S, EM, 3EM). Gen1 devices speak a completely different API — REST instead of JSON-RPC, Basic instead of Digest — but they are normalized to the same model: a 3EM Gen1 exposes exactly the same 24 features as a Pro 3EM Gen2.
Three transports, from best to last resort: local → MQTT → cloud. Each has its reason to exist:
- local is the nominal path;
- MQTT (optional) is the most reliable way to find a large installation. mDNS announces itself in short multicast bursts that are easy to miss: at my place, the same park sometimes came back to 19, sometimes to 27 devices, with some systematically absent. A Shelly that publishes on your broker announces itself permanently — it is discovered, it reports in real-time, and it remains controllable even if it is unreachable locally;
- Shelly Cloud (optional) takes over when a device is not reachable either locally or via MQTT.
Gladys displays the transport actually used, device by device, with the graded badge and its reason when it is not the nominal path.
The device model is deduced, not hard-coded. Features come from the components that the device actually declares in Shelly.GetStatus, never from a model table. A Pro 1 gets an On/Off, a Pro 1PM gets metrology in addition — and a Shelly released after this code still works, as long as it speaks the documented vocabulary.
Covered components: switch, em, emdata, em1, em1data, pm1, temperature, humidity, devicepower.
Real-time, and why it is adjustable
Gladys accepts 300 states per minute per integration. A single Pro 3EM pushes approximately one update per second on ~25 measurements: transmitting everything as-is would make ~900 states/minute, three times the ceiling.
Values are therefore split into two channels:
- a real-time channel (5 s by default, adjustable from 1 s to 30 s) that carries all instantaneous powers — total, per phase, per relay — and all on/off states;
- the rest (voltages, currents, apparent powers, energy meters, temperatures) follows the refresh interval, but served from the freshest pushed value, without HTTP round trips.
The point I care about: your setting is a floor, not a promise. The cost of the channel depends on the park, not on the setting — a value that never moves costs nothing. The integration measures what it actually publishes and extends its own interval if the park becomes too large, writing it in the logs, then returns to itself. A state refused by Gladys would be invisible; a slowed-down channel, not.
And every minute, a line says exactly where you stand:
Real-time lane: 178 state(s) published in the last minute (every 5s, from
5 WebSocket and 15 MQTT device(s)); 243/300 states/min of the Gladys budget used
Installation
In Gladys: Integrations → Install an integration → Shelly → Install, then Discovery → Scan.
There is nothing to fill for a 100% local installation. The fields (additional addresses, device password, MQTT broker, cloud key) only serve if your installation needs them, and each is explained directly in the form.
(image 4: the configuration page, empty form)
Requires Gladys ≥ 4.83.0. If a device remains undetectable during the scan, the documentation explains what to look at — and configuring MQTT is almost always the answer on a large installation.
Tested on
My installation: 19 Shelly devices, 10 x Pro 3EM, 1 x Pro 4PM, 2 x Pro 3, 1 x Plus 2PM, 2 x Plus Plug S, and 3 x 3EM Gen1. Local, MQTT, and both combined.
Thanks @pierre-gilles 
By the way, I hit a core limit: publishDiscoveredDevices() was getting a PayloadTooLargeError beyond a dozen devices rich in features (a Pro 3EM = 24 features ≈ 8.4 Ko). Pierre-Gilles fixed this very quickly on the core side (PR #2732). Until everyone is up to date, the integration publishes the largest acceptable subset and logs by name what has been discarded, rather than silently losing the scan.
What’s next
Not yet supported, due to lack of hardware to validate: roller shutters (cover), variable lighting (light), and inputs (input). The code is ready to welcome them, I’m missing the devices: if you have them and want to test a :dev image, let me know, I’ll prepare it with pleasure — it’s the best way for it to arrive quickly.
Shelly BLU devices are not managed either: they speak Bluetooth, and the integration does not do BLE.
The roadmap is open: Roadmap — Shelly external integration · Issue #1 · Terdious/gladys-shelly · GitHub
Bugs, feedback, exotic device payloads: issues are wide open. And if you test, tell me what it gives — especially on models that I don’t have, and especially on models that I don’t have ![]()


