Trying to use package function to call a common.yaml from each ESPHome device

I did a wee search but could not find too much on this (but apologies if I missed it). I have 5 ESP32-C3s, all running well for a while now, with an example yaml from one of them, as shown below.

The 5# yamls are identical, w.e.o. lines 2 and 3, which define names. That is it, the remaining 95% of the yamls are the same for each device (all BT Proxy Trackers, for now).

display_name: esp32c3btproxykat
friendly_name: ESP32C3BTProxyKat

As the 5# yamls are the same, I thought to streamline future edits so I only need to make changes in one file, say esp32common.yaml.

So what I have done, having read the esphome packages section is to split my example working yaml into:

  1. The original first 3 lines (which includes the names) and package: section, which calls the esp32common.yaml file. I saved this as the original file name esp32c3btproxykat.yaml;
substitutions: # name substitutions ref Digiblur https://digiblur.com/wiki/ha/esphome-bluetooth-proxy-esp32c3/
  display_name: esp32c3btproxykat
  friendly_name: ESP32C3BTProxyKat
  
  packages:
    # wifi: !include common/wifi.yaml - example additional package for wifi
    esp32c3devices: !include common/esp32c3btproxy-common.yaml
  1. A new yaml, esp32common.yaml, which contains the remaining 95% of the original yaml, without the above lines. I put this in /homeassistant/esphome/common.

However when I tried to verify it I got the following error:

INFO ESPHome 2025.4.0
INFO Reading configuration /config/esphome/esp32c3btproxykat.yaml...
Failed config

substitutions: None
  display_name: esp32c3btproxykat
  friendly_name: ESP32C3BTProxyKat
  
  Must be string, got <class 'esphome.helpers.OrderedDict'>. did you forget putting quotes around the value?.
  packages: 
    esp32c3devices: 
      esphome: 
        name: ${display_name}
        friendly_name: ${friendly_name}
        min_version: 2024.11.0
        name_add_mac_suffix: False
      wifi: 
        reboot_timeout: 3min
        ssid: !secret wifi_ssid

My understanding was that you could just call the common yaml from the main yaml for each device and that the display_name and friendly_name variables would be passed to the common_yaml. I will admit I am not 100% clear on this though!

I would really appreciate a heads up on why this is failing and how to achieve what it is I am looking to do. Thank you.

ORIGINAL WORKING YAML

substitutions: # name substitutions ref Digiblur https://digiblur.com/wiki/ha/esphome-bluetooth-proxy-esp32c3/
  display_name: esp32c3btproxykat
  friendly_name: ESP32C3BTProxyKat

esphome:
  name: ${display_name}
  friendly_name: ${friendly_name}
  min_version: 2024.11.0
  name_add_mac_suffix: false

wifi:
  # output_power: 8.5dB # enable if having issues with connectivity
  reboot_timeout: 3min
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  fast_connect: true # Optional. Required (true) if connecting to a hidden SSID; suggest disable if setting Manual IP

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

esp32: 
  variant: ESP32C3
  board: esp32-c3-devkitm-1
  framework:
    type: esp-idf # This is important: the default "arduino" framework does not perform well.
    sdkconfig_options:
      CONFIG_BT_BLE_42_FEATURES_SUPPORTED: y # BLE 4.2 is supported by ALL ESP32 boards that have bluetooth, the original and derivatives.
      CONFIG_BT_BLE_50_FEATURES_SUPPORTED: y # Also enable this on any derivative boards (S2, C3 etc) but not the original ESP32.
      CONFIG_ESP_TASK_WDT: y
      CONFIG_ESP_TASK_WDT_TIMEOUT_S: "10" # Extend the watchdog timeout, so the device reboots if the device appears locked up for over 10 seconds.

logger: # Enable logging
  level: DEBUG # Debug=Default; use Verbose if error checking
  # baud_rate: 0  # 0 Enables logging, but disables serial-port logging to free CPU and memory

ota: # Allow Over-The-Air updates
- platform: esphome
  password: !secret ota_password

