Another Things and Items file Generator

What is it?

  • Generate .things and .items files based on device templates to eliminate repetitive copy/pasting.
  • Very easy to change / add / remove devices.
  • Useful when you have multiple devices that are the same, e.g. 30 Tasmota / ESPhome light bulbs, 20 switches, Zigbee2mqtt motion sensors, etc.
  • Create one template for each device type
  • Use the same template for all the devices of the same kind
  • It can be used from the command line or automatically reload and regenerate .things and .items files whenever your device list changed (see the second post below).

How do I use it? I have one BIG yaml file called devices.yaml. This one file contains the list of all the things/devices to be generated by their corresponding templates, so it contains multiple things one after another. They all get generated into ‘generated.things’ and ‘generated.items’.

I have other custom / hand edited things and items files too for unique one off items like the central air conditioning system.

Usage

Command Line Usage

Execute itemsgen.rb from the command line

$ chmod 755 itemsgen.rb
$ ./itemsgen.rb devices.yaml -f

The command line options will be printed out with -h:

Usage: ./itemsgen.rb [options] yamlfile
    -t, --things THINGS_FILE         The path to things file output
    -i, --items  ITEMS_FILE          The path to items file output
    -v, --verbose                    Print details
    -n, --dry-run                    Run process but do not write to output files
    -f, --force                      Overwrite output files
    -d, --template-dir PATH          Path to look for the template files
    -h, --help                       Print this help

When the output files are specified inside the yaml file, the -t and -i options are not required. When provided, they will override the yaml, however.

When template-dir is not specified, it will look in the templates/ subdirectory relative to the yaml file.

Template Example

Simple:

YAML data:

MasterBathRoom_Thermometer:
  template: aqara-thermometer
  groups:
    - gMasterBathRoom

Template:

Thing mqtt:topic:mosquitto:<%= thingid %> (mqtt:broker:mosquitto)  @ "Environment" {
    Channels:
        Type number : temperature  [ stateTopic="zigbee/<%= thingid %>/temperature", unit="°C" ]
        Type number : humidity     [ stateTopic="zigbee/<%= thingid %>/humidity", unit="%" ]
        Type number : pressure     [ stateTopic="zigbee/<%= thingid %>/pressure", unit="mbar" ]
        Type number : linkquality  [ stateTopic="zigbee/<%= thingid %>/linkquality" ]
        Type contact: availability [ stateTopic="zigbee/<%= thingid %>/availability", on="online", off="offline" ]
        Type number : battery      [ stateTopic="zigbee/<%= thingid %>/battery", unit="%" ]
}

// Template: <%= template_name %>

Group <%= name %> "<%= label %>" <%= make_groups groups %> <%= make_tags 'Sensor' %>
<%# Must add one extra line after the closing tag %>

Number:Temperature <%= name_parts[0] %>_Temperature "<%= room %> Temperature" <temperature> <%= make_groups name, temperature['groups'], 'gTemperature' %> <%= make_tags %w[Measurement Temperature], temperature['tags'] %> { channel="mqtt:topic:mosquitto:<%= thingid %>:temperature", ga="TemperatureSensor", alexa="TemperatureSensor.temperature"<%= add_metadata temperature['metadata'] %> }
Number             <%= name_parts[0] %>_Humidity "<%= room %> Humidity [%.1f%%]" <humidity> <%= make_groups name, humidity['groups'], 'gHumidity' %> <%= make_tags %w[Measurement Humidity], humidity['tags'] %> { channel="mqtt:topic:mosquitto:<%= thingid %>:humidity", alexa="CurrentHumidity"<%= add_metadata humidity['metadata'] %> }
Number:Pressure    <%= name_parts[0] %>_Pressure "<%= room %> Pressure" <%= make_groups name, pressure['groups'] %> <%= make_tags %w[Measurement Pressure], pressure['tags'] %> { channel="mqtt:topic:mosquitto:<%= thingid %>:pressure"<%= add_metadata pressure['metadata'] %> }
Number             <%= name %>_Link    "<%= label %> Link" <network>  <%= make_groups name, 'gSignalStrength' %> { channel="mqtt:topic:mosquitto:<%= thingid %>:linkquality" }
Number             <%= name %>_Battery "<%= label %> Battery [%d%%]" <battery> <%= make_groups name, 'gBatteries' %> { channel="mqtt:topic:mosquitto:<%= thingid %>:battery" }
Contact            <%= name %>_Availability "<%= label %> Availability [MAP(availability.map):%s]" <%= make_groups name, 'gAvailability' %> { channel="mqtt:topic:mosquitto:<%= thingid %>:availability" }

More complex:

YAML:

MasterBedRoom_Light:
  template: tasmota-light
  groups: # Common group that applies to the light
    - gMasterBedRoom
  power:
    groups: # Groups that apply only to the "Power" item
      - gInsideLights
      - gPresenceSimulators
  color: # This lightbulb supports color feature
  ct: # This lightbulb supports color temperature

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" ]
<% if key? 'ct' %>
        Type dimmer : ct         [ stateTopic="stat/<%= thingid %>/RESULT", transformationPattern="REGEX:(.*CT.*)∩JSONPATH:$.CT", commandTopic="cmnd/<%= thingid %>/CT", min=153, max=500, step=1 ]
<% end %>

