In previous notes we have explored how to integrate an IoT device (ESP8266) with an MQTT broker. The next step on this journey is to actually make such a device controllable from Home Assistant via the MQTT connection.
The work in this note will be based on the Home Assistant MQTT integration documentation.
There are three ways that devices can be “added” to the MQTT integration
Only the first option would be relatively “automatic” or “plug and play”, in the sense that one could point the device at the MQTT broker and (presumably) have it automatically “appear” in Home Assistant. The latter two options it appears would require either manually setting the configuration YAML in Home Assistant or manually entering some stuff in the UI. Additionally the “discovery” approach is taken by the Tasmota integration which is what I’m used to and I find works pretty well.
Hence we’ll try and set up a device here by MQTT discovery.
From the linked documentation page, these are the rough steps.
Step 1 - we’ll need to send discovery messages on a specific discovery topic
Step 2 - work out the right format for the discovery message
Step 3 - we’ll need to do that at the right time
There appear to be two options to send the discovery messages at the right time, which are following “birth” and “will” messages from Home Assistant, or publishing those discovery messages with the “retained” flag. Because we have explored subscribing to topics already in the previous notes and because there seem to be more disadvantages to the latter approach, in this note we’ll take the former approach.
Hence
Step 3a - we’ll need to send a discovery message when the device connects to the MQTT broker
Step 3b - we’ll need to send a discovery message when Home Assistant sends a “birth” message (announces that it’s online)
The discovery topic format is documented here https://www.home-assistant.io/integrations/mqtt/#discovery-topic
as
<discovery_prefix>/<component>/[<node_id>/]<object_id>/config.
<discovery_prefix> is a configuration item in the
MQTT integration in Home Assistant, which defaults to
homeassistant. I have not changed this in my Home
Assistant, hence that’s the right value to use.
For <component> we’ll use device
since we’ll be using the “newer” device discovery rather than component
discovery.
Per the documentation, since we will assign our devices a
unique_id, we can omit the <node_id>
level.
Per the documentation, since we will assign our devices a
unique_id, the <object_id> will be set
to that unique_id.
So for the case where the unique_id is
e.g. e1e080f341364ceeb6038a050ed7dd60 the discovery topic
would be
homeassistant/device/e1e080f341364ceeb6038a050ed7dd60/config.
NEXT: code to connect to and publish to discovery topic
This is the major unknown in this note, everything else is frankly pretty easy to figure out with the code given in previous notes.
Unfortunately, the documentation is not (at least in my opinion) particularly well organised, so here’s what I’m able to piece together.
The discovery payload is, unsurprisingly, a JSON.
For device discovery, the JSON has the following top level keys
device / dev *origin / ocommand_topicstate_topicqosencodingcomponents / cmpsdevice and origin, maps**, are
required.
command_topic, string, should be given for a device
which accepts commands, such as a switch or a light.
state_topic, string, should be given for a device which
can publish states to Home Assistant such as a switch (on/off) or a
sensor.
qos, integer, optional, I assume it sets the quality of
service for messages sent to the device? This is apparently not
documented on the page.
encoding, string, optional, appears to tell Home
Assistant the string encoding expected in messages from and to the
device, which defaults to UTF-8. UTF-8 “covers” the entire range of
ASCII encoding, outside of which our device will not step, so we can
ignore this key completed.
components is a map, with a key per “component” in the
device, each with an map value. A component is an independent “part” in
a device - for example a single switch might just have one switch
component, a temperature and humidity sensor might have two components
i.e. two sensors with different units of measurement,
* where abbreviated
keys are supported, I’ll give full_key /
abbreviated_key
** JSON objects, but I’ll call them maps because that comes more naturally to me
deviceTo find the full documentation for the device map I had to dive into the YAML configuration documentation for the specific MQTT integrations, for example buttons or switches.
I’m pretty sure the device entry is the same for all of the different integrations, but I don’t feel like comparing all 20 or however many there are…
Everything in this map is apparently optional, which is a bit
surprising considering the device key is required in the
device discovery payload?
So, I’m just going to start with whatever is suggested in the example discovery payloads, under the assumption that nothing here (except probably the ids) is that important for our non-commercial device anyway. These fields are
identifiers/ids, string or list of
strings, id or sequence of ids which uniquely identify the devicename, string, name of the device (presumably, human
friendly?)manufacturer / mf, string, self evident (I
think we’ll omit this one)model / mdl, string, also self evident (I
think we’ll omit this one)sw_version / sw, string, firmware
versionserial_number / sn, string, self
evidenthw_version / hw, string, hardware
versionFor identifiers we’ll just use the unique id for the
device, which we’ll take as a UUID. We’ll use that as
serial_number too.
For name we worked in previous notes on a switch, so
let’s call it “Test Switch”.
For sw_version and hw_version I’m sure a
real project would follow some well defined versioning scheme such as
semver. For simple learning projects like this, I prefer a simpler
approach, so we’ll just use an integer number, setting these both to
(string) “1”.
Assuming UUID c1d9bca9ddfc4803be31d7920d57f91e then
would give us a device map
{
"hw_version": "1",
"identifiers": "c1d9bca9ddfc4803be31d7920d57f91e",
"name": "Test Switch",
"serial_number": "c1d9bca9ddfc4803be31d7920d57f91e",
"sw_version": "1"
}originThe purpose of this field it seems is only to add context to the logs generated by Home Assistant relating to updates to the device, but it’s required for the device discovery payload, so add it we must. There are three keys
name, string, the documentation says “the name of the
application that is the origin of the discovered MQTT item”, though we
won’t have a separate application from the device in our case?sw_version / sw, string, software version
of this applicationsupport_url / url, string, URL for support
for the applicationOnly name is required so that’s all we’ll give, and
we’ll make it match the device name, Test Switch.
Hence our origin payload will be super simple
{
"name": "Test Switch"
}command_topic and
state_topicThese fields tell Home Assistant where it should publish commands to be consumed by the device and on which topic the device will publish updates about its state.
Luckily we have chosen a switch example to work on, which means we will need to set both.
Obviously these need to be unique so multiple devices don’t interfere with one another.
We could go super simple here, something like
command_topic ->
<<UUID>>/commandstate_topic ->
<<UUID>>/stateHowever I would generally go with something a bit more structured and
that provides a bit more context - remember in the previous note we saw
the Tasmota topics like stat/tasmota_DEVICE_SHORT_ID/RESULT
and cmnd/tasmota_DEVICE_SHORT_ID/Power1.
We might want to include the application name or the manufacturer name (if we had one) or the model name, but since we only have a device name for now, let’s use that to “namespace” our topics.
command_topic ->
test_switch/command/<<UUID>>state_topic ->
test_switch/state/<<UUID>>TODO: components
TODO
TODO