It's time to de-duplicate and tidy up some of my ESPHome Configs This doc is broken down into a few sections, the explanation for why, the shared configurations, and finally my device type configurations, this last one has usage docs.
Update 17th April 2025:
restore_switch_state.yaml
no longer uses method call/perform, I think this was always broken, but it was compiled and installed fine until ESPHome 2025.4.0
Table of Contents
Why de-duplicate?
I want to make it easier to maintain and apply changes and keep configurations consistent, with my previous blog posts I had things working but they were inconsistent (because I pulled configs from t’internet)
The issues I had were
- Not all devices restored state, some of my bulbs did, none of my switches did, when power was restored
- I had inconsistent IDs based on where configuration files came from (
rgbww_light
vslight_rgbww
) - I wanted more consistency across the formatting
- If I decided to add a configuration option in the future, I didn’t want to add it to 20+ devices by hand
Research
I found an initial “simple” solution from the ESPHome documentation, the YAML insertion operator
The issues with this is it literally just adds the content to the current definition, so you can’t have duplicate sections
If your main yaml has
substitutions:
some_var: value
<<: !include common.yaml
And the common.yaml
has
substitutions:
other_var: other value
You’ll get errors, this wasn’t great for my reduce, reuse, recycle plan.
So then I found out about packages, this is exactly what I wanted since you merge the files this way.
The format here is very similar, but also a bit odd (I wanted to know more about the naming and if I could reference things, but no luck)
packages:
common: !include common.yaml
restore_light_state: !include restore_light_state.yaml
Then my files are well configured (below to save repitition) to reduce the duplication
Shared configurations
These aren’t really device specific but are building blocks for the devices
common.yaml
This file defines my ESPHome configs that are common across all my devices
substitutions:
wifi_fast_connect: "false"
# Common configuration to setup defaults
logger:
web_server:
captive_portal:
mdns:
api:
ota:
platform: esphome
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
ap:
# Allow rapid re-connection to previously connect WiFi SSID, skipping scan of all SSID
fast_connect: "${wifi_fast_connect}"
restore_light_state.yaml
This restores the light state based on the individual device configuration, my original configs had light_rgbww
or rgbww_light
as the light id, which I didn’t like!
# Provides directive to allow the light state to be restored from the previous mode
esphome:
on_boot:
then:
- script.execute: fast_boot_script
- select.set_index:
id: power_mode
index: !lambda |-
return id(restore_mode)-1;
- lambda: |-
switch(id(restore_mode))
{
case 1:{
auto call = id(rgbww_light).turn_off();
call.perform();
break;
}
case 2:{
auto call = id(rgbww_light).turn_on();
call.set_color_mode(ColorMode::WHITE);
call.set_brightness(1.0);
call.perform();
break;
}
default:{
break;
}
}
button:
- platform: factory_reset
name: "Factory Reset"
id: Reset
entity_category: config
globals:
- id: fast_boot
type: int
restore_value: yes
initial_value: "0"
- id: restore_mode
type: int
restore_value: yes
initial_value: "1"
select:
- platform: template
name: "Power On State"
id: "power_mode"
optimistic: true
options:
- Always Off
- Always On
- Restore Power Off State
on_value:
then:
- lambda: |-
id(restore_mode)=i+1;
script:
- id: fast_boot_script
then:
- if:
condition:
lambda: return ( id(fast_boot) >= 3 );
then:
- lambda: |-
ESP_LOGD("${friendly_name}", "Now the counter is greater than or equal to 3, perform reset device and reboot");
id(fast_boot) = 0;
fast_boot->loop();
global_preferences->sync();
auto call = id(rgbww_light).turn_on();
call.set_transition_length(500);
call.set_brightness(1.0);
call.set_color_mode(ColorMode::RGB);
call.set_rgb(0.0, 0.0, 1.0);
call.perform();
- delay: 5s
- button.press: Reset
- lambda: |-
id(fast_boot) += 1;
fast_boot->loop();
global_preferences->sync();
ESP_LOGD("${friendly_name}", "Now the counter is %d. Reset the device when the counter is greater than or equal to 3", id(fast_boot));
- delay: 10s
- lambda: |-
ESP_LOGD("${friendly_name}", "Reset counter");
id(fast_boot) = 0;
fast_boot->loop();
global_preferences->sync();
restore_switch_state.yaml
Similar to restore light state, but for outlets! I could probably deduplicate some more, but this is fine for me for now
Updated 2025/04/17
# Provides directive to allow the switch state to be restored from the previous mode
esphome:
on_boot:
then:
- script.execute: fast_boot_script
- select.set_index:
id: power_mode
index: !lambda |-
return id(restore_mode)-1;
- lambda: |-
switch(id(restore_mode))
{
case 1:{
id(outlet).turn_off();
break;
}
case 2:{
id(outlet).turn_on();
break;
}
default:{
break;
}
}
globals:
- id: fast_boot
type: int
restore_value: yes
initial_value: "0"
- id: restore_mode
type: int
restore_value: yes
initial_value: "1"
button:
- platform: factory_reset
name: "Factory Reset"
id: Reset
entity_category: config
select:
- platform: template
name: "Power On State"
id: "power_mode"
optimistic: true
options:
- Always Off
- Always On
- Restore Power Off State
on_value:
then:
- lambda: |-
id(restore_mode)=i+1;
script:
- id: fast_boot_script
then:
- if:
condition:
lambda: return ( id(fast_boot) >= 3 );
then:
- lambda: |-
ESP_LOGD("${friendly_name}", "Now the counter is greater than or equal to 3, perform reset device and reboot");
id(fast_boot) = 0;
fast_boot->loop();
global_preferences->sync();
id(outlet).turn_on();
- delay: 5s
- button.press: Reset
- lambda: |-
id(fast_boot) += 1;
fast_boot->loop();
global_preferences->sync();
ESP_LOGD("${friendly_name}", "Now the counter is %d. Reset the device when the counter is greater than or equal to 3", id(fast_boot));
- delay: 10s
- lambda: |-
ESP_LOGD("${device_name}", "Reset counter");
id(fast_boot) = 0;
fast_boot->loop();
global_preferences->sync();
Devices
These are the device specific files, I add a quick and easy section to copy-and-paste to new devices if I need to re-use or share them with others
globe-50323.yaml
This is my core definition for the Globe 50232 pot lights
## Configuration for Global 50323 Pot Lights
# substitutions:
# number: "4"
# <<: !include common/globe-50323.yaml
packages:
common: !include common.yaml
restore_light_state: !include restore_light_state.yaml
esphome:
name: "globe-50323-rgbct-recessed-${number}"
friendly_name: "Globe Lighting 50323 Recessed RGBCT Light (${number})"
bk72xx:
board: generic-bk7231t-qfn32-tuya
text_sensor:
- platform: libretiny
version:
name: LibreTiny Version
output:
- platform: libretiny_pwm
id: output_red
pin: P8
- platform: libretiny_pwm
id: output_green
pin: P7
- platform: libretiny_pwm
id: output_blue
pin: P6
- platform: libretiny_pwm
id: output_cold
pin: P26
- platform: libretiny_pwm
id: output_warm
pin: P24
light:
- platform: rgbww
id: rgbww_light
name: Light
color_interlock: true
cold_white_color_temperature: 6500 K
warm_white_color_temperature: 2700 K
red: output_red
green: output_green
blue: output_blue
cold_white: output_cold
warm_white: output_warm
globe-rgb.yaml
This defines my Globe RGB WW Bulbs
# Copy these to the parent
# substitutions:
# light_restore_mode: RESTORE_DEFAULT_ON
# number: "X"
#
# <<: !include common/globe-rgb.yaml
packages:
restore_light_state: !include restore_light_state.yaml
common: !include common.yaml
esphome:
friendly_name: "Globe RGB ${number}"
name: "globe-rgb-${number}"
bk72xx:
board: generic-bk7231t-qfn32-tuya
text_sensor:
- platform: libretiny
version:
name: LibreTiny Version
sm2135:
clock_pin: P8
data_pin: P26
rgb_current: 20mA
cw_current: 55mA
output:
- platform: sm2135
id: output_red
channel: 2
- platform: sm2135
id: output_green
channel: 1
- platform: sm2135
id: output_blue
channel: 0
- platform: sm2135
id: output_cold
channel: 4
- platform: sm2135
id: output_warm
channel: 3
light:
- platform: rgbww
id: rgbww_light
name: Light
restore_mode: ${light_restore_mode}
color_interlock: true
cold_white_color_temperature: 6500 K
warm_white_color_temperature: 2700 K
red: output_red
green: output_green
blue: output_blue
cold_white: output_cold
warm_white: output_warm
button:
- platform: restart
name: "Restart"
entity_category: config
- platform: safe_mode
name: "Safe Mode"
internal: false
entity_category: config
athom-rgbcw.yaml
This is REALLY complex but I wanted to keep it around to remind myself of the complexity here
# Put these in the parent file
# substitutions:
# number:
# # Allows ESP device to be automatically linked to an 'Area' in Home Assistant. Typically used for areas such as 'Lounge Room', 'Kitchen' etc
# room: ""
# # Description as appears in ESPHome & top of webserver page
# device_description: "athom 7w rgbcw light bulb"
# # Project Name
# project_name: "Athom Technology.Athom RGBCW Bulb"
# # Projection version denotes the release version of the yaml file, allowing checking of deployed vs latest version
# project_version: "v1.1.3"
# # Restore the light (GPO switch) upon reboot to state:
# light_restore_mode: RESTORE_DEFAULT_ON
# # Define a domain for this device to use. i.e. iot.home.lan (so device will appear as athom-smart-plug-v2.iot.home.lan in DNS/DHCP logs)
# dns_domain: ".local"
# # Set timezone of the smart plug. Useful if the plug is in a location different to the HA server. Can be entered in unix Country/Area format (i.e. "Australia/Sydney")
# timezone: ""
# # Set the duration between the sntp service polling ntp.org servers for an update
# sntp_update_interval: 6h
# # Network time servers for your region, enter from lowest to highest priority. To use local servers update as per zones or countries at: https://www.ntppool.org/zone/@
# sntp_server_1: "0.pool.ntp.org"
# sntp_server_2: "1.pool.ntp.org"
# sntp_server_3: "2.pool.ntp.org"
# # Enables faster network connections, with last connected SSID being connected to and no full scan for SSID being undertaken
# wifi_fast_connect: "false"
# # Define logging level: NONE, ERROR, WARN, INFO, DEBUG (Default), VERBOSE, VERY_VERBOSE
# log_level: "DEBUG"
# # Enable or disable the use of IPv6 networking on the device
# ipv6_enable: "false"
# color_interlock: "true"
packages:
restore_light_state: !include restore_light_state.yaml
common: !include common.yaml
esphome:
name: "athom-rgbcw-bulb-${number}"
# Default friendly name
friendly_name: "Athom RGBCW Bulb ${number}"
comment: "${device_description}"
area: "${room}"
name_add_mac_suffix: false
min_version: 2024.6.0
project:
name: "${project_name}"
version: "${project_version}"
esp8266:
board: esp8285
restore_from_flash: true
preferences:
flash_write_interval: 1min
dashboard_import:
package_import_url: github://athom-tech/athom-configs/athom-rgbww-light.yaml
binary_sensor:
- platform: status
name: "Status"
entity_category: diagnostic
sensor:
- platform: uptime
name: "Uptime Sensor"
id: uptime_sensor
entity_category: diagnostic
internal: true
- platform: wifi_signal
name: "WiFi Signal dB"
id: wifi_signal_db
update_interval: 60s
entity_category: "diagnostic"
- platform: copy
source_id: wifi_signal_db
name: "WiFi Signal Percent"
filters:
- lambda: return min(max(2 * (x + 100.0), 0.0), 100.0);
unit_of_measurement: "Signal %"
entity_category: "diagnostic"
device_class: ""
button:
- platform: restart
name: "Restart"
entity_category: config
- platform: safe_mode
name: "Safe Mode"
internal: false
entity_category: config
output:
- platform: esp8266_pwm
id: red_output
pin: GPIO4
min_power: 0.000499
max_power: 1
- platform: esp8266_pwm
id: green_output
pin: GPIO12
min_power: 0.000499
max_power: 1
- platform: esp8266_pwm
id: blue_output
pin: GPIO14
min_power: 0.000499
max_power: 1
- platform: esp8266_pwm
id: warm_white_output
pin: GPIO13
min_power: 0.000499
max_power: 0.9
- platform: esp8266_pwm
id: white_output
pin: GPIO5
min_power: 0.000499
max_power: 0.9
light:
- platform: rgbww
id: rgbww_light
name: "RGBCW_Bulb"
restore_mode: ${light_restore_mode}
red: red_output
green: green_output
blue: blue_output
warm_white: warm_white_output
cold_white: white_output
cold_white_color_temperature: 6000 K
warm_white_color_temperature: 3000 K
color_interlock: ${color_interlock}
text_sensor:
- platform: wifi_info
ip_address:
name: "IP Address"
entity_category: diagnostic
ssid:
name: "Connected SSID"
entity_category: diagnostic
mac_address:
name: "Mac Address"
entity_category: diagnostic
# Creates a sensor showing when the device was last restarted
- platform: template
name: "Last Restart"
id: device_last_restart
icon: mdi:clock
entity_category: diagnostic
# device_class: timestamp
# Creates a sensor of the uptime of the device, in formatted days, hours, minutes and seconds
- platform: template
name: "Uptime"
entity_category: diagnostic
lambda: |-
int seconds = (id(uptime_sensor).state);
int days = seconds / (24 * 3600);
seconds = seconds % (24 * 3600);
int hours = seconds / 3600;
seconds = seconds % 3600;
int minutes = seconds / 60;
seconds = seconds % 60;
if ( days > 3650 ) {
return { "Starting up" };
} else if ( days ) {
return { (String(days) +"d " + String(hours) +"h " + String(minutes) +"m "+ String(seconds) +"s").c_str() };
} else if ( hours ) {
return { (String(hours) +"h " + String(minutes) +"m "+ String(seconds) +"s").c_str() };
} else if ( minutes ) {
return { (String(minutes) +"m "+ String(seconds) +"s").c_str() };
} else {
return { (String(seconds) +"s").c_str() };
}
icon: mdi:clock-start
time:
- platform: sntp
id: sntp_time
# Define the timezone of the device
timezone: "${timezone}"
# Change sync interval from default 5min to 6 hours (or as set in substitutions)
update_interval: ${sntp_update_interval}
# Set specific sntp servers to use
servers:
- "${sntp_server_1}"
- "${sntp_server_2}"
- "${sntp_server_3}"
# Publish the time the device was last restarted
on_time_sync:
then:
# Update last restart time, but only once.
- if:
condition:
lambda: 'return id(device_last_restart).state == "";'
then:
- text_sensor.template.publish:
id: device_last_restart
state: !lambda 'return id(sntp_time).now().strftime("%a %d %b %Y - %I:%M:%S %p");'
globe-smart-plug.yaml
Again, similar to ones above but it uses the restore-switch-state
package instead of light
# Globe Electric 50329 smart plug
# Copy this to the new config
# substitutions:
# number: "1"
# <<: !include common/globe-smart-plug.yaml
packages:
restore_outlet_state: !include restore_outlet_state.yaml
common: !include common.yaml
esphome:
friendly_name: "Globe Plug ${number}"
name: globe-plug-${number}
comment: Globe Electric 50329 Smart plug
bk72xx:
board: wb2s
time:
- platform: homeassistant
id: homeassistant_time
sensor:
- platform: uptime
name: Uptime
unit_of_measurement: minutes
filters:
- lambda: return x / 60.0;
- platform: wifi_signal
name: Signal
update_interval: 60s
light:
- platform: status_led
name: "led"
internal: true
id: led
pin:
number: P7
inverted: true
binary_sensor:
- platform: gpio
pin:
number: P26
inverted: true
id: button1
filters:
- delayed_on: 10ms
- delayed_off: 10ms
on_click:
- switch.toggle: outlet
- platform: status
name: Status
switch:
- platform: gpio
name: Outlet
id: outlet
pin: P24
icon: mdi:power-socket-us
on_turn_on:
- light.turn_on: led
on_turn_off:
- light.turn_off: led
Leave a Reply