Less is more.
Especially in configuration files for remote nodes of your home automation system, where you tend to repeat yourself. A lot, to be honest, especially if you have fairly generous requirements for what every remote node has to have even before you get settled in to what it actually does.
On the other hand, I have finally gotten the configuration file for - to use as an example - my most basic “environmental sensor” node down to a much nicer 28 lines, a welcome change from its previous “eh, under 100 lines”. How? Substitutions and packages.
This is what the file looks like now:
substitutions: | |
deviceName: env-office | |
deviceFriendlyName: Office Environment Sensor | |
description: Environment sensor for my office | |
version: 1.0.0 | |
packages: | |
device: !include packages/esp8266-huzzah.yaml | |
base: !include packages/base.yaml | |
# I2C bus | |
i2c: | |
# Sensors | |
sensor: | |
# BME280 environment sensor | |
- platform: bme280 | |
temperature: | |
name: Office Temperature | |
humidity: | |
name: Office Humidity | |
pressure: | |
name: Office Pressure | |
# TSL2561 ambient light sensor | |
- platform: tsl2561 | |
name: Office Illuminance |
Almost nothing in there that isn’t specifically relevant to this particular sensor, rather than including a whole bunch of boilerplate.
At the top, we have the substitutions. (See here in the docs.) Those define those certain details that the packages may need. In this case:
deviceName - the name of the device, for use in naming;
deviceFriendlyName - the friendly name of the device, which will be used to name the various sensors defined in packages;
description - a brief description of the node, for the ESPHome dashboard; and
version - the version of the node’s definition.
Then, we include a couple of packages. (See here, a bit further down the page, for those docs.) I’ve chosen to include two packages in every device. The first is the “device” package; in this case, esp8266-huzzah.yaml, because most of my nodes are based around the Adafruit Feather HUZZAH ESP8266, which is an adorable and flexible wee microcontroller at a reasonable price - and one I bought a whole bunch of back at the start of my home automation project. This package looks like this:
# Common features for all Adafruit Feather ESP8266 HUZZAH device nodes. | |
esphome: | |
name: ${deviceName} | |
platform: ESP8266 | |
board: huzzah | |
comment: ${description} | |
project: | |
name: "avatar.${deviceName}" | |
version: "${version}" | |
# Enable status LED | |
status_led: | |
pin: | |
# 0 is red LED (inverted) | |
number: 0 | |
inverted: true |
Which is basically just recapitulating the substitutions into the relevant places of the opening of an ESPHome config file. You can look up the details if you want ‘em, but I’m going to assume you’re probably familiar with what one of those looks like.
I also put the Status LED (that magic LED that indicates whether or not your node is working) configuration into this device-specific file because it requires configuration of the appropriate GPIO pin, which happens to be 0 on the HUZZAH but is not necessarily so on other microcontrollers I may use.
The rest of the components don’t have that constraint, so my generic configuration went into the longer base.yaml package:
# Common features for all our device nodes. | |
# Enable logging | |
logger: | |
# Enable wireless networking | |
wifi: | |
ssid: "Arkane Systems" | |
password: "<REDACTED>" | |
fast_connect: true | |
# Enable fallback hotspot (captive portal) in case wifi connection fails | |
ap: | |
ssid: "${deviceName} Fallback Hotspot" | |
password: "<REDACTED>" | |
captive_portal: | |
# Enable over-the-air updates. | |
ota: | |
password: '<REDACTED>' | |
# Enable Home Assistant API | |
api: | |
password: '<REDACTED>' | |
# Binary Sensors | |
binary_sensor: | |
# Node state. | |
- platform: status | |
name: ${deviceFriendlyName} | |
# Sensors | |
sensor: | |
# Uptime sensor | |
- platform: uptime | |
name: ${deviceFriendlyName} Uptime | |
# Wireless strength signal | |
- platform: wifi_signal | |
name: ${deviceFriendlyName} Wireless | |
# Switches | |
switch: | |
# Restart node. | |
- platform: restart | |
name: ${deviceFriendlyName} Restart | |
# Shutdown node. | |
- platform: shutdown | |
name: ${deviceFriendlyName} Shutdown | |
# Text sensors | |
text_sensor: | |
# Version | |
- platform: version | |
name: ${deviceFriendlyName} ESPHome Version |
Now this one? Is a fair bit of stuff, which would all have had to be repeated in every node definition. We’re setting up logging, we’re defining the setup for the WiFi and its fallback hotspot - information which would also have to be changed in every config if the WiFi were reconfigured - we’re setting up OTA updates and the Home Assistant API…
… and then we’re adding sensors. As you can see, I like my default sensors. Apart from one which simply indicates the node is connected to Home Assistant (which is to say, a failure sensor), I have each node report its uptime, its wireless signal strength (which I map to check on how the signal propagates around my house), and its current version (ESPHome version and compile time for that node) such that nothing gets missed at upgrade time.
Having the restart switch is also handy if one of the nodes glitches for some reason. (I haven’t found much of a use for the shutdown one yet, but include it anyway. The ability to remotely kill a node that’s gone deranged when I’m not on the premises seems like a good thing to have on general principles.)
So that would be a lot of boilerplate to copy to every new node file (or update in every old node file), as you can see. Fortunately, with substitutions and packages, I don’t have to.