Data out: MQTT to Home Assistant¶
The bridge publishes each gateway to Home Assistant with MQTT Discovery: a retained config message per entity, then retained state messages as values change. Home Assistant builds the device and entities from the config messages. No custom integration is installed in Home Assistant.
Setting it up from the UI is covered in Settings → Home Assistant; fixing or removing entities in Fixing, moving and removing entities.
Identity is frozen
Everything on this page that names a device or entity (namespace default, device identifiers and names, unique_ids, topics, slugs) is frozen until the purge release (decision D1 in the Install lifecycle policy). This page describes what exists. Don't build on a change to it.
Names used below¶
| Placeholder | Meaning | Example |
|---|---|---|
<prefix> |
The bridge's namespace (Settings → Home Assistant → MQTT Broker → Topic Prefix). fwh_mb on new installs; older installs may still use the legacy franklinwh. |
fwh_mb |
<short_id> |
The device's topic id. The default gateway uses the last 8 characters of the aGate serial, <serial8>. Any other gateway uses its gateway id, lower-cased, with anything but letters and digits turned into _. |
<serial8>, demo |
<SERIAL> |
The full aGate serial. | |
<slug> |
The entity's slug, for example battery_soc. Listed in Settings → Home Assistant → Entities, or GET /api/mqtt/topics. |
battery_soc |
<component> |
Home Assistant platform: sensor, binary_sensor, select or number. |
sensor |
The discovery prefix is homeassistant, fixed.
Topics¶
| Topic | Retained | Payload | Direction |
|---|---|---|---|
homeassistant/<component>/<prefix>_<short_id>_<slug>/config |
yes | Discovery JSON (below). Empty = remove the entity. | bridge → HA |
<prefix>/<short_id>/<group>/<slug> |
yes | The value as text. binary_sensor: 1 / 0. |
bridge → HA |
<prefix>/<short_id>/availability |
yes | online / offline |
bridge → HA |
<prefix>/<short_id>/control/<slug>/set |
— | The new value as text | HA → bridge |
<group> in a state topic is one of battery, capacity, power, energy, solar,
status or control. It is a topic folder only; the publishing groups in Settings
are a separate thing.
Real topics for the demo gateway (short_id demo):
homeassistant/sensor/fwh_mb_demo_battery_soc/config
fwh_mb/demo/battery/battery_soc
fwh_mb/demo/power/grid_power_kw
homeassistant/binary_sensor/fwh_mb_demo_wset_enabled/config
fwh_mb/demo/status/wset_enabled
homeassistant/select/fwh_mb_demo_battery_command/config
fwh_mb/demo/control/battery_command
fwh_mb/demo/control/battery_command/set
fwh_mb/demo/availability
For the default gateway the same entities sit under fwh_mb/<serial8>/… and
homeassistant/sensor/fwh_mb_<serial8>_battery_soc/config.
To see everything a bridge publishes, with each topic:
It returns one block per publishing gateway (gateways[], each with short_id and
topics[]: slug, name, ha_type, source point, state, discovery and command topics).
Discovery payload¶
Built in publish/mqtt_publisher.py, build_discovery_payload(). For the default
gateway's State of Charge:
{
"unique_id": "fwh_mb_<serial8>_battery_soc",
"name": "State of Charge",
"state_topic": "fwh_mb/<serial8>/battery/battery_soc",
"availability_topic": "fwh_mb/<serial8>/availability",
"device": {
"identifiers": ["fwh_mb_modbus_<SERIAL>"],
"name": "FranklinWH Modbus <serial8>",
"model": "aGate",
"manufacturer": "FranklinWH Technologies Co., Ltd",
"serial_number": "<SERIAL>",
"sw_version": "Modbus Bridge v0.2.9",
"hw_version": "aGate <aGate firmware>"
},
"unit_of_measurement": "%",
"device_class": "battery",
"state_class": "measurement",
"icon": "mdi:battery"
}
A control entity adds command_topic (and options for a select, min / max / step
for a number):
{
"unique_id": "fwh_mb_<serial8>_battery_command",
"name": "Battery Command",
"state_topic": "fwh_mb/<serial8>/control/battery_command",
"command_topic": "fwh_mb/<serial8>/control/battery_command/set",
"options": ["Not Active", "Force Charge", "Force Discharge", "Force Standby", "Release"],
"...": "availability_topic and device as above"
}
Device and unique_id scheme¶
| Item | Default gateway | Any other gateway |
|---|---|---|
| unique_id | <prefix>_<serial8>_<slug> |
<prefix>_<short_id>_<slug> |
| Device identifier | <prefix>_modbus_<SERIAL> |
<prefix>_modbus_<gateway id>_<serial>, lower-cased and slugged |
| Device name | FranklinWH Modbus <serial8> |
FranklinWH Modbus <serial8>; a mock is FranklinWH Modbus <short_id> (mock) |
sw_version |
Modbus Bridge v<version> |
same |
modbus is in both the identifier and the name so Home Assistant never merges this
bridge's device with another FranklinWH integration's device for the same aGate. The
reasons are in Home Assistant device identity.
The MQTT client id is franklinwh_bridge_<8 hex>, unique per install.
When things are published and removed¶
| Event | What the bridge sends |
|---|---|
| Connect to the broker | online on each device's availability topic, then every discovery config (retained), then state as samples arrive. |
| Each poll | Retained state for each entity whose value was read. A value read as unknown, on entities that reset, is sent as None. A value not read this poll is not re-sent. |
| A battery command changes state | The command entities' state at once, without waiting for the next poll. |
| MQTT publishing switch off | offline on each availability topic, then disconnects. Home Assistant shows the entities as unavailable; they are not removed. |
| Bridge shutdown or restart | Disconnects without sending offline (see the note below). |
| A gateway's Publish this gateway switch off | An empty retained config for each of its entities (Home Assistant removes them) and offline on its availability topic. |
| A publishing group disabled | Its entities' configs are cleared, unless another enabled group holds them. |
Unpublish (POST /api/mqtt/unpublish) |
Empty retained configs for every entity and an empty availability. Stays unpublished across restarts until Republish (POST /api/mqtt/publish). |
| Change namespace | Removes this bridge's own retained configs under the old prefix (it reads them first and leaves another producer's alone), then publishes under the new one. |
Availability only goes offline from the switch
In 0.2.9 the bridge registers no MQTT last will, and its shutdown doesn't publish
offline. So when the bridge stops, restarts or loses power, the retained
availability stays online and Home Assistant keeps showing the last values. Watch
the values' age, or the bridge's own /api/status, if you need to detect that.
TLS
The MQTT Broker card stores a TLS Mode, but in 0.2.9 the publisher connects without TLS whatever it says. Keep the broker on a trusted network.
Command topics¶
The bridge subscribes to <prefix>/<short_id>/control/+/set for each gateway it
publishes controls for and passes the payload to that gateway's command handler: the same handler, slugs and
checks as POST /api/gateways/{gw_id}/command (see the
slug table). The Activity and control logs record
these as requested by mqtt.
# Force charge the demo gateway at 2 kW from any MQTT client on the broker:
mosquitto_pub -h <broker> -t fwh_mb/demo/control/battery_command_power/set -m 2000
mosquitto_pub -h <broker> -t fwh_mb/demo/control/battery_command/set -m "Force Charge"
mosquitto_pub -h <broker> -t fwh_mb/demo/control/battery_command/set -m Release
Only the bridge's own namespace is obeyed; a topic for an unknown short_id is logged and
ignored.
While the Control publishing group is off, the bridge unsubscribes from the command topics and refuses any command that still arrives (logged as "Command refused, its publishing group is off"). It subscribes again when Control is switched back on.
The broker is the security boundary
Command topics carry no bridge login. Anything that can publish to the broker can command the battery. Protect the broker with a username and password, and don't expose it outside your network, or turn off the Control publishing group if nothing should command the battery over MQTT.
Reading the data without Home Assistant¶
The state topics are plain retained MQTT, so any client can read them:
For history, the REST API has more than MQTT carries: GET /api/metrics,
GET /api/point-history/series and GET /api/energy/totals (see the
API reference).