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) {
    Channels:
        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:

BedRoom3_PIR:
  template: aqara-pir
  groups: 
    - gLightTriggers
    - gMotions

BedRoom3_Button:
  template: aqara-button
  groups: 
    - gLightButtons

BedRoom3_Light:
  template: tasmota-light
  features:
    - ct
  groups: 
    - gInsideLights
    - gPresenceSimulators
  metadata:
    - 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) {
    Channels:
        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|groups}} { channel="mqtt:topic:mosquitto:{{thingid}}:power", autoupdate="false" }
Dimmer {{name}}_Dimmer "{{label}}" { channel="mqtt:topic:mosquitto:{{thingid}}:dimmer{{metadata|metadata}} }
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:

LivingRoom_Light:
   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!

6 Likes

Updated (on github) with support for better syntax for metadata and simpler syntax in the template to avoid having to do loops for multiple groups/tags/metadata inside the template.

I have 60+ devices, my generated .things file is now over 600 lines (including blank lines/spacing), my generated .items file is 400 lines. I’m so glad I wrote this tool.

I understand that I can create the items directly using the REST api, but I like using .items and .things files because it’s easier to see the resulting things/items.

To handle a multi-gang wall switch, each individual switch/relay (and button if they are decoupled) can be specified.

Example:

MasterBathRoom_Switch1:
  template: tasmota-switch
  switches:
    - name: MasterBathRoom_Light_Switch
      label: Master Bath Room Light Switch
      groups:
        - gRule_Always_On
    - name: MasterBathRoom_ExhaustFan_Power
      label: Master Bath Room Exhaust Fan
      groups: 
        - gInsideLights
        - gExhaustFans
      metadata: 
        - ga: Fan
  buttons:
    - name: MasterBathRoom_Light_Button
      label: Master Bath Room Switch Button
      groups:
        - gLightButtons

Which will produce:

Switch MasterBathRoom_Light_Switch "Master Bath Room Light Switch" <switch> (gRule_Always_On) { channel="mqtt:topic:mosquitto:masterbathroom-switch1:power1", autoupdate="false" }
Switch MasterBathRoom_ExhaustFan_Power "Master Bath Room Exhaust Fan" <switch> (gInsideLights, gExhaustFans, gRule_Auto_Off) { channel="mqtt:topic:mosquitto:masterbathroom-switch1:power2", autoupdate="false", ga="Fan", gRule_Auto_Off="30m" }
String MasterBathRoom_Light_Button "Master Bath Room Switch Button"  (gSimple_Rule) { channel="mqtt:topic:mosquitto:masterbathroom-switch1:button1", autoupdate="false", gSimple_Rule="on" [ SINGLE="(? items.MasterBathRoom_Light_Power != ON ?) MasterBathRoom_Light_Dimmer=100,MasterBathRoom_Light_Power=TOGGLE", DOUBLE="MasterBathRoom_Light_Dimmer=1,MasterBathRoom_Light_Power=ON", TRIPLE="MasterBathRoom_Light_Dimmer=50,MasterBathRoom_Light_Power=ON", QUAD="MasterBathRoom_Light_CT=TOGGLE(0,100),MasterBathRoom_Light_Power=ON", HOLD="MasterBathRoom_Light_Switch=TOGGLE" ] }
Number MasterBathRoom_Switch1_RSSI   "Master Bath Room Switch1 RSSI [%d%%]"  <network> (gSignalStrength)  { channel="mqtt:topic:mosquitto:masterbathroom-switch1:rssi" }
String MasterBathRoom_Switch1_State	(gTasmotaState)                            { channel="mqtt:topic:mosquitto:masterbathroom-switch1:state" }

The templates can include other templates and import macros - if needed. I haven’t had the need to do it in my case yet.

1 Like