Tutorial: Publishing an MQTT Discovery Payload That Reaches the Energy Dashboard

Contents
A device that announces itself over MQTT does so with a single message. It lands on a topic Home Assistant is already listening to, it carries JSON, and it has to be retained – otherwise it exists only until the next restart of either side.
Creating an entity that way is a matter of six lines. Getting that entity accepted as an energy source is a matter of three more, and those three are where the work actually sits: each of them is silently ignored when it is wrong, and the symptom is always the same empty dropdown.

The Topic Decides Where the Entity Appears
Discovery works over a topic pattern rather than a registration call. Home Assistant subscribes to a prefix, and everything published beneath it in the right shape becomes an entity.
homeassistant/sensor/kellerzaehler/energie/config
│ │ │ │
prefix component node_id object_id
The prefix is homeassistant unless it was changed in the MQTT integration. The component decides the entity type and has to match what the payload describes – a sensor for a reading, a binary_sensor for a yes-or-no state, a switch for something that can be commanded.
Node ID and object ID are free text and serve only to keep the topic unique. They are not the entity ID: that one is built from the name later, and changing the topic afterwards creates a second entity rather than renaming the first.
The Smallest Payload That Actually Works
Four keys are enough for an entity to exist. A name, the topic the readings arrive on, a unit, and an identifier that stays stable across restarts.
{
"name": "Cellar meter",
"state_topic": "haus/keller/zaehler",
"unit_of_measurement": "kWh",
"unique_id": "kellerzaehler_energie"
}
Of those four, unique_id is the one worth insisting on. Without it the entity shows up but cannot be renamed, cannot be assigned to an area, and cannot be selected in the Energy dashboard at all. It has to be unique across the whole installation, which is why the topic segments make a good basis for it.
The device block is optional. It groups entities that share the same device identifier under one device, making one device with eight readings easier to manage than eight separate entities.
"device": {
"identifiers": ["kellerzaehler"],
"name": "Cellar meter",
"manufacturer": "Eastron",
"model": "SDM630"
}
The Three Fields the Energy Dashboard Reads
An entity with a number in it is still not an energy source. Three fields have to agree before it turns up in the selection list, and all three describe something different.
| Field | Required value | What it says |
|---|---|---|
| device_class | energy | The number is an amount of energy, not a power |
| unit_of_measurement | an energy unit, e.g. kWh | The unit that amount is counted in |
| state_class | total_increasing | The value only ever counts upwards |
The most frequent mismatch is between the first two. A meter reporting watts is a power sensor: device_class power, state_class measurement, unit W. That sensor is useful and correct, and it will never appear as an energy source, because power is not energy. What the Energy dashboard wants is the counter that adds up.
The third field is the one that decides how a meter reset is handled. With total_increasing, a value that suddenly drops is read as a new counting cycle rather than as negative consumption, and the statistics carry on. With total, the same drop needs a last_reset timestamp to be interpreted, and without it the hour in question becomes a spike. For a physical meter that runs forwards and occasionally starts over, total_increasing is the honest choice.
Publishing It by Hand
Nothing about this needs a device. A single command creates the entity, and the same command with different content updates it.
mosquitto_pub -h 192.168.1.10 -u ha -P geheim -r \
-t "homeassistant/sensor/kellerzaehler/energie/config" \
-m '{
"name": "Cellar meter",
"state_topic": "haus/keller/zaehler",
"unit_of_measurement": "kWh",
"device_class": "energy",
"state_class": "total_increasing",
"unique_id": "kellerzaehler_energie",
"device": { "identifiers": ["kellerzaehler"], "name": "Cellar meter" }
}'
mosquitto_pub -h 192.168.1.10 -u ha -P geheim -r \
-t "haus/keller/zaehler" -m "18234.7"
The -r flag is the part that gets forgotten, and it belongs on both messages for different reasons. On the config topic it means the description survives a Home Assistant restart, which would otherwise wipe the entity until the device happens to announce itself again. On the state topic it means the entity has a value immediately after a restart instead of sitting unavailable until the next reading arrives.
One caveat about payload size: firmware that speaks discovery usually sends abbreviated keys – stat_t, dev_cla, stat_cla, unit_of_meas, uniq_id, dev. Both spellings work and can even be mixed. Handwritten payloads are easier to read in full form.
When the Entity Appears and Stays Empty
An entity that exists but shows nothing is almost always a topic mismatch: the state_topic in the payload and the topic the value is published on differ by a character. Subscribing to the wildcard shows both sides at once.
mosquitto_sub -h 192.168.1.10 -u ha -P geheim -v -t "haus/#"
If the value arrives but is nested inside a larger JSON message, a value_template picks it out – "value_template": "{{ value_json.total_kwh }}". Without it the entity receives the whole object and reports it as unknown.
And a note on a field that is easily set too short: expire_after marks the entity unavailable when no update arrives within the given number of seconds. On an energy counter that reveals a dead device without harming the long-term statistics, because unavailable states are skipped there and do not end a counting run. The timeout has to be well above the reporting interval, though: a meter that reports every fifteen minutes needs more than 900 seconds, or it shows as unavailable between two reports.
Patience helps with the last step. Long-term statistics are computed in five-minute intervals and the Energy dashboard aggregates by the hour, so a freshly created sensor stays blank until the first full hour has passed. That is not a fault, and re-publishing the payload does not speed it up.
Removing a Device Without Leaving a Ghost
Deleting the entity in the interface removes it until the next restart, at which point the retained config message recreates it. The removal has to happen where the creation happened.
mosquitto_pub -h 192.168.1.10 -u ha -P geheim -r \
-t "homeassistant/sensor/kellerzaehler/energie/config" -m ""
An empty retained payload on the config topic deletes both the entity and the stored message. The state topic keeps its own retained value and is worth clearing the same way, otherwise the next device to use that topic inherits a reading that belongs to something else.
Questions and answers
How does a bidirectional meter with import and export get into the Energy dashboard?
As two separate entities, not one. A bidirectional meter keeps two registers, one for import and one for export, labelled 1.8.0 and 2.8.0 in OBIS notation, and both only run forwards. Each gets a config message of its own with its own unique_id, both with device_class energy, state_class total_increasing and the same identifier in the device block, so that they appear under one device. The Energy dashboard asks for the two separately anyway when the grid is set up: energy taken from the grid and energy returned to it.
A single net value that falls during export, by contrast, does not fit total_increasing, which expects values that only count upwards; every drop would be read as a new counting cycle. If a device only supplies such a net value, or only the instantaneous power with a sign, the two directions are best separated before publishing. As a fallback they can be integrated from the power in Home Assistant, although that is less accurate than the meter’s own registers.
What permissions does a device that registers through discovery need on the broker?
Write access to its own config topic under homeassistant/ and to its own state topics, plus at most read access to homeassistant/status, where Home Assistant publishes “online” after every start and a device can then send its config again. Giving every device the same access as Home Assistant allows each of them to create any entity, or to delete one with an empty retained message. Mosquitto separates this through an ACL file with a user of its own for each device.