A .things and .items generator using templates

I got sick of writing my .things and .items entries manually each time I have to add a new device, or want to modify existing ones, so I wrote a template-based .things and .items file generator

For example, whenever I added a Tasmota smart light bulb, I would add these lines into my .things file:

Thing mqtt:topic:mosquitto:bedroom3-light "Bed Room3 Light" (mqtt:broker:mosquitto) {
        Type switch: power	    [ stateTopic="stat/bedroom3-light/RESULT", transformationPattern="REGEX:(.*POWER.*)∩JSONPATH:$.POWER", commandTopic="cmnd/bedroom3-light/POWER" ]
        Type dimmer: dimmer     [ stateTopic="stat/bedroom3-light/RESULT", transformationPattern="REGEX:(.*Dimmer.*)∩JSONPATH:$.Dimmer", commandTopic="cmnd/bedroom3-light/Dimmer" ]
        Type dimmer: ct         [ stateTopic="stat/bedroom3-light/RESULT", transformationPattern="REGEX:(.*CT.*)∩JSONPATH:$.CT", commandTopic="cmnd/bedroom3-light/CT", min=153, max=500, step=1 ]
        Type switch: reachable  [ stateTopic="tele/bedroom3-light/LWT", on="Online", off="Offline" ]
        Type number: rssi	    [ stateTopic="tele/bedroom3-light/STATE", transformationPattern="JSONPATH:$.Wifi.RSSI" ]
        Type string: state      [ stateTopic="tele/bedroom3-light/dummy", commandTopic="cmnd/bedroom3-light/STATE" ]

And I would add these lines into my .items file:

Switch BedRoom3_Light_Power  "Bed Room3 Light Power" <light> (gInsideLights, gPresenceSimulators) { channel="mqtt:topic:mosquitto:bedroom3-light:power", autoupdate="false" }
Dimmer BedRoom3_Light_Dimmer "Bed Room3 Light" { channel="mqtt:topic:mosquitto:bedroom3-light:dimmer", ga="Light" }
Dimmer BedRoom3_Light_CT     "Bed Room3 Light CT"            { channel="mqtt:topic:mosquitto:bedroom3-light:ct" }
Number BedRoom3_Light_RSSI   "Bed Room3 Light RSSI [%d%%]" <network> (gSignalStrength) { channel="mqtt:topic:mosquitto:bedroom3-light:rssi" }
String BedRoom3_Light_State   (gTasmotaState)          { channel="mqtt:topic:mosquitto:bedroom3-light:state" }

So the process usually involves copy pasting from an existing light in the .things / .items file, then manually renaming the relevant bits, and there are quite a few bits to rename!

It is quite painful, and error prone, if I decided I wanted to add a group, or change the thing’s name, or change something else, and have this change applied to all my tasmota light bulb items / things.

My script helps me solve this by using templates to avoid repetitive editing.

What it does: read from a list of devices, and generate the relevant .things and .items lines based on a template. Each device can specify which template to use, e.g. “tasmota-light-rgb” (for coloured bulbs), or “tasmota-light-cct” (for tunable white bulbs), “aqara-pir” (for xiaomi aqara human sensor via zigbee), “aqara-button” (for a button control), etc. So I just have to write the template once and it can be reused by multiple device. It guarantees that my things and items all use the same pattern, yet be flexible enough for each individual device’s customisations, such as groups, metadata, etc.

If I want to tweak my items definition, I just need to edit the template, and regenerate the .items / .things file(s) and all my relevant devices will be updated.

I use Jinja2 as the template engine. All my script does is read from a yaml structure and pass it on to the template engine. The template syntax is very simple, yet powerful when needed.

Each type of device is defined in a separate template file, so it is easy to manage / edit.

My device list looks like this:

  template: aqara-pir
  groups: (gLightTriggers, gMotions)

  template: aqara-button
  groups: (gLightButtons)

  template: tasmota-light-cct
  groups: (gInsideLights, gPresenceSimulators)
    - ga="Light"

There is a special section in the yaml file to specify settings such as where to output the file(s) append headers to the .things / .items file, etc. So the one .yaml file contains the complete instructions of what to generate, how and where.

The way the things are defined in the yaml doesn’t have to be this way. For example if you want to specify the groups as a list instead of a string as above, you can, as long as you adjust how it’s accessed in the template.

Example of “tasmota-light-cct” template:

Thing mqtt:topic:mosquitto:{{thingid}} "{{label}}" (mqtt:broker:mosquitto) {
        Type switch: power	    [ stateTopic="stat/{{thingid}}/RESULT", transformationPattern="REGEX:(.*POWER.*)∩JSONPATH:$.POWER", commandTopic="cmnd/{{thingid}}/POWER" ]
        Type dimmer: dimmer     [ stateTopic="stat/{{thingid}}/RESULT", transformationPattern="REGEX:(.*Dimmer.*)∩JSONPATH:$.Dimmer", commandTopic="cmnd/{{thingid}}/Dimmer" ]
        Type dimmer: ct         [ stateTopic="stat/{{thingid}}/RESULT", transformationPattern="REGEX:(.*CT.*)∩JSONPATH:$.CT", commandTopic="cmnd/{{thingid}}/CT", min=153, max=500, step=1 ]
        Type switch: reachable  [ stateTopic="tele/{{thingid}}/LWT", on="Online", off="Offline" ]
        Type number: rssi	    [ stateTopic="tele/{{thingid}}/STATE", transformationPattern="JSONPATH:$.Wifi.RSSI" ]
        Type string: state      [ stateTopic="tele/{{thingid}}/dummy", commandTopic="cmnd/{{thingid}}/STATE" ]

Switch {{name}}_Power  "{{label}} Power" <light> {{groups}} { channel="mqtt:topic:mosquitto:{{thingid}}:power", autoupdate="false" }
Dimmer {{name}}_Dimmer "{{label}}" { channel="mqtt:topic:mosquitto:{{thingid}}:dimmer"{% for m in metadata: %}, {{m}}{%endfor%} }
Dimmer {{name}}_CT     "{{label}} CT"            { channel="mqtt:topic:mosquitto:{{thingid}}:ct" }
Number {{name}}_RSSI   "{{label}} RSSI [%d%%]" <network> (gSignalStrength) { channel="mqtt:topic:mosquitto:{{thingid}}:rssi" }
String {{name}}_State   (gTasmotaState)          { channel="mqtt:topic:mosquitto:{{thingid}}:state" }

Note that {{thingid}}, {{name}}, {{label}} are automatically generated by my script, for convenience. {{name}} is the BedRoom3_Light, {{thingid}} is name converted to lower case and underscore replaced with a dash, i.e. bedroom3-light, and {{label}} is name but turned into human readable Bed Room 3 Light. They are just for convenience, and they don’t have to be used. They can be overridden / explicitly specified inside the yaml structure if desired. Other variables can also be created, for example:

   mythingid: living-room-light-device-thingamajig
   myvariable1: something
   my_metadata: hello

in the template:

Thing something:something:{{mythingid}} {{myvariable1}}

Switch {{name}}_Something "This is {{myvariable1}}" { dummymetadata={{my_metadata}} }

I decided to put both the Things and Items in one template file, to keep things more organised and easily edited. My script will split the Things / Items into their respective files.

Anyway, I wrote this to make my life easier.

Comments and suggestions are welcome!