api: # Enable Home Assistant API; MUST check option under ESPHome Configure!
  encryption:
    key: !secret encryption_key
  on_client_connected: # Only enable BLE tracking when wifi is up and api is connected;Gives single-core ESP32-C3 devices time to manage wifi and authenticate with api
     - esp32_ble_tracker.start_scan:
        continuous: true
  on_client_disconnected: # Disable BLE tracking when there are no api connections live
    if:
      condition:
        not:
          api.connected:
      then:
        - esp32_ble_tracker.stop_scan:
  
# Optional Manual IP
# manual_ip:
#   static_ip: 192.168.53.109
#   gateway: !secret gateway
#   subnet: !secret subnet

captive_portal:

esp32_ble_tracker:
  id: ble_tracker
  scan_parameters: # Bermuda suggests 280/320 or 920/1000 to allow offset BT/Wifi
    interval: 320ms # default 1100ms
    window: 280ms # default 1100ms
    continuous: true
    active: false #defaults to true - selecting false saves power but may not work

bluetooth_proxy:
  active: true # Optional, defaults to false

button:
  - platform: safe_mode
    id: button_safe_mode
    name: Safe Mode Boot

  - platform: factory_reset
    id: factory_reset_btn
    name: Factory Reset

# time:
#  - platform: homeassistant
#    id: homeassistant_time

sensor:
  - platform: uptime
    # The uptime sensor is extremely helpful to know if your device is rebooting
    # when it shouldn't be. This might indicate your interval-to-window timing is
    # too tight, and the window needs to be reduced.
    name: "Uptime Sensor"
    update_interval: 60s

I do something very similar. It didn’t make sense to repeat the common YAML for every device.

Here’s an example. Here is my device file: flash.yaml and my wifi.yaml

# Blink the LED_BUILTIN (D4) on a Wemos D1 Mini

############################################

substitutions:
  device_name: blink
  friendly_name: blink
  project_name: ${device_name}
  project_version: "1.0"
#  my_ssid: !secret wifi_ssid
#  my_pass: !secret wifi_password

packages:
  wifi: !include common/wifi.yaml
  device_base: !include common/esp8266.yaml

esp8266:
  #Override the default board
  board: d1_mini  

# Status Light
light:
  - platform: status_led
    name: ${device_name} status LED
    id: ready
    pin: D1  


##################### This Board ########################

# Blink the LED on D4 (GPIO2, LED_BUILTIN)
switch:
  - platform: gpio
    pin:
      number: D4 
      mode: output
    id: builtinLED

interval:
  - interval: 1000ms
    then:
      - switch.toggle: builtinLED

=======================

# wifi.yaml
#
# Example useage:
# May be overridden in the device configuration.yaml
#substitutions:
#  my_ssid: !secret wifi_ssid
#  my_pass: !secret wifi_password

substitutions:
  my_ssid: !secret iot_ssid
  my_pass: !secret iot_password

wifi:
  ssid: ${my_ssid}
  password: ${my_pass}
  power_save_mode: none  # Disable power-saving mode to avoid flashing Status LED

sensor:
  - platform: wifi_signal
    name: ${friendly_name} WiFi Level
    update_interval: 30s

# Get the WiFi details
text_sensor:
  - platform: wifi_info
    ip_address:
      name: ${friendly_name} IP
    ssid:
      name: ${friendly_name} SSID
    mac_address:
      name: ${friendly_name} Mac Address    


ota:
  platform: esphome
  
web_server:
  port: 80

mdns:
  disabled: False 

This greatly reduces my device file size.

In your case, you didn’t post your package, but I don’t think there is an "esp32c3devices: " component.

Your packages line appears to be indented, so is seen as part of substitutions.

1 Like

Nope:

I was referring to the OPs yaml, not yours.

Doesn’t need to be - the keys in the packages dict aren’t actually used for anything.

He didn’t post his package text, but I suspect that it staers with esp32c3devices:

It starts with esphome:, according to the log output posted earlier.

Yes, that is correct, my ‘common’ yaml is everything from esphome: down (of the original yaml, unchanged).

Is there only a defined list of package names? I just chose a name I could remember.

I will try with the indent and let you know.

Many thanks to you both.

You star! Have not run it yet but it validates fine.
Can’t believe you saw that so fast. Thank you.

INFO ESPHome 2025.4.0
INFO Reading configuration /config/esphome/esp32c3btproxykat.yaml...
substitutions:
  display_name: esp32c3btproxykat
  friendly_name: ESP32C3BTProxyKat
