I wrote an items and things file generator in Python last year, because I just learned Python back then. This tool has been extremely useful to me, and I have been using it up until today.
I have rewritten this tool in Ruby, keeping only the main idea and the templates, which had to be modified to match the new templating engine. Note to use this, you just need the ruby interpreter (usually already installed on MacOS and Linux). I believe it will work with any ruby version without any external dependencies, but I’ve only tested it with 2.6 and 3.0.
Features
- Things channels and Items fields output are nicely aligned
- Use the power of Ruby inside the erb template
- Includes helper methods to make template writing easier
- Easily add default icon, groups, tags, and metadata
- Be as flexible or as simple as desired
- It can be used from the command line or from another ruby script, e.g. within a openhab-jruby rule
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