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.
A device block is optional and pays for itself immediately: entities that share the same identifier are grouped under one device, and one device with eight readings is far easier to live with than eight loose 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 | Wh, kWh, MJ or GJ | 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 warning about a field that looks helpful: expire_after marks the entity unavailable when no update arrives within the given number of seconds. On a switch that is useful. On an energy counter it tears holes into the long-term statistics, because every gap ends a counting run. A meter that reports every fifteen minutes is better off without it.
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.