esphome:
  name: esp32c3btproxykat
  friendly_name: ESP32C3BTProxyKat
  min_version: 2024.11.0
  name_add_mac_suffix: false
  build_path: build/esp32c3btproxykat
  area: ''
  platformio_options: {}
  includes: []
  libraries: []
  debug_scheduler: false
esp32:
  variant: ESP32C3
  board: esp32-c3-devkitm-1
  framework:
    sdkconfig_options:
      CONFIG_BT_BLE_42_FEATURES_SUPPORTED: y
      CONFIG_BT_BLE_50_FEATURES_SUPPORTED: y
      CONFIG_ESP_TASK_WDT: y
      CONFIG_ESP_TASK_WDT_TIMEOUT_S: '10'
    version: 5.1.5
    advanced:
      ignore_efuse_custom_mac: false
    components: []
    platform_version: https://github.com/pioarduino/platform-espressif32.git#51.03.07
    source: pioarduino/framework-espidf@https://github.com/pioarduino/esp-idf/releases/download/v5.1.5/esp-idf-v5.1.5.zip
    type: esp-idf
  flash_size: 4MB
wifi:
  reboot_timeout: 3min
  fast_connect: true
  ap:
    ssid: esp32c3btproxykat
    password: IoTSmurfDev
    ap_timeout: 1min
  domain: .local
  power_save_mode: LIGHT
  enable_btm: false
  enable_rrm: false
  passive_scan: false
  enable_on_boot: true
  networks:
  - ssid: SmurfIoT
    password: IoTSmurfDev
    priority: 0.0
  use_address: esp32c3btproxykat.local
logger:
  level: DEBUG
  baud_rate: 115200
  tx_buffer_size: 512
  deassert_rts_dtr: false
  hardware_uart: USB_SERIAL_JTAG
  logs: {}
ota:
- platform: esphome
  version: 2
  port: 3232
api:
  encryption:
    key: 9ozEj0Wf+4ULDxT4AdMtgjYCO0hxs83ttOGkudv7Zs8=
  on_client_connected:
    then:
    - esp32_ble_tracker.start_scan:
        continuous: true
  on_client_disconnected:
    then:
    - if:
        condition:
          not:
            api.connected: {}
        then:
        - esp32_ble_tracker.stop_scan: {}
  port: 6053
  password: ''
  reboot_timeout: 15min
captive_portal: {}
esp32_ble_tracker:
  id: ble_tracker
  scan_parameters:
    interval: 320ms
    window: 300ms
    continuous: false
    active: true
    duration: 5min
  max_connections: 3
bluetooth_proxy:
  active: true
  cache_services: true
  connection_slots: 3
  connections:
  - {}
  - {}
  - {}
button:
- platform: restart
  name: Restart
  entity_category: config
  disabled_by_default: false
  icon: mdi:restart
  device_class: restart
- platform: factory_reset
  id: factory_reset_btn
  name: Factory Reset
  disabled_by_default: false
  icon: mdi:restart-alert
  entity_category: config
  device_class: restart
- platform: safe_mode
  id: button_safe_mode
  name: Safe Mode Boot
  disabled_by_default: false
  icon: mdi:restart-alert
  entity_category: config
  device_class: restart
sensor:
- platform: uptime
  name: Uptime Sensor
  update_interval: 60s
  disabled_by_default: false
  force_update: false
  unit_of_measurement: s
  icon: mdi:timer-outline
  accuracy_decimals: 0
  device_class: duration
  state_class: total_increasing
  entity_category: diagnostic
  type: seconds
- platform: wifi_signal
  name: WiFi Signal dB
  id: wifi_signal_db
  update_interval: 60s
  entity_category: diagnostic
  disabled_by_default: false
  force_update: false
  unit_of_measurement: dBm
  accuracy_decimals: 0
  device_class: signal_strength
  state_class: measurement

INFO Configuration is valid!

For completeness, this is my common yaml for my 5# identical device types for identical functions, that I put in /homeassistant/esphome; although I called it esp32c3btproxy-common.yaml for ease of reference.

# Common yaml for KM's esp32c3 devices