<% if key? 'color' %>
        Type string : color      [ stateTopic="stat/<%= thingid %>/RESULT", transformationPattern="REGEX:(.*Color.*)∩JSONPATH:$.Color", commandTopic="cmnd/<%= thingid %>/Color" ]
        Type colorHSB : colorhsb [ stateTopic="stat/<%= thingid %>/RESULT", transformationPattern="REGEX:(.*HSBColor.*)∩JSONPATH:$.HSBColor", commandTopic="cmnd/<%= thingid %>/HSBColor" ]
<% end %>
        Type number : rssi	                [ stateTopic="tele/<%= thingid %>/STATE", transformationPattern="JSONPATH:$.Wifi.RSSI" ]
        Type string : state      [ stateTopic="tele/<%= thingid %>/dummy", commandTopic="cmnd/<%= thingid %>/STATE" ]
        Type string : ipaddress [ stateTopic="stat/<%= thingid %>/STATUS5", transformationPattern="JSONPATH:$..StatusNET.IPAddress", commandTopic="cmnd/<%= thingid %>/status" ]
        Type contact: availability [ stateTopic="tele/<%= thingid %>/LWT", on="Online", off="Offline" ]
}

<%

# Based on `omit_assistant_metadata` (from yaml), conditionally return
# the given array or an empty array
assistant = {
  light: %w[ga="Light" alexa="Endpoint.Light"],
  brightness: %w[ga="lightBrightness" alexa="BrightnessController.brightness"],
  power: %w[ga="lightPower" alexa="Powercontroller.powerState"],
  ct: %w{ga="lightColorTemperature" alexa="ColorTemperatureController.colorTemperatureInKelvin" [increment="10"]},
  color: %w[ga="lightColor" alexa="ColorController.color"]
}.transform_values { |metadata| omit_assistant_metadata ? [] : metadata }
%>
// Template: <%= template_name %>

Group <%= name %> "<%= label %>" <light> <%= make_groups groups %> <%= make_tags 'Lightbulb', tags %> <%= make_metadata assistant[:light], metadata %> 

Switch <%= name %>_Power  "<%= label %> Power" <light> <%= make_groups name, power['groups'] %> <%= make_tags %w[Control Power], power['tags'] %> { channel="mqtt:topic:mosquitto:<%= thingid %>:power", autoupdate="false"<%= add_metadata assistant[:power], power['metadata'] %> }
Dimmer <%= name %>_Dimmer "<%= label %>" <%= make_groups name, dimmer['groups'] %> <%= make_tags %w[Control Light], dimmer['tags'] %> { channel="mqtt:topic:mosquitto:<%= thingid %>:dimmer"<%= add_metadata assistant[:brightness], dimmer['metadata'] %> }

<% if key? 'ct' %>
Dimmer <%= name %>_CT     "<%= label %> CT" <%= make_groups name, ct['groups'] %> <%= make_tags %w[Control ColorTemperature], ct['tags'] %> { channel="mqtt:topic:mosquitto:<%= thingid %>:ct"<%= add_metadata assistant[:ct], ct['metadata'] %> }
<% end %>

<% if key? 'color' %>
Color <%= name %>_Color  "<%= label %> Color" <colorwheel> <%= make_groups name, color['groups'] %> <%= make_tags %w[Control Color], color['tags'] %>  { channel="mqtt:topic:mosquitto:<%= thingid %>:colorhsb"<%= add_metadata assistant[:color], color['metadata'] %> }
<% end %>

Number <%= name %>_RSSI   "<%= label %> RSSI [%d%%]" <network> <%= make_groups name, 'gSignalStrength' %>  { channel="mqtt:topic:mosquitto:<%= thingid %>:rssi" }
String <%= name %>_State <%= make_groups name, 'gTasmotaState' %>     { channel="mqtt:topic:mosquitto:<%= thingid %>:state", id="<%= thingid %>" }
String <%= name %>_IPAddress <%= make_groups name, 'gTasmotaIPs' %>     { channel="mqtt:topic:mosquitto:<%= thingid %>:ipaddress", autoupdate="false" }
Contact <%= name %>_Availability "<%= label %> Availability [MAP(availability.map):%s]" <%= make_groups name, 'gAvailability' %> { channel="mqtt:topic:mosquitto:<%= thingid %>:availability" }


Note: I’m a Ruby newbie, and the code isn’t as polished as it could be. I especially suck at choosing good class / variable names.

I’d appreciate any criticisms, feedback and suggestions.

I am planning to integrate this into my automation rules so that it would automatically regenerate the .items and .things files when the yaml file is modified. Currently waiting for the directory watch feature from @broconne

1 Like

Update:

  • 2023-03-29 Updated the script to be compatible with helper library 5.0

The items/things file can be generated automatically when devices.yaml file is updated with the help of this rule:

require "personal"
require "personal/itemsgen"

rule "Autogen Items" do
  watch OpenHAB::Core.config_folder / "misc" / "devices.yaml", for: :modified
  run do |event|
    logger.info("#{event.path} #{event.type}. Regenerating things/items file.")
    yaml = YAML.load_file(event.path)
    gen = OpenhabGenerator::Devices.new(yaml)
    gen.template_dir = OpenHAB::Core.config_folder / "misc" / "templates"
    output = gen.generate
    File.write(OpenHAB::Core.config_folder / "things" / "z_generated.things", output["things"])
    File.write(OpenHAB::Core.config_folder / "items" / "z_generated.items", output["items"])
  end
end
3 Likes