Using Includes Strategically

A fellow forum user recently sent me a PM about my use of includes in ESPHome and I wanted to share my strategy with a wider audience in case it was helpful for anyone else. The advantage of the method is that if I want to make sweeping changes to my devices I can do it in one file and then Update All my nodes instead of editing 20 files individually.

This started when I wanted to drop the connectivity sensor from my switches and foreseeing other such a change in the future, I migrated to a strategy where I define my switches with two include files plus the secrets file. The first include is specific to the switch type and the second is shared across all my devices.

For example, this is what most of my switches look like:

substitutions:
  # https://esphome.io/guides/configuration-types.html#substitutions
  device_name: storageroomlights            # hostname & entity_id
  friendly_name: Storage Room Lights        # Displayed in HA frontend
  icon: mdi:lightbulb

# include any override YAML here

<<: !include .gosund_ks602.yaml
<<: !include .common.yaml

And that’s it! Just the substitutions section and two includes.

The order of the includes is important. The first section to get defined is the section that wins. So, for instance, I have the logger: section defined in my .common.yaml file and also in some of my switch specific includes (see example below). I need the one in the switch specific file to win, so it is before the common one. I also use this technique to override anything else defined later in the includes, which is why the includes need to be at the bottom of the file, not the top.

Here is an example of my .gosund_ks602.yaml included above:

esphome:
  # https://esphome.io/components/esphome
  name: ${device_name}
  platform: ESP8266
  board: esp01_1m

logger:
  baud_rate: 0 # turn off serial logging so we don't interfere with the green LED

sensor:
   # Uptime sensor
   - platform: uptime
     name: "${friendly_name} Uptime"
   
binary_sensor:
   - platform: status
     name: "${friendly_name} Connectivity"
    
  - platform: gpio
    pin:
      number: GPIO0
      inverted: True
    id: button_id
    on_press:
      then:
        - switch.toggle: relay

switch:
  - platform: gpio
    name: "${friendly_name}"
    icon: ${icon}
    pin: GPIO14
    id: relay
    on_turn_on:
      then:
        - output.set_level: 
            id: red_led
            level: 100%
    on_turn_off:
      then:
        - output.turn_off: red_led
    restore_mode: ALWAYS_OFF

output:
  - platform: esp8266_pwm
    id: red_led
    pin:
      number: GPIO16
      inverted: True
    
status_led:
    pin: # green led
      number: GPIO1
      inverted: True

And here is my .common.yaml file:

wifi:
  # https://esphome.io/components/wifi
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  use_address: ${device_name}.mycomain.com # I use this to add a custom domain instead of using the default .local

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "${friendly_name}"
    password: !secret wifi_ap_password

captive_portal:

# Enable logging
logger:
  # https://esphome.io/components/logger

# Enable Home Assistant API
api:
  # https://esphome.io/components/api
  password: !secret api_password

ota:
  # https://esphome.io/components/ota
  password: !secret ota_password

# Enable Web server.
web_server:
  port: 80
  auth:
    username: !secret web_username
    password: !secret web_password

# Get the time from Home Assistant to sync the onboard real-time-clock.  
time:
  - platform: homeassistant
    id: ha_time

And finally, my secret.yaml file (not my real info):

wifi_ssid: MiddleEarth
wifi_password: gandalfthegray
wifi_ap_password: hardtoguess
api_password: hardtoguess
ota_password: hardtoguess
web_password: hardtoguess
web_username: admin

I also name my includes to stat with a period (.) so that they are excluded from showing up in the ESPHome dashboard. I edit them via the SMB share or a file editor in HA. I hope this is helpful!

10 Likes

Great post! Thanks for sharing!!

A great addition was released in 1.15.0, witch is packages.
I just changed my includes to packages!

Can someone explain (or at least confirm) the meaning of the following from the packages documentation:

Dictionaries are merged key-by-key. Lists of components are merged by component ID if specified. Other lists are merged by concatenation. All other config values are replaced with the later value.

I’m assuming that “Lists of components are merged by component ID if specified.” means that if my first file has

sensor:
  - platform: abc

and my second file has

sensor:
  - platform: xyx

both will be included, right?

And “All other config values are replaced with the later value.” means that if my first file has

logger:
  level: abc

and my second file has

logger:
  level: xyz

the level will be set as xyz. This later value wining seems to be the opposite of what @coderanger is saying above.

But I have no idea what “Dictionaries are merged key-by-key.” or “Other lists are merged by concatenation.” mean.

Thank you! This tip is so useful!

1 Like