esphome:
  name: ${display_name}
  friendly_name: ${friendly_name}
  min_version: 2024.11.0
  name_add_mac_suffix: false

wifi:
  # output_power: 8.5dB # enable if having issues with connectivity
  reboot_timeout: 3min
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  fast_connect: true # Optional. Required (true) if connecting to a hidden SSID; suggest disable if setting Manual IP

  ap: # Enable fallback hotspot (captive portal) in case wifi connection fails
    ssid: ${display_name}
    password: !secret wifi_ap_password
    
  # Optional Manual IP in wifi section
  # manual_ip:
  #   static_ip: 192.168.53.xxx
  #   gateway: !secret gateway
  #   subnet: !secret subnet

esp32: 
  variant: ESP32C3
  board: esp32-c3-devkitm-1
  framework:
    type: esp-idf # This is important: the default "arduino" framework does not perform well.
    sdkconfig_options:
      CONFIG_BT_BLE_42_FEATURES_SUPPORTED: y # BLE 4.2 is supported by ALL ESP32 boards that have bluetooth, the original and derivatives.
      CONFIG_BT_BLE_50_FEATURES_SUPPORTED: y # Also enable this on any derivative boards (S2, C3 etc) but not the original ESP32.
      CONFIG_ESP_TASK_WDT: y
      CONFIG_ESP_TASK_WDT_TIMEOUT_S: "10" # Extend the watchdog timeout, so the device reboots if the device appears locked up for over 10 seconds.

logger: # Enable logging
  level: DEBUG # Debug=Default; use Verbose if error checking
  # baud_rate: 0  # 0 Enables logging, but disables serial-port logging to free CPU and memory

ota: # Allow Over-The-Air updates
- platform: esphome
  # password: !secret ota_password

api: # Enable Home Assistant API; MUST check option under ESPHome Configure!
  encryption:
    key: !secret encryption_key
  on_client_connected: # Only enable BLE tracking when wifi is up and api is connected;Gives single-core ESP32-C3 devices time to manage wifi and authenticate with api
     - esp32_ble_tracker.start_scan:
        continuous: true
  on_client_disconnected: # Disable BLE tracking when there are no api connections live
    if:
      condition:
        not:
          api.connected:
      then:
        - esp32_ble_tracker.stop_scan:
  
captive_portal:

esp32_ble_tracker:
  id: ble_tracker
  scan_parameters: # Bermuda suggests 280/320 or 920/1000 to allow offset BT/Wifi
    interval: 320ms # default 1100ms - Bermuda's defaults but also suggests 1000/900
    window: 300ms # default 1100ms
    continuous: false # Bermuda - Don't auto start BLE scanning, we control it in the `api` block's automation.
    active: true # defaults to true - selecting false saves power but may not work - Bermuda also says select true

bluetooth_proxy:
  active: true # Optional, defaults to false - true allows outbound connections from HA to devices.

button:
  - platform: restart
    name: "Restart"
    entity_category: config
    
  - platform: factory_reset
    id: factory_reset_btn
    name: Factory Reset
        
  - platform: safe_mode
    id: button_safe_mode
    name: Safe Mode Boot

sensor:
  - platform: uptime
    # The uptime sensor is extremely helpful to know if your device is rebooting
    # when it shouldn't be. This might indicate your interval-to-window timing is
    # too tight, and the window needs to be reduced.
    name: "Uptime Sensor"
    update_interval: 60s
    
  - platform: wifi_signal
    name: "WiFi Signal dB"
    id: wifi_signal_db
    update_interval: 60s
    entity_category: "diagnostic"
    
# time:
#  - platform: homeassistant
#    id: homeassistant_time

No, you can choose any name you like. I don’t know why the name is even required, it could just as easily be a list of packages rather than a dictionary.

1 Like

Thank you again; that makes sense, unless it has a meaning, like calling a specific common yaml or only operating on a yaml from the named section or something.

I could not find a connection between the example package names I came across and the file you refer to after the colon; so it’s all about the filename (and location). You might as well write donald_duck: and it wouldn’t know any better.

I think this can be done a lot easier…

I use this:

<<: !include esp-base.yaml

This esp-base.yaml contains all basic config, and is included in all other config yaml that call it.

