Skip to content

Proposal of an MQTT Smarthome 2.0 (Sketch)

Simon Christmann edited this page Dec 23, 2017 · 6 revisions

Let me introduce a few words that we'll be using in this document:

Datapoint: The semantic description for a certain datapoint, e.g. "hue of a light bulb"
Value: The numerical/boolean/whatever value that is conencted to one datapoint
State: A combination of one or multiple datapoints that are needed to give a representation of a device's state

Topic: The actuall MQTT topic a message will be posted on.
Payload: The actuall MQTT payload of such a message. Datatype can be JSON or String.
Message: Topic with payload

Device: Typically a physical device such as an AVR or a bridge to other home automation systems such as KNX/Homematic.

Service: One device can have multiple services. A service can have exactly one state.

Datapoints and Topics

For e.g. an RGB bulb has 3 relevant datapoints to make an overal state:

  • hue
  • saturation
  • brightness

with the according values. In this case it makes no sense to broadcast status messages for a single datapoint per topic, e.g.

            datapoint -> value

hue1/status/light/hue -> 1.0
hue1/status/light/sat -> 1.0
hue1/status/light/bri -> 1.0

because to determine the state, all three datapoints are needed.
It is usually not a problem to split these topics, but let's say a script crashes and needs to be restarted. The user then manually changes just the brightness of the bulb. The freshly started script will receive just the brightness and still has no data about the current color of the bulb in a webinterface. Whenever the state of a bulb changes, it makes sense to publish the complete new state, wrapped in a JSON string on one topic:

hue1/status/light -> { "hue":1.0, "sat":1.0, "bri":1.0 }

                  -> { datapoint1: value1, datapoint2: value2, ...}
                  -> state

This structure is also easier to process for a watchdog.

Furthermore: let's say we have two topics like

hue1/status/lights/state -> 1.0
hue1/status/lights/hs -> {hue: 1.0, sat: 1.0}

floating around. When mode is switched to ct, the topic hue1/status/lights/hs becomes logically invalid (because the bulb doesn't have a hue anymore). Instead a new datapoint, containing the color temperature becomes relevant:

hue1/status/lights/ct -> 5000 (in Kelvin)

Here it would also be a better choice to publish all relevant datapoints in one topic, so that

hue1/status/lights -> { state: 1.0, hs: {hue: 1.0, sat: 1.0} }

would change to:

hue1/status/lights -> { state: 1.0, ct: 5000 }

This way other scripts need less logic than before. Formerly a subscriber needs to know, what to do when a ct message arrives (drop the old hs value out of its database). With ne new proposal we can just overwrite the whole state variable with the new topic payload, the contents of the topic will always contain the complete and most recent state.

Implementation

We don't want to restrict developers into any scheme of topics. Therefore, a suggestion is to implement libraries that allow developers to choose the depth of their topic structure themselves. See test.js and test-result.png here.

However, we encourage to implement topic/datapoint combinations that make "sense", like mentioned above.

Meta information

It is not neccessary to provide metadata for your topics, however it would be nice to have a way to provide documentation for other scripts about topic structure, units of values etc.

Getting back to the example above:

hue1/status/light -> { "hue":1.0, "sat":1.0, "bri":1.0 }

It's for every developer to decide with unit he wants to use. A hue (that doesn't have a unit) for e.g. can be expressed in many ways: 0 - 1.0, 0% - 100%, 0 - 65535 (Philips Hue), etc.

Developers are however encouraged to use physical units whenever possible (especially for sensors), e.g. temperature (°C), brightness (lux), power consumption (watts). For e.g. the brige for Homematic brightness sensors would need to implement a conversion.

It would also be nice to provide a description for contents of a topic. Homematic for e.g. provides a native description for "paramsets" (generated by hmGetInfo), like this:

"SET_TEMPERATURE": {
  "VALUE": 4.5,
  "CONTROL": "HEATING_CONTROL.SETPOINT",
  "DEFAULT": 20,
  "FLAGS": {
    "VISIBLE": true,
    "INTERNAL": false,
    "TRANSFORM": false,
    "SERVICE": false,
    "STICKY": false
  },
  "ID": "SET_TEMPERATURE",
  "MAX": 30.5,
  "MIN": 4.5,
  "OPERATIONS": {
    "READ": true,
    "WRITE": true,
    "EVENT": true
  },
  "TAB_ORDER": 6,
  "TYPE": "FLOAT",
  "UNIT": "°C"
}

For an mqtt-smarthome, this description could look similar, e.g.:

{
    "hue1/status/light": {
        "hue": {
            "default": 0,
            "min": 0,
            "max": 360,
            "unit": "°",
            "type": "int",
            "refreshInterval": 10000
        },
        "sat": { //...
        }
    }

    "topic": {
        "datapoint1" : { ... },
        "datapoint2" : { ... }
    }
}

Services

A single device can have multiple services. For e.g. the ESP8266 DevilRemote has an output that sets the speaker's volume and a DS18B20 sensor measuring the temperature. These two services could be:

devilremote123/_/hifi -> {"volume":1.0, "bass":"-1.0"}
devilremote123/_/temperature -> 21.0

General structure of topics and datapoints

The "1.0" topic structure is optimized for bridges:

bridge/_/deviceID/datapoint -> value

vs.

manufacturer/_/device_ID/service/datapoint -> value
bridge      /_/device_ID/service/datapoint -> value

Maintenance and unreachable detection

bridge/connected -> 0 | 1 | 2 (last will message)

vs.

manufacturer/maintenance/device_ID/available -> true | false (last will message)
bridgeName  /maintenance/bridge   /available -> true | false (last will message)
bridgeName  /maintenance/device_ID/available -> true | false

Introduce maintenance status messages instead of the former connected:

device/maintenance/#

Topics below this level can be used to determine the health of a device or a bridge. For e.g. you could implement the topics

device/maintenance/available -> true | false (last will message, similar to the former /connected)
device/maintenance/uptime
device/maintenance/temperature/cpu

Retained messages + Watchdog

When a bridge service publishes retained topics of for e.g. a wirelessly connected device, the message will be preserved even if the device's battery dies or becomes unreachable in any other way.
Let's take a battery driven, wirelessly attached Homematic sensor as an example:

hm-MQ1234567-3/status/brightness -> 1000.0

It is known that the brightness is refreshed once every 6mins. Without retained messages, when a new client joins the MQTT broker, it won't know the current daylight brightness until the sensor broadcasts its state again. However, with retained messages this newly connected client will always get a value, even if the sensor becomes unavailable for any reason.

In MQTT-Smarthome "1.0" this was solved by introducing the ts datapoint. But this involves additional logic on the client side, because you have to check every message for its validity first. Let's say we have a watchdog, that either knows how long the interal of a sensor is from a database (mqtt-meta), calculates it by mesuring the average time inbetween messages or gets the value set via MQTT itself:

Bridge sends:

watchdog/set/timeout -> [ {"topic":"hm-MQ1234567-3/status/brightness", "seconds":360},
                          {"topic":"hm-MQ1234567-3/status/motion",     "seconds":360} ]

The watchdog then sets a timeout for 6mins (plus tollerance) and subscribes to the topics. Whenever a new message arrives, the timout timer is reset, because the device is (obviously) still alive. When the message doesn't arrive anymore, it can mark the device as unavailable an post a null message to the according topic to clear the retained message, that now became invalid.

The watchdog can also subscribe to

+/maintenance/available

and clear the device's topics when a device marks itself as offline via last will.

The watchdog should run on the same host as the MQTT broker.