Skip to content

# MQTTController

MQTTController is the Reactor interface to the world of MQTT. It requires a MQTT broker running somewhere on the network (MQTTController is not itself a broker, it's a client). The MQTT broker must be fully MQTT 3.1.1 compliant (or higher). The reference broker used for development and testing of MQTTController is Eclipse Mosquitto. Most Linux distributions can install this from their package managers, and docker containers are also readily available.

openLuup MQTT Broker

Users choosing the MQTT broker built in to openLuup must be running at least openLuup version 2022.12.2. Earlier versions are not supported. Additionally, this broker does not support subscriptions at QoS levels 1 and 2, and does not persist retained topics across restarts of openLuup, so it's not fully 3.1.1 compliant. Still, it has basic functionality and is convenient if you're already running openLuup, just be aware that retained messages can be lost during reboots/recoveries, and this can lead to entity states being inconsistent with their devices/clients and thus incorrect automation execution. The `init` configuration key described in Additional Entity Configuration below, when used with a device/client inquiry topic to restore synchronization of data, is a possible work-around.

There are three functions that MQTTController performs at the highest level of perspective:

1. It maps devices that send and receive MQTT events into Reactor entities. For example, a Shelly1 relay device or an ESP8266 flashed with Tasmota can connect to an MQTT broker and exchange messages. Reactor connected to the same broker will "hear" status updates published by connected devices, and can update attributes on a Reactor entity accordingly (e.g. set the temperature_sensor.value attribute to the value of the temperature published), and actions performed on the Reactor entity can be turned into command messages to the device.
2. It can "echo" any entity in Reactor to the MQTT universe, effectively acting as a gateway so that all entities known to Reactor are published.
3. Commands can be issued to Reactor using MQTT to run actions on any Reactor entity.

MQTTController can also serve as a local gateway for MQTT messages for other Reactor controllers (that is, other controllers can ask MQTTController to subscribe to topics and exchange datagrams on their behalf).

## Installation

MQTTController is not included in base Reactor distributions. It must be installed separately by downloading the package from the extras subdirectory of the Reactor download server.

Once the package file (a ZIP or Gzip'd tar archive) is downloaded:

1. Create, if it does not already exist, a directory called `ext` in your Reactor install directory (so it should be at the same level as `config`, `storage`, etc.).

       cd /path/to/reactor
       mkdir ext

   If you are running Reactor in a docker container, the `ext` directory should be created in the data directory, where your `config` and `storage` directories live as indicated above.

2. Change directory into the new `ext` directory:

       cd ext

3. Unpack the archive (use whichever command is correct for the type of archive downloaded):

       tar xzvf /path/to/MQTTController-XXXXX.tar.gz    # or...
       unzip /path/to/MQTTController-XXXXX.zip

4. Run the install script. If you are using a "bare metal" install (not a docker container):

       cd MQTTController
       ./install.sh

   If you are running Reactor in a docker container, we will open a container shell in which to do the install (the Reactor container must be up and running):

       docker exec -it <container-name> /bin/sh
       cd /var/reactor/ext/MQTTController
       ./install.sh
       exit

From here, proceed to Basic Configuration below.

## Basic Configuration

In order to use MQTTController at all, you have to add an entry for it to the controllers section of your `reactor.yaml` file.

    controllers:
      # Your existing controllers will be below the above line.
      # Add the following after the last "- id" line in this
      # section.
      - id: mqtt
        name: MQTT
        enabled: true
        implementation: MQTTController
        config:
          # Replace IP with that of your MQTT broker below
          source: "mqtt://127.0.0.1:1883/"

Restart Reactor to make the changes take effect. After that, you should be able to refresh the UI, go the Entities list, clear any existing filters, and choose "MQTT" from the controllers filter selector. That should then show you two entities: the MQTT controller system entity, and its default group entity. If you don't see this, check the log for errors.

If you need to authenticate with your MQTT broker, you can supply `username` and `password` fields at the same level as `source`.

## Configuring Devices

Devices are configured within an `entities` subsection of the `config` section for MQTTContoller.

MQTTController uses a data-driven model to map published messages to attributes on Reactor entities, and entity actions to published commands. There are two ways to map a device:

1. If an existing preconfigured template will work for your device, you provide the topic that identifies your device, and tell MQTTController what template to apply to it.
2. If there is no existing template that works for your device, you create one by telling MQTTController what Reactor capabilities the device has, and how to map published messages with state/telemetry updates into the attributes of those capabilities. You will also tell Reactor how to take actions in those capabilities and map them to topics to be published to make the device do what you need it to do.

The preconfigured templates available are listed at the bottom of this page. They are defined in `mqtt_devices.yaml`, in the `templates` section. Do not modify this file. This is a system file. There is a separate mechanism for you to define your own templates without modifying the system file.

### Easy Device Configuration - Using Existing Template

As an example, let's say we have a Shelly1 configured with the topic `shelly1_43DFD2` that is configured to operate as a single relay to operate a stairway light. To create an entity for this device, we can use the `shelly_relay` template. The template supports multi-relay devices, but the Shelly1 only has a single relay on channel 0:

      config:
        source: "mqtt://127.0.0.1:1883/"
        entities:
          shelly1_stairway:
            name: "Stairway Light"
            topic: shelly1_43DFD2
            include: shelly_relay
            channel: 0

After applying this configuration, we restart Reactor, and then refresh the UI. We should then be able to find the Stairway Light entity in the Entities list. You should be able to use the `power_switch.on` and `.off` actions to turn it on and off, and observe that the state correctly tracks that of the device, even when operated from the Shelly mobile app or any other means.

!!! tip Use Retained Messages! It is strongly recommended that you configure your devices to use retained MQTT messages. This ensures that Reactor (and any other MQTT-connected apps you may be using, like Hass) get the latest state from the MQTT broker automatically at startup. Some devices, notably Tasmota, do not have MQTT retain on by default (a poor choice for IoT devices, IMO). On Tasmota devices, you can turn it on by using the `PowerRetain on`, `SwitchRetain on`, `SensorRetain on` and `StateRetain on` commands in the Tasmota console. Check with your device's documentation for the default state of the retain flag on messages it publishes, and how to turn it on if the default is off.

### Detailed Device Configuration

If an existing template doesn't supply the behavior you need, you can configure the entity directly. Later, if you wish, you can turn your configuration into a reusable template, so if you have many similar devices, the configuration can be shared between then.

To get MQTTController to create and maintain an entity for your MQTT-enabled device, you need to create a mapping configuration that tells MQTTController:

- What type of entity to create;
- What capabilities it will have;
- How to turn state/status topics received for the node into attribute values on the entity;
- How to turn action requests for the entity into published MQTT messages.

To illustrate how this is done, let's implement a configuration for a Tasmota in generic/relay configuration (there's a template for this, but we're doing it explicitly here to show how that's done). Our Tasmota device is configured for a single relay, basically a switch, so we're going to map it to a Switch type entity with the two capabilities that most switches have in Reactor: `power_switch` and `toggle`.

We'll begin with our basic entity configuration. First, we need to assign a unique ID for the entity. To make it easy, we're going to use the Tasmota node's ID:

      - id: mqtt
        config:
          source: "mqtt://127.0.0.1:1883/"
          entities:
            "tasmota_FFFFFF_relay":  # this is your new entity's ID
              name: "Tasmota Relay"
              topic: "tasmota_FFFFFF"

Rules for Assigning Entity IDs

Entity IDs are identifiers, not names, and as such, the rules for valid IDs are strict: they must be between 1 and 64 characters in length; they may contain only upper- and lower-case letters A-Z, digits 0-9, and underscore; the ID must be unique on that MQTTController instance (it does not need to be unique system-wide). It should also never change, as rules and reactions will use a stored copy of the entity ID to find your entity, so if you change it, those rules and reactions will not find your entity and throw an error until you update them as well.

Now we can get into configuring what the entity can do:

            "tasmota_FFFFFF_relay":
              name: "Tasmota Relay"
              topic: "tasmota_FFFFFF"
              type: Switch
              capabilities:
                - power_switch
                - toggle
              primary_attribute: power_switch.state

OK, the above additions tell MQTTController that we're going to create a Switch type entity, and it will have capabilities `power_switch` and `toggle`. Its primary attribute will be `power_switch.state`.

Info

Don't worry if you don't recognize the Switch entity type, or know what you should put there. This value is used by the Dashboard to guess a default configuration for tiles, but since the Dashboard isn't really the focus of work right now, you can ignore this value. In fact, you can leave it out altogether; it won't hurt anything.

The `power_switch` capability is the only one of the two being assigned that has any attributes, and its only attribute is that `state` attribute being assigned as the primary. We need to get the value out of status update topics we receive and into that attribute. Tasmota sends topics of the form `stat/nodeID/POWER` for the status of a single relay device. When the relay is turned on, the payload is the string `on`, and when it's turned off, the payload is `off`. We'll create an `events` section, and put a handler in for that topic.

              primary_attribute: power_switch.state
              events:
                "stat/%topic%/POWER":
                  "power_switch.state":
                    expr: "bool(payload)"

The first thing to notice is the `%topic%` string in the event topic. This is a substitution: MQTTController will put the string in the configuration value `topic` for this entity into that string. We could also have just hard coded it (that is, use `stat/tasmota_FFFFFF/POWER`), but if we want to make a template out of this later, it's better to use the substitution.

Having told MQTTController what topic this entity is interested in, we then tell it what attributes that topic can effect. This topic only affects one attribute, `power_switch.state`, and we use an expression to convert the payload string (`on` or `off` as stated above) to the boolean type that is required for `power_switch.state`. The expression here is trivially easy because the function `bool()` recognizes and converts the string `on`, `yes` and `true` to true, and `off`, `no` and `false` to false, and is not case-sensitive. The `payload` variable is defined for all topics received and will be set to the received payload.

If that topic could affect more attributes, we would list them using the same structure as that described for `power_switch.state`.

Likewise, if there were other topics that could affect attributes (including other topics that would affect `power_switch.state`), we could just list them in the events section and structure them as we have done so far. MQTTController will make sure every topic gets processed by every entity that is interested in it, and every attribute that can be affected by it.

Note that not every topic/payload sent may contain the data necessary for every attribute. To avoid errors from expressions and/or accidentally setting your attribute values to null in these cases, you can add `if_expr` to test if the payload contains the data needed to update the attribute. The expression should result in boolean true or false; if false, the attribute will not be evaluated further and will not be modified from its current value. Here's what that looks like for an additional Tasmota topic:

              events:
                "stat/%topic%/POWER":
                  "power_switch.state":
                    expr: "bool(payload)"
                "tele/%topic%/STATE":
                  "power_switch.state":
                    json_payload: true
                    if_expr: '! isnull( payload?.POWER )'
                    expr: 'upper( payload.POWER ) == "ON"'

Also note that we've set `json_payload` to true here. This tells MQTTController to parse the payload as JSON and make it available as a real object, rather than a long text string, to your expression(s) for that attribute. If the payload is not parsable as JSON, the resulting object will be null, so notice that we've guarded the `if_expr` for this case using a coalescing member access operator (`?.`). The result of this `if_expr` is therefore false if either (a) the payload isn't JSON, or (b) it's a JSON payload but the resulting object does not contain a member named `POWER`.

Shortcut Extraction of JSON Object Value

If you are just drawing one value without modification from a JSON object payload, you can use `source` instead of `expr` to pull the value. For example, `source: switch.status` would be equivalent to `expr: payload?.switch?.status`. Using `source` is more efficient than `expr` to access simple unmodified values. Note, however, that `source` does not support array indexing (i.e. you cannot use it to substitute for the expression `payload?.switch?[0]?.status`, which contains an array element reference).

Now let's move on to actions. In the `actions` section, we list each capability that has actions, and within each of those, we list each action and its implementation. Let's start with `power_switch.on` and `power_switch.off`. These simple actions take no parameters, so their implementation is very simple:

              events:
                 ...
              actions:
                power_switch:
                  "on":
                    topic: "cmnd/%topic%/Power"
                    payload: "on"
                  "off":
                    topic: "cmnd/%topic%/Power"
                    payload: "off"

Under each action, the required value `topic` sets the topic to be published to implement the action. If the topic has a payload, then the `payload` value can be added. The `payload` shown here for both actions is just a string, because the payload for these commands is a string, and in this case, it's a fixed string (it is always `on` for the on action and `off` for the off action. The `power_switch.set` action makes things a little more complex, because it takes a boolean parameter named `state` to determine what to do with the light/switch/relay. Here's that implementation:

              actions:
                power_switch:
                  ...
                  set:
                    topic: "cmnd/%topic%/Power"
                    payload:
                      expr: "parameters.state ? 'on' : 'off'"
                      type: raw

In this implementation, the topic is the same, but the payload is more dynamic. The `payload` value is an object with two keys. The `expr` key/value is an expression that figures out whether to return the string `on` or `off` based on the value of the `state` parameter. The `type` key/value of `raw` tells MQTTController that the string returned by the expression should not receive any translation. So the payload thus becomes the string `on` or `off` sent with this topic to implement this action.

Sometimes the payload will be a simple value like a number, often derived from a parameter. In this case, the `parameter: name` spec in the payload definition will fetch the value from the named parameter to use as the payload value. In the following example, the (theoretical) setpoint of a thermostat is being set using the `setpoint` parameter value of the `hvac_heating.set_setpoint` action:

              actions:
                hvac_heating:
                  ...
                  set_setpoint:
                    topic: "thermok/%topic%/set"
                    payload:
                      parameter: setpoint

Some topics have more complex payloads. It's not usual for a topic to have a JSON payload, for example. In this case, the `expr` value may return an object, and if the `type` is set to `json`, MQTTController will JSONify the expression result to a string that is then sent as the payload.

              actions:
                rgb_color:
                  ...
                  set_rgb:
                    topic: "cmnd/%topic%/Color"
                    payload:
                      type: json
                      expr: >
                        { "r": parameters.red, "g": parameters.green, "b": parameters.blue }

In some cases, you may find it useful to have access to attributes on the subject entity. For example, when implementing the `dimming.up` and `dimming.down` actions, if your MQTT-enabled light doesn't have topics to increase and reduce brightness by some amount, you can emulate it by doing the math based on the current brightness, which is stored in the `dimming.level` attribute of your entity. The `entity` object is defined in expression scope for this purpose.

Assuming our example light offers a basic `set` topic that takes a simple number payload in the range 0-255 to set the brightness of a light, we use the current brightness on the entity to compute a new target brightness to set (`dimming.up` increases 10%, `dimming.down` decreases 10%):

              actions:
                dimming:
                  ...
                  up:
                    topic: "%topic%/set"
                    payload:
                      expr: "min(1, entity.attributes.dimming.level + 0.1) * 255"
                  down:
                    topic: "%topic%/set"
                    payload:
                      expr: "max(1, entity.attributes.dimming.level - 0.1) * 255"
                # The use of min()/max() above assured that the values stay in
                # the range 0-255 when published.

If your action needs to set the MQTT `qos` level or `retain` flag to values other than the default (see the QoS and Retain section, below), just provide those using keys of the same name. The value of `qos` must be an integer 0 through 2, and `retain` must be boolean.

Since we've done this in chunks, here's the whole configuration all together:

            "tasmota_FFFFFF_relay":
              name: "Tasmota Relay"
              topic: "tasmota_FFFFFF"
              type: Switch
              capabilities:
                - power_switch
                - toggle
              primary_attribute: power_switch.state
              events:
                "state/%topic%/POWER":
                  "power_switch.state":
                    expr: 'bool(payload)'
                "tele/%topic%/STATE":
                  "power_switch.state":
                    json_payload: true
                    if_expr: '! isnull( payload?.POWER )'
                    expr: 'upper( payload.POWER ) == "ON"'
              actions:
                power_switch:
                  "on":
                    topic: "cmnd/%topic%/Power"
                    payload: "on"
                  "off":
                    topic: "cmnd/%topic%/Power"
                    payload: "off"
                  set:
                    topic: "cmnd/%topic%/Power"
                    payload:
                      expr: "parameters.state ? 'on' : 'off'"
                      type: raw

Notice anything missing?

We haven't defined the `toggle.toggle` action. The `toggle` capability has no attributes and just that one action, but we haven't defined what that action does. As it turns out, MQTTController can supply a default implementation for `toggle.toggle`, so we don't need to define it.

If a standard action of a capability does not apply to the device, you can tell Reactor not to show the action in the UI. Just set the action to `false` in the configuration. Here is what it looks like for the Shelly RGBW2 default template1 for the actions of capability `light_effect`:

          light_effect:
            "start":
              # Use duration argument; speed is not used.
              topic: "shellies/%topic%/color/%channel:0%/set"
              payload:
                # detail redacted for clarity
            "stop":
              topic: "shellies/%topic%/color/%channel:0%/set"
              payload: '{ "effect": 0 }'
            "set_speed": false  # action not implemented     <---- HERE
            "set_duration":
              topic: "shellies/%topic%/color/%channel:0%/set"
              payload:
                type: json
                expr: '{ "transition": int( parameters.duration * 1000 ) }'

Here, the Shelly RGBW2 uses a time period (duration) for the transition; the notion of "speed" doesn't apply. The `set_speed` action is therefore not implemented, and that is indicated by setting its value to `false` as shown. This signals the UI to not show that action in menus for entities that use this template.

If none of the standard actions of a capability can be implemented for a device, the capability value can be set to `false`, and all actions for that capability will be removed from the related entity. Here's an example of what that looks like:

        actions:
          button: false  # no actions of "button" are implemented for this device

It is also possible to add an action to the `x_mqtt_device` capability for an entity. This is used to support actions that are not provided by standard capabilities. Again using the Shelly RGBW2 template1 as an example, the template defines a `set_white` action in the `x_mqtt_device` capability to define an action to set the intensity of the white channel of an RGBW strip (`rgb_color` doesn't support additional channels, and I haven't yet provided a standard system capability that does).

          x_mqtt_device:
            set_white:
              arguments:
                white:
                  type: ui1
                  min: 0
                  max: 255
              topic: "shellies/%topic%/color/%channel:0%/set"
              payload:
                type: json
                expr: "{ 'white': parameters.white }"

The only addition here to other action configurations shown so far is the `arguments` element. It uses the standard form to describe the action's arguments, if it has any. It can be omitted if the action takes no arguments. In this case, we define the argument `white` as an integer in the range of 0 to 255, inclusive (the very specific `ui1` type means a one-byte unsigned integer, but `int` would have worked just as well).

Historical Inconsistency

Notice in the foregoing example that the action's arguments are defined, but when it comes to building the payload, the expression refers to them as parameters. This is an old inconsistency in the implementation of Reactor. Most programmers use these terms interchangeably, so things like this happen. At this point, it would be too disruptive to choose one or the other and change it everywhere, so we're stuck with it. Moving on...

### Additional Entity Configuration

The `lwt` key may be specified to identify the topic sent by your device as a Last Will and Testament (e.g. `lwt: tele/%topic%/LWT` is common for Tasmota). When this topic is received, MQTTController will mark the entity down by setting `x_mqtt_device.online` to false. The specific conditions under which the LWT is sent are device-dependent; consult your device and broker documentation for more details. Also, note that this default handling does not examine the payload; it assumes that receiving the LWT with any payload is an indication that the device is down (or going down). If this implementation isn't appropriate for your device, don't use the `lwt` key; instead, create your own event handler for the LWT topic in which the `x_mqtt_device.online` attribute is set based on the payload content.

The `init` key may be used to send initialization strings to a device. These strings will be sent every time Reactor connects to the MQTT broker. This is useful for ensuring that the remote device/client is in a known state. It can also be used to query the current state of the device/client to ensure the Reactor entity is in sync with it. The `init` topics are always sent with the default QoS and retain false (see QoS and Retain below).

The `init` key's value may be:

- A string, in which case the topic (with no payload) is the only topic sent at initialization;
- An array, containing any number and mix of:
  - a bare string, a topic to be sent (with no payload);
  - an object containing the required key `topic` (string) and the optional keys `payload`, `type` (string, `raw` or `json`), `qos` (integer 0-2) and `retain` (boolean).

The optional `payload` value can be any type. It will be converted to a string (in a straightforward way) before being sent, unless `type: json` is given, in which case it will be formatted as a JSON string before being published.

The `query` key can be used to send a topic that causes the device to report its current state and update the related entity. The query topic will be sent when an `x_mqtt_device.poll` action is performed on an entity. The form of the `query` value is the same as the entries of the `init` array: a plain string (the topic), or if a payload is needed, a required `topic` string and optional `payload`, `type`, `qos` and `retain` values. Query topics are always sent with the default QoS and retain false (see QoS and Retain below).

Here's an example of how these are formatted in the configuration (when needed):

            "tasmota_FFFFFF_relay":
              name: "Tasmota Relay"
              topic: "tasmota_FFFFFF"
              type: Switch
              capabilities:
                - power_switch
                - toggle
              primary_attribute: power_switch.state
              lwt: "tele/%topic%/LWT"
              query: "cmnd/%topic%/Power"
              init:
                - "cmnd/%topic%/someAction"
                - topic: "cmnd/%topic%/anotherAction"
                  payload:
                    level: 50
                    rate: 3
                  type: json
                  qos: 1

Because the second `init` topic has `type: json` with its payload, the actual payload transmitted by this example would be `{"level":50,"rate":3}`.

The default QoS for `init` and `query` topics is the controller's default QoS, and the default retain flag is false. The example above shows `qos` being set explicitly to 1 for one of the `init` topics, which overrides the default (see QoS and Retain below for more information).

## Controlling Attribute Evaluation Order

With the structure of event configuration discussed so far, the order in which attributes are evaluated and updated is not deterministic. That is, if an event is configured to update two or more attributes, there is no defined order by which the attributes are processed, and their order in your configuration is of no consequence.

If the order of processing is important, you can use the array form of event configuration. In this form, the data under the event's topic is an array of objects, each containing the `attribute` key to specify the attribute name. The remainder of the object is the same.

Let's look at a specific example, highlighting the differences in the format and why we might care:

        events:
          "%topic%/power:
            "voltage_sensor.value":
              expr: "float( payload.load.voltage )"
            "power_sensor.value":
              expr: "float( payload.load.power )"
            "current_sensor.value":
              expr: "entity.attributes.power_sensor.value / entity.attributes.voltage_sensor.value"

In this case, we have a hypothetical solar inverter/controller that is sending topics to give us instantaneous power output and line voltage, but it doesn't report amperes, so we are computing the amperes (ignoring power factor, etc.) by simply dividing power (in watts) by voltage.

The problem here is that there is no guarantee, in this typical configuration form, that when the topic is received, the three attributes are evaluated and updated in the order shown. The `current_sensor.value` attribute is relying on the entity's newly-updated voltage and power values, but if this attribute happens to be evaluated first or second, those entity attributes may not yet have been updated, and the computed current will be incorrect.

To fix this, we can reformat the configuration like this:

        events:
          "%topic%/power:
            - attribute: "voltage_sensor.value"
              expr: "float( payload.load.voltage )"
            - attribute: "power_sensor.value"
              expr: "float( payload.load.power )"
            - attribute: "current_sensor.value"
              expr: "entity.attributes.power_sensor.value / entity.attributes.voltage_sensor.value"

The key change made for each entry is the addition of `- attribute:` before the attribute name. This does two things: it refines the entry as an array element, and it makes the attribute name a key/value pair in the object that is the array element (`expr` is another key in each respective object).

In this array-based structure, the order in which the attributes are processed is guaranteed to be the order in which they appear in the configuration: voltage will be updated first, followed by power/watts, and finally current/amps.

## Converting Your Configuration into a Template

In the previous section, we built the device configuration directly into our per-device configuration. If we had several such devices, though, it would be verbose and more difficult to maintain all of them with each device having a complete copy of all those lines. We can simplify our configuration by converting our device configuration into a template.

To begin, create a directory in your `config` subdirectory called `mqtt_templates`. Next, we'll assign an identifier (name) to our template. We'll call it `my_tasmota_relay`, and create a YAML file with that basename (e.g. `my_tasmota_relay.yaml`) in the `mqtt_templates` subdirectory.

    # Optional description of what your template does (this is a comment)
    my_tasmota_relay:

Now, we can basically copy the device configuration we previously created, leaving behind the ID, `name` and `topic`, since these are values that will later be passed to the template from entity configuration (that is, we don't want the template to have fixed/overriding values for these, so that the template can be reused to make multiple entities).

Passing data from an entity configuration to the template is straightforward. In any `%keyword%`-form substitution, the replacement string will be the value from the key of the same name in the entity's configuration. As a trivial example, if you add `flavor: raspberry` to your entity's configuration, then in any topic strings in your template you could include `%flavor%` and that would be replaced in runtime with the word raspberry (e.g. topic `/icecream/%flavor%/rating` becomes `/icecream/raspberry/rating` in use). This allows you to fully parameterize your templates. In addition, as of version 22282, if your substitution is of the form `%flavor:chocolate%`, then if flavor is not defined in the entity's configuration, the substitution will use the word chocolate, so you can establish defaults. For an example of this kind of parameterization in operation, look at the implementation of the "real" `shelly_relay` template in MQTTController's `mqtt_devices.yaml` file, which uses `channel` and `unit` to handle multi-relay devices.

You can also access configuration data in your template's expressions. The `config` object is defined in expression context, and allows you to access the elements of the entity's configuration. Building on the example in the previous paragraph, your expression could refer to `config.flavor` to access the configured value of the flavor configuration value.

If your template uses required substitutions or expression-context values, it is recommended that you add a `requires: []` entry to the template configuration (before `events` and at the same indent level), specifying as the array contents the names of all values required by the template (e.g. `required: [topic, unit]`). If a user of your template omits one of these required values from their configuration, a detailed, helpful alert will be displayed and logged. The array form must be used even if there is only one required value.

Your completed template file will look something like this:

    # Optional description of what your template does (this is a comment)
    my_tasmota_relay:
      type: Switch
      capabilities:
        - power_switch
        - toggle
      primary_attribute: power_switch.state
      requires: [topic]
      lwt: "tele/%topic%/LWT"
      events:
        "state/%topic%/POWER":
          "power_switch.state":
            expr: "bool(payload)"
        "tele/%topic%/STATE":
          "power_switch.state":
            json_payload: true
            if_expr: '! isnull( payload?.POWER )'
            expr: 'upper( payload.POWER ) == "ON"'
      actions:
        power_switch:
          "on":
            topic: "cmnd/%topic%/Power"
            payload: "on"
          "off":
            topic: "cmnd/%topic%/Power"
            payload: "off"
          set:
            topic: "cmnd/%topic%/Power"
            payload:
              expr: "parameters.state ? 'on' : 'off'"
              type: raw

In your device configuration, you can refer to your custom template with the `include` key. Here's our revised device configuration, with an additional entity of the same type added for another Tasmota relay node, using the template for both:

      - id: mqtt
        ...
        config:
          source: "mqtt://127.0.0.1:1883/"
          entities:
            "tasmota_FFFFFFF_relay":
              name: "Tasmota Relay 1"
              topic: "tasmota_FFFFFF"
              include: "my_tasmota_relay"
            "tasmota_BEEFEE_relay":
              name: "Tasmota Relay 2"
              topic: "tasmota_BEEFEE"
              include: "my_tasmota_relay"

### Modular Elements

When an event and its response implementation are common to a number of devices or templates you are creating, it may be desirable to push that configuration into a separate "sub-template" and use it as a modular building block for those templates. For example, many Shelly devices emit WiFi status events that provide the current RSSI (received signal strength) and IP address of the device. Since many devices emit the same message, rather than copying the event configuration into the template for each, you can create a sub-template just for the Wifi event and its handling, and include that template from the device templates.

The `include` key is used to include one or more other templates into the current template. It takes either a single string value (template name), or an array of strings (template names). As an example, there are several Shelly devices that provide a Wifi status event and a battery status event. We define these events and their handling in two templates like this:

      "shelly_battery_gen1":
        requires: [topic]
        capabilities: ['battery_power']
        events:
          "shellies/%topic%/sensor/battery":
            "battery_power.level":
              expr: 'float(payload) / 100'
            "battery_power.since":
              expr: 'time()'

      "shelly_wifi_gen1":
        requires: [topic]
        capabilities: ['wifi_status']
        events:
          "shellies/%topic%/info":
            "wifi_status.rssi":
              json_payload: true
              expr: "payload.wifi_sta.rssi"
            "wifi_status.ssid":
              json_payload: true
              expr: "payload.wifi_sta.ssid"
            "wifi_status.station_ip":
              json_payload: true
              expr: "payload.wifi_sta.ip"

We can then reference those sub-templates in the templates for those devices that support these events:

      # shelly H&T (Gen1)
      shelly_handt:
        type: ValueSensor
        capabilities: ['temperature_sensor']  # others added by include below
        primary_attribute: temperature_sensor.value
        requires: [topic]
        lwt: "shellies/%topic%/online"
        include:
          - shelly_temperature_gen1  # this sub-template was not shown above, but works the same way
          - shelly_humidity_gen1     # this sub-template was not shown above, but works the same way
          - shelly_wifi_gen1
          - shelly_battery_gen1

`include`s can be nested — an included template may include other templates. A template may only be included once, but it is not an error to attempt a duplicate inclusion — Reactor will know that the template has already been included and not repeat it.

### Template-defined Capabilities

Available as of Build 24144

This feature is available in build 24144 and beyond.

Templates may define custom capabilities if needed. Capabilities defined in templates are, by definition, extension capabilities, so their names must be prefixed with `x_`. If you are publishing templates for use by others, it is recommended that you establish a naming convention for your extension capabilities to avoid naming collisions with other developers. You could, for example, name the capability tightly to the brand of the device, or more tightly, brand and model (e.g. `x_shelly_handt3_somecapability`); or you might include your Github account name (e.g. `x_toggledbits_somecapability` using my name as an example only).

A template file may have a top-level `capabilities` section that defines an object, the keys of which are the capabilities to be defined. The object should contain an `attributes` and an `actions` object to declare the attributes and actions the capability will provide. The format definition is as follows:

    capabilities:
      x_some_capability_name:
        attributes:
          attributeName1:
            type: string|int|real
            min: minimum_value  # optional
            max: maximum_value  # optional
            default: default_value  # optional
            values:  # for string type, optional list (array) of possible values
          attributeName2:
            # ...etc...
        actions:
          actionName1:
            arguments:
              argName1:
                type: string|int|real
                min: minimum_value  # optional
                max: maximum_value  # optional
                default: default_value  # optional
                values:  # for string type, optional list (array) of possible values
              argName2:
                # ...etc..
          actionName2:
            arguments:
              # ...etc..
            actions:
              # ...etc..

    templates:  # ...etc...

`type` is the only required detail, and even that isn't fully required, because `string` is the default type. An attribute defined as `attributeName: {}` will be assumed to have a string value. All of the other details are optional. Mostly, they are used as hints for the UI to guide the user. The UI does not strictly enforce min/max or values, it will simply warn the user (if only by color highlighting) that a value is not within expectations.

MQTTController will complain if a template file attempts to redefine a capability that was previously defined in another template. That is, it will complain if two templates try to define the same capability, and the definitions are different; if the definitions are the same, it will simply ignore the duplicate definition.

## Echo Capability - Publishing Reactor Entities

MQTTController can, if configured, publish messages for every state change of a set of entities you choose (or all entities), rules, and global variable changes. In this way, all entities known to Reactor may become MQTT-enabled, regardless of their source. MQTTController also knows how to receive topics to perform actions on these entities.

Info

I'm not sure what to call this function, so for now I'm just calling it "echo" capability. Gateway...? Relay...? Great and Powerful Oz? Any ideas?

Enable echoing by defining an `echo` section in your MQTTController configuration:

      - id: mqtt
        ...
        config:
          source: "mqtt://..."
          entities:
            ...
          echo:
            #include_entities: []
            #exclude_entities: []
            #include_groups: []
            #exclude_groups: []
            #include_capabilities: []
            #exclude_capabilities: []
            primary_attributes: true
            qos: 0
            retain: false

Note

Some configuration keys have been commented out (`#`). As shown above, the defaults will be used for all of these keys. Uncommenting some of these keys immediately changes behavior, so be sure you read the descriptions of each below before doing so.

The `include_` and `exclude_` filters determine what data is echoed. They are arrays, and you can either use the compact array notation (`['string1','string2']`) or the multi-line list form (preferred for lists of more than 2 items).

        list_name:
          - "list element 1"
          - "list element 2"
          - "list element 3"

In MQTTController, the rules for include/exclude filter lists are as follows:

1. If an `include` list is not defined (i.e. commented out or does not exist at all in the config), everything passes (jump to step 3).
2. If the `include` list is defined, then only those items matching any element pass.
3. If an `exclude` list is not defined, nothing is excluded (except in capabilities, see below).
4. If the `exclude` list is defined, then those items matching any element are excluded.

Beware Empty Arrays

The meaning of an empty array (`[]`) is different from that of being commented out or undefined. It is interpreted to mean nothing; therefore, in `include` lists, an empty array would mean include nothing, so be particularly careful there.

Filtering is done in the order: entities, groups, capabilities. Within each type, the `include` list is processed before the `exclude` list. So the specific order of filtering is: `include_entities`, `exclude_entities`, `include_groups`, `exclude_groups`, `include_capabilities`, `exclude_capabilities`.

The `include_entities` key is an array containing canonical ID patterns to match. The patterns are defined as follows:

- Literal canonical IDs, like `hubitat>100`, to refer to a single entity;
- Wildcard to match all entities on a particular controller: `hubitat>*` matches all entities from the controller with ID `hubitat`;
- Regular expressions: `/pattern-regexp/optional-flags`

For example (using alternate array syntax for improved readability):

        echo:
          include_entities:
            - "vera>*"
            - "hass>*"

This would match all entities from the controllers with IDs `vera` and `hass`. If `included_entities` is not specified (i.e. commented out or not present in the config), then all entities from all controllers are included.

After inclusion, `exclude_entities` is processed in the same manner as `include_entities`, except that any entity that matches will be discarded.

The optional `include_groups` and `exclude_groups` arrays give further control over the entities echoed. The elements of these arrays are canonical IDs of groups. If an entity is a member of any of the listed `include_groups`, it will be allowed to echo. If it is a member of any of the listed `exclude_groups`, it will not echo. Dynamic changes in group membership will affect these filters dynamically. Group filters require MQTTController version 21343 or higher.

From the final set of entities that have passed filtering so far, the `include_capabilities` and `exclude_capabilies` lists will determine what capabilities are published as topics. If `include_capabilites` is not defined, all capabilities will be published (except those excluded). If `exclude_capabilities` is not defined, by default the extended (`x_`) capabilities will be excluded. If you wish to prevent this default exclusion and include all capabilities including the extended, simply set `exclude_capabilities` to an empty array (i.e. `exclude_capabilities: []`, meaning nothing is excluded).

Regular expression patterns are supported in `include_capabilities` and `exclude_capabilities` values, using the form `/regexp/optional-flags` (e.g. `/^x_hass\b/i` would be a case-insensitive match for any capability starting with `x_hass`).

The keys `qos` and `retain` determine the QoS and retentation of the state messages published. By default, these messages are sent at QoS 0 and no retention, but you may change these behaviors by providing different values as defined by MQTT.

If you do not needs rules or global variables published in your configuration, you can set `publish_rule_states` and/or `publish_global_variables`, respectively, to `false` to disable that function.

### Working with Entity Topics

When a capability for an entity is published, the topic will be `reactor/<mqtt-id>/<ctrl-id>/<entity-id>/state/<capability-name>`, and the payload is a JSON object containing the attribute key/value pairs of that capability for that entity. The `mqtt-id` is the ID of the MQTTController, the `entity-id` is the ID of the entity that is the subject of the message, and the `ctrl-id` is the controller ID to which that entity belongs.

    reactor/mqtt/vera/device_10/state/power_switch {"state":true}

The setting of `primary_attributes` in the `echo` section determines whether a generic `value` topic is sent for the primary attribute of the entity. If this is true, you will see topics of the form `reactor/<mqtt-id>/<ctrl-id>/<entity-id>/value` with a payload string containing the primary attribute value. For a switch, the primary attribute of which is typically `power_switch.state`, one might expect:

    reactor/mqtt/hass/switch_living_room/value true

For some users, it may be convenient for the entity name to also be part of the topic on announced attribute changes. MQTTController allows you to add the `entity_identifier` configuration key to the `echo` section with a value of `id` (the default) or `combined`. If `combined` or `id/name` is specified, the entity name will be added as an additional level of the published topic, such as `reactor/mqtt/vera/device_10/Living Room Lights/state/power_switch`. If `name/id` is specified, the name will precede the ID. Note that while these options add a level to the published topics for attribute changes, they do not change any other topic structures, such as those for performing actions (see below).

MQTTController can handle specific received topics and turn them into actions on Reactor entities. To perform an action on an entity, publish a topic in this form:

    reactor/<mqtt-id>/<ctrl-id>/<entity-id>/perform/<action-capability>/<action-name>

If the action takes parameters, encode the parameter key/value pairs as a JSON payload. Here's an action to turn on our fictional living room light:

    reactor/mqtt/hass/switch_living_room/perform/power_switch/on    (no payload)
    reactor/mqtt/hass/switch_living_room/perform/power_switch/set {"state":true}

Both of the above topics accomplish the same task; the first requires no parameters/payload, while the second requires a "state" parameter. Here's a more complex payload example, setting color on an RGB bulb:

    reactor/mqtt/vera/device_188/perform/rgb_color/set_rgb {"red":255,"green":128,blue:16}

MQTT Actions Always Available

Reactor will receive entity action messages and process them whether the echo function is enabled or not, and will process actions for any entity/capability, whether it matches echo filtering or not.

MQTTController will send a full update for an entity if it receives the following message:

    reactor/<mqtt-id>/<ctrl-id>/<entity-id>/query

### Working with Rule Topics

If rule publishing is enabled (which it is by default when echo is enabled), changes to the set/reset state of all rules will be published in topics of the following form:

    reactor/mqtt/Rules/rule-id/state

The payload for this topic will be boolean true if the rule has set, and false if it has reset.

To force the publication of a rule state message at any time, you can send:

    reactor/mqtt/rules/rule-id/query

You cannot control the state of rules via MQTT. Rule state is driven exclusively by the result of its conditions.

To help reduce traffic, you can disable publishing of rule state topics when echo is enabled by adding `publish_rule_states: false` to your MQTTController configuration.

### Working with Global Variables

If global variable publishing is enabled (the default when echo is enabled), a value change to any global variable will cause a topic of the following form to be published:

    reactor/mqtt/Expr/expression-name/value

The payload for this message is the (JSON) expression value. Be aware that primitive numbers and strings, when converted to JSON, are not surrounded by `{}` as many seem to expect for JSON. A number converted to JSON is simply a string containing the number (e.g. `123`), and a boolean will be the string `true` or `false`. A string value will be quoted (e.g. `"I am a valid JSON result"`). Arrays and objects will, of course, have the familar `{}` envelope.

You can set the value of a global variable, if it is expression-less, by sending the value as JSON using same semantics described in the preceding paragraph on a topic of the form:

    reactor/mqtt/Expr/expression-name/set

To query the value of a global variable any time, send:

    reactor/mqtt/Expr/expression-name/query

The echo feature does not need to be enabled to set or query a global variable as shown above.

To help reduce traffic, you can disable publishing of global variable topics when echo is enabled by specifying `publish_global_variables: false` to MQTTController your configuration.

## QoS and Retain

Configuration of QoS and the retain flag for topics published by MQTTController is a hierarchy, where settings at lower (more detailed/specific) levels overrides that of higher levels. The default QoS is 0, and the default for retain is false. The optional `default_qos` (integer 0-2) and `default_retain` (boolean) configuration values (in controller configuration, i.e. same level as `source`) can be used to modify these defaults.

For `echo` outbound entity attribute topics, the default QoS and retain flag are used unless the `qos` and `retain` flags are set under `echo` configuration, in which case, those more-specific values override the defaults. These overrides will also apply to outbound topics for alerts, global variables, and rule states, if enabled.

The following topics are always sent using the controller's default QoS, but the retain flag false (i.e. not affected by `default_retain`):

1. Topics in per-entity `query` configuration;
2. Topics in per-entity `init` configuration (but both can be overridden by specific configuration in the topic entry);
3. Topics in per-entity action implementations (but both can be overridden by specific configuration in the action definition).

The Last Will and Testament (LWT) is a vital health message and therefore normally has default QoS 1 and retain flag true, unless overridden by setting `lwt_qos` (integer 0-2) and `lwt_retain` (boolean) in controller configuration (at the same level as `source`).

## Polling

In the event that a device cannot reliably publish status messages, MQTTController provides for periodic polling of devices. Polled devices must implement the `query` topic (Additional Entity Configuration above) to be eligible.

Polling of a device is enable and the polling interval set by adding the `poll_interval` configuration value to the entity's configuration (same level as `name`, for example). The value is an integer interval in seconds (must be >= 1). MQTTController will send the configured `query` topic on this interval, or as close to it as practicable (keep reading...).

To prevent polling of multiple devices from causing waves of high traffic on the network, MQTTController will limit the frequency of polling to, by default, once every 5000 milliseconds (five seconds). That is, if a number of devices all become due for polling at the same instant, each will be polled in turn, one every five seconds, until all queries have been sent. This pacing can be modified by setting the `poll_frequency` configuration value in MQTTController's instance configuration (that is, at the same level as `source`); the value is in milliseconds and must be >= 100.

The `poll_interval` cannot be set in a template; it is permitted in entity configuration only. If it appears in a template, it will be ignored.

## Additional MQTTController Behaviors

MQTTController will respond with a `reactor/<mqtt-id>/pong` message when it receives the topic `reactor/<mqtt-id>/ping`. If a payload is received, it is echoed back; otherwise, the current Reactor host date/time is returned as an Epoch time in milliseconds.

## Troubleshooting and Debugging

To see what messages MQTTController is receiving, you can enable topic logging in the `config` section of its controller configuration. A list of topics or partial topics to match can be provided, and the matching topics will be logged to `<id>-topics.log`, where `<id>` is the ID of your MQTTController.

The following will log all topics received from Owntracks and BlueIris, for example (lines before `log_topics` are shown for context and indenting reference and should not be copied:

        implementation: MQTTController
        enabled: true
        config:
          source: "mqtt://127.0.0.1:1883/"
          log_topics:
            - "owntracks/"
            - "blueiris/"

If you're not sure what topic you're looking for and you want to log all topics, the following will match and log all topics received by MQTTController:

          log_topics:
            - ".*"

Fast- and Ever-Growing Log File Danger

Topic logging is meant to be used briefly, not to be left on. The topic log file will grow forever and eventually fill up the disk. It is not managed, pruned, or truncated on restarts. For this reason, do not leave any topic logging on any longer than you need it. Turn it off and remove the log file from your `logs` directory to recover and preserve your disk space, and prevent the system instability and errors that an "out of space" condition can cause.

You can also increase the logging level of MQTTController for more detail, and route its log messages to a separate file to separate them from other messages and make them easier to follow. This increased detail and focus should allow you to see if/when a topic is being received, what its payload is, and how the topic and payload are handled. Here is an example addition to your `logging.yaml` file to increase the log level to 5 (which is probably sufficient in most cases), and direct the output to a separate `mqtt.log` file. Note that the `MQTTController:` line is indented two spaces (no tabs).

config/logging.yaml

    logging:  # For indent reference only - DO NOT copy/paste this line

      ...etc...

      MQTTController:
        level: 5
        streams:
          - type: file
            name: mqtt.log
            keep: 2
            recycle: true

Remember to remove this section (or keep it and just set the level to 4 or lower) after troubleshooting to avoid filling your storage with excessive logging that you no longer need.

## List of Pre-defined Templates

The following templates are currently defined. If you work out a custom configuration for a commonly-used device, please consider contributing your template!

Template Name | Use/Description | Parameters
--- | --- | ---
`shelly_relay` | Creates an entity that maps one relay channel of a Shelly node to an entity. | topic, channel
`shelly_input` | Creates an entity that that maps one input channel of a Shelly node to an entity. | topic, channel
`shelly_temperature` | Creates an entity to contain the temperature sensor data from a Shelly device. | topic
`shelly_humidity` | Creates an entity to contain the humidity sensor data from a Shelly device. | topic
`shelly_motion` | Creates an entity to reflect the motion/no motion state of a Shelly motion sensor. | topic
`shelly_smoke` | Creates an entity to reflect the state of a Shelly smoke detector. | topic
`shelly_flood` | Creates an entity to reflect the dry/wet state of a Shelly flood sensor. | topic
`shelly_door_window` | Creates an entity to reflect the opened/closed state of a Shelly flood sensor. |
`shelly_battery` | Creates an entity to contain the battery level of a battery-operated Shelly device (e.g. a Shelly HT). | topic
`shelly_power` | Creates an entity with a Shelly device's power (watts) measurement. | topic
`shelly_tilt` | Creates an entity to contain the measurement of a Shelly device's tilt sensor. | topic
`shelly_vibration` | Creates an entity to contain the measurement of a Shelly device's tilt sensor. | topic
`shelly_handt3` | Creates an entity for a Shelly H&T Gen3 device. Because the device is battery-powered, attributes will not populate until the first wake-up. | topic
`tasmota_generic_relay` | Maps payload data and actions to the entity (capabilities `switch_power` and `toggle`); use optional `unit` to specify relay number if the device supports/reports more than one (default: blank/no unit). | topic, unit
`tasmota_detached_button` | Maps payload data for a detached switch input to the entity (capability `button`); use optional `unit` to specify switch number if the device supports/reports more than one (default: blank/no unit). | topic, unit
`tasmota_detached_switch_mode0` | Maps payload data for a detached switch in mode 0 (toggle/momentary; capability `button`); use optional `unit` to specify switch number if the device supports/reports more than one (default: blank/no unit). | topic, unit
`tasmota_detached_switch_mode1` | Maps payload data for a detached switch in mode 0 (on/off; capability `binary_sensor`); use optional `unit` to specify switch number if the device supports/reports more than one (default: blank/no unit). | topic, unit
`tasmota_sensor_temperature_humidity` | Maps temperature and humidity values from payload into a single entity (capabilities `temperature_sensor` and `humidity_sensor`); use `source` to identify sensor subunit (default: `Global`). | topic, source
`tasmota_sensor_temperature` | Maps temperature (only) from payload into the entity (capability `temperature_sensor`); use `source` to identify sensor subunit (default: `Global`). | topic, source
`tasmota_sensor_humidity` | Maps humidity level (only) from payload into the entity (capability `humidity_sensor`); use `source` to identify sensor subunit (default: `Global` ). | topic, source
`tasmota_sensor_battery` | Maps payload battery data to the entity; requires `source` to identify the sensor subunit (default: `Global`). | topic, source
`tasmota_sensor_co2` | Maps payload carbon dioxide level data to the entity; requires `source` to identify the sensor subunit (default: `Global`). | topic, source
`tasmota_sensor_dewpoint` | Maps payload dewpoint to the entity; requires `source` to identify the sensor subunit (default: `Global`). | topic, source
`tasmota_sensor_rssi` | Maps payload RSSI to the entity; requires `source` to identify the sensor subunit (default: `Global`). | topic, source
`tasmota_sensor_flora`2 | Mi Flora plant sensor for Temperature, Illuminance, Moisture and Fertility, use `source` to identify subunit (default: `Global`) | topic, source
`owntracks_in_region`3 | Creates a binary sensor the state of which is driven by an iPhone or Android device running OwnTracks. The sensor is true when the device is in the given region. The `topic` is the Device ID assigned in the OwnTracks application's configuration. The `regionName` is the name of a region defined in OwnTracks. All parameter values are case-sensitive (must match the case used in the OwnTracks app). | topic, regionName, user

Community Contributions

Community users are encouraged to contribute templates for new devices. These may either be included in releases, or we'll link to them here if their work is large and growing. To install and use a community template (as individual file(s) or a ZIP archive), create a `mqtt_templates` subdirectory in your `config` directory, and place the template file or ZIP archive in that subdirectory. You can then configure entities using the community template(s).

1. More Shelly and Tasmota templates from dbochicchio/@therealdb: Github repository.

## Special Expression Functions for MQTTController

The following functions are defined for expressions evaluated in MQTTController entity event handlers and action implementations:

- `hsltorgb( hue, sat, lev )` — converts hue/saturation/level color to RGB color. The `hue` is given in degrees; `sat` and `lev` are given as percentage values in the range 0 to 1. The function returns the equivalent (approximate) RGB color as a three-element array. Example: `hsltorgb(60,1.0,0.5)` returns `[255,255,0]` (yellow).
- `rgbtohsl( red, green, blue )` — converts RGB color to HSL; this is the inverse of `hsltorgb()`, above.

## Using MQTT from Other Controllers

MQTTController has a mechanism that allows other controllers to subscribe to MQTT and send topics. This saves the other controller from having to create and manage a separate connection to the MQTT broker. While it creates a dependency on MQTTController, it is probably the case that a user using MQTT for your controller's device(s) is probably already using MQTT for other devices in their infrastructure, and it's already configured and running.

MQTTController does not require any special configuration for these tools to work. It doesn't even need to be configured beyond being enabled and having a correct `source` to find the broker. No events, entities, or echo need to be configured; MQTTController just needs to be up and running.

To subscribe to a topic, your Controller instance will need to do the following:

1. Find the running instance of MQTTController. This is done by calling the `getControllerByID(id)` method of `Structure`, the instance of which you can get from the Controller base class by simply referring to `this.getStructure()`. The `id` will typically be `mqtt` for users that just follow the recommended installation instructions for MQTTController. You may want to make this a configurable string in your controller in case the user deviates from this recommendation, or has multiple MQTTController instances and wants to specify which to use. The following code snippet demonstrates all of this working together in a Controller subclass method:

       const mqttcontroller = this.getStructure().getControllerByID( this.config.mqtt_controller || 'mqtt' );

2. Write a handler in your Controller subclass to receive your subscribed MQTT topics. You can have as many handlers as you wish: handle different topics with separate handlers, or handle all of your subscriptions in one handler; it's up to you. The handlers are methods of your Controller subclass that will receive arguments `topic` and `payload` when a topic matching your subscription arrives.

       handleMyTopic( topic, payload ) {
           // your implementation here
       }

3. Request a subscription from the MQTTController instance by calling its `extSubscribeTopic( this, topic, callback )` method. The first argument `this` is a reference to your controller instance (and typically the keyword `this` is exactly what is used in your code). The `topic` can include the typical `+` and `#` wildcards for subscribing to MQTT topics. The `callback` is a function reference or closure. When a matching topic is received by MQTTController, it will call the function with `topic` and `payload` arguments. To use a method of your subclass as the callback, use the JavaScript function `bind()` as shown below.

       // Using bind() to use a method of your controller as handler:
       mqttcontroller.extSubscribeTopic( this, "opensprinkler/#", this.handleMyTopic.bind( this ) );
       // Using a closure as handler:
       mqttcontroller.extSubscribeTopic( this, "tele/+/status", ( topic, payload ) => {
           /* Handling code here */
       });

   Note that it is possible to omit the `callback` argument. In this case, your controller's `notify( event )` method will be called with a `ext-mqtt-controller` event. The topic and payload will be in the `data` of the event object.

To unsubscribe from a topic, call MQTTController's `extUnsubscribeTopic( this, topic )` method. The `topic` must exactly match a topic string previously used in `extTopicSubscribe()`. If `topic` is omitted or null, all topics to which your controller subscribed are unsubscribed.

To publish topics, call MQTTController's `extPublishTopic( topic, payload, opts )` method. The `topic` must be a valid topic string. The `payload` can be any type; if it is an object, array, or null, it will be sent as JSON4. Otherwise, it is simply converted to a string and sent. To send no payload at all, specify `undefined` as the argument (or omit it). The optional argument `opts` is an object that may specify the key `qos` with an integer value 0 through 2 (inclusive, default 0) and `retain` as a boolean (default: false).

        mqttcontroller.extPublishTopic( "mycontroller/status/OK" );
        mqttcontroller.extPublishTopic( "mycontroller/dimmer/level", 50 );
        mqttcontroller.extPublishTopic( "mycontroller/weather/current", { temp: 21, hum: 44 },
            { qos: 1, retain: true } );

    This method returns a *Promise* that resolves when the topic has been sent, or rejects if an error occurs.

## Publishing Templates for the Community

Best Practices:

- Each template file must contain a `templates:` key that is the container for all templates being defined. A template file can define any number of templates.
- Each template file should only define templates for a single device/endpoint or type (e.g. it would not be recommended to include Tasmota and Shelly templates in one file).
- Your templates should be published on Github (see the `repository` field, above). Please do not distribute your templates through posts in community forums or other similar venues where version control is more difficult for you and your users.
- Your template(s) should be distributed as a single YAML file, or if it's necessary/convenient to have several files, as a ZIP archive.
- Please use the `author`, `version`, `description`, `license` and `repository` top-level keys (same level as, and preceding, `templates:`), which should be, respectively, your name and optional email address, a version number for the template, a description of your templates' purpose/supported devices, the type of license applicable (GNU, MIT, etc., or a URL/link to the license file in the repository), and the URL to the Github repository from which the file came and updates can be found.
  - If you are distributing templates in a single YAML file, these keys should appear at the beginning of the file (example below).
  - If you are distributing multiple files as a ZIP archive, these keys can be included in any one of the files; it may be practical to use a manifest file (e.g. `manifest.yaml`) to contain only these metadata keys and an empty `templates: {}` line (so the file defines no templates, just metadata).
- Your templates should make use of keyword substitution (in fields like `topic`), and where necessary, expression-context values, to make them as re-usable as possible.
- Your template names should be unique and descriptive to avoid collisions between templates. For example, calling your template sensor is a poor choice; better would be to use a name like blueiris_camera_motion_sensor. Even though your templates may come from files with more descriptive names, the source filenames will not normally be visible to the user after installation, so the template names themselves must be sufficiently descriptive.
- Your Github repository should contain release notes (in a README or separate CHANGELOG, for example), so your users can understand what has changed between versions.

Example template header (single file):

    # Templates for HaMagicWerks Devices (this is a comment)
    ---
    description: "HaMagicWerks Device Templates"
    author: "Clyde Terwilligerock <clyde.terw@example.com>"
    license: MIT
    repository: "https://github.com/clyde_terw/Reactor-MQTT-HaMagicWerks"
    version: 22341

    templates:
      hamw_motion_sensor:
        # ...etc...

A manifest file in a ZIP archive would look almost identical to the above, just defining no templates (i.e. it would have `templates: {}` at the end).

Updated: 2025-Sep-30

1. You can find this template in your MQTTController distribution file `mqtt_devices.yaml`. ↩↩

2. Thank you to SmartHome Community user @Crille for contributing this template. ↩

3. Thank you to SmartHome Community user @therealdb for the inspiration and basis of this template. ↩

4. This implies, of course, that the object can be converted to JSON by `JSON.stringify()`, which means that it can contain only JavaScript primitive types (string, number, boolean, null), `Date` instances, and arrays and simple, prototype-less objects (i.e. not instances of defined `class`es, be they defined by you or JavaScript types like `Map`, `Set`, etc.) containing only these permitted types as elements/values. ↩