Bonus, any following re-defined variable will override the one that is in the esp-base.yaml, which allows each config to use it’s own variable, instead of the one pre-defined :wink:

1 Like

Hi, thanks, I did look briefly at that, but my eyes curled up in my head as soon as I saw the name … it is called a " YAML insertion operator".

So you suggest I replace this:

packages:
    esp32c3devices: !include common/esp32c3btproxy-common.yaml

with this:

<<: !include common/esp32c3btproxy-common.yaml

and I am done?

Packages have one more additional benefit: can pass parameters into and use them, f.e. pin number, anything else.
F.e.:

packages:
  status: !include { file: package/status_on_LED.yaml,  vars: { pin: GPIO13 } }

And then use pin inside package same way as substitution.

light:
  - platform: status_led
    id: led_status
    name: 'LED'
    pin:
      number: ${pin}
...
1 Like

Erm…yes…

Copy this to common/esp32c3btproxy-common.yaml:

esphome:
  name: ${display_name}
  friendly_name: ${friendly_name}
  min_version: 2024.11.0
  name_add_mac_suffix: false

wifi:
  # output_power: 8.5dB # enable if having issues with connectivity
  reboot_timeout: 3min
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  fast_connect: true # Optional. Required (true) if connecting to a hidden SSID; suggest disable if setting Manual IP

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

esp32: 
  variant: ESP32C3
  board: esp32-c3-devkitm-1
  framework:
    type: esp-idf # This is important: the default "arduino" framework does not perform well.
    sdkconfig_options:
      CONFIG_BT_BLE_42_FEATURES_SUPPORTED: y # BLE 4.2 is supported by ALL ESP32 boards that have bluetooth, the original and derivatives.
      CONFIG_BT_BLE_50_FEATURES_SUPPORTED: y # Also enable this on any derivative boards (S2, C3 etc) but not the original ESP32.
      CONFIG_ESP_TASK_WDT: y
      CONFIG_ESP_TASK_WDT_TIMEOUT_S: "10" # Extend the watchdog timeout, so the device reboots if the device appears locked up for over 10 seconds.

logger: # Enable logging
  level: DEBUG # Debug=Default; use Verbose if error checking
  # baud_rate: 0  # 0 Enables logging, but disables serial-port logging to free CPU and memory

ota: # Allow Over-The-Air updates
- platform: esphome
  password: !secret ota_password

api: # Enable Home Assistant API; MUST check option under ESPHome Configure!
  encryption:
    key: !secret encryption_key
  on_client_connected: # Only enable BLE tracking when wifi is up and api is connected;Gives single-core ESP32-C3 devices time to manage wifi and authenticate with api
     - esp32_ble_tracker.start_scan:
        continuous: true
  on_client_disconnected: # Disable BLE tracking when there are no api connections live
    if:
      condition:
        not:
          api.connected:
      then:
        - esp32_ble_tracker.stop_scan:
  
# Optional Manual IP
# manual_ip:
#   static_ip: 192.168.53.109
#   gateway: !secret gateway
#   subnet: !secret subnet

captive_portal:

esp32_ble_tracker:
  id: ble_tracker
  scan_parameters: # Bermuda suggests 280/320 or 920/1000 to allow offset BT/Wifi
    interval: 320ms # default 1100ms
    window: 280ms # default 1100ms
    continuous: true
    active: false #defaults to true - selecting false saves power but may not work

bluetooth_proxy:
  active: true # Optional, defaults to false

button:
  - platform: safe_mode
    id: button_safe_mode
    name: Safe Mode Boot

  - platform: factory_reset
    id: factory_reset_btn
    name: Factory Reset

# time:
#  - platform: homeassistant
#    id: homeassistant_time

sensor:
  - platform: uptime
    # The uptime sensor is extremely helpful to know if your device is rebooting
    # when it shouldn't be. This might indicate your interval-to-window timing is
    # too tight, and the window needs to be reduced.
    name: "Uptime Sensor"
    update_interval: 60s

and then use this for each module:

substitutions:
  display_name: esp32c3btproxykat
  friendly_name: ESP32C3BTProxyKat

<<: !include common/esp32c3btproxy-common.yaml

So you can give each module their own display/friendly name by changing esp32c3btproxykat accordingly :wink:

1 Like