Dynamically create switches in ESPHome

I have a 24 relay board. I’d like to simplify my configuration. Is there any way to avoid adding 24 switch elements within ESPHome’s YAML? This is a similar question to this Reddit post.

I’m doing this now, but I still have to add it 24 times. Is there any way to generate this in some kind of loop? Or does it need to be manually entered 24 times?

I couldn’t figure out how to simplify it further, either.

switch:
  - platform: gpio
    id: station_1
    name: "ESP Station 1"
    pin:
      sn74hc595: sn74hc595_hub
      number: 1
    on_turn_on: !include { file: includes/sprinkler_on_turn_on.yaml, vars: { id: 1 }}

  - platform: gpio
    id: station_2
    name: "ESP Station 2"
    pin:
      sn74hc595: sn74hc595_hub
      number: 2
    on_turn_on: !include { file: includes/sprinkler_on_turn_on.yaml, vars: { id: 2 }}

... and 22 more times.

The point of my include is a fail-safe to turn off the switch if on for too long.

- delay: 2h
- if:
    condition:
      switch.is_on: station_${id}
    then:
      - logger.log: "Turning off sprinker ${id} after 2 hours"
      - switch.turn_off: station_${id}

By the way, what happens if the switch is turned on for 1.5 hours. Turned off and then turned back on? Does the second time replace the running delay? Or would they both run and at the first 2 hours the switch would be turned off?

That is, I assume it doesn’t work like a HA automation’s for: where it must be continuously on to trigger.

Manually copy-paste.
Ask Chatgpt to do it.
Make a macro on your editor.

Yes.
If you want a full control, it’s better to use a script component for automation.

Seems I need a way to kill the timer as the on_turns_on continues to run even if there is a later turn-off and then turn-on cycle. That is, each call creates its own timer.

If I use the following config and turn the switch on at 0 seconds, then off at 5s, then on at 10s, and off at 15s the on_turn_on fires two times. That is, the first turn on at 0 seconds fires AND the second on at 10 seconds fires:

- platform: gpio
  name: ESP Station 12
  id: station_12
  pin:
    sn74hc595: sn74hc595_hub
    number: 11
    mode:
      output: true
    inverted: false
  on_turn_on:
  - then:
    - logger.log:
        format: 20s timeout started for sprinker 12
        tag: main
        level: DEBUG
        args: []
    - delay: 20s
    - logger.log:
        format: Auto-timeout sprinker 12
        tag: main
        level: DEBUG
        args: []
    - switch.turn_off:
        id: station_12
  disabled_by_default: false
  restore_mode: ALWAYS_OFF
  interlock_wait_time: 0ms

Here’s manually turning toggling the switch ever 5 seconds, with two Turning ON at 0s and 10s.

[12:15:00][D][switch:012]: 'ESP Station 12' Turning ON.
[12:15:00][D][switch:055]: 'ESP Station 12': Sending state ON
[12:15:00][D][main:679]: 20s timeout started for sprinker 12
[12:15:05][D][switch:016]: 'ESP Station 12' Turning OFF.
[12:15:05][D][switch:055]: 'ESP Station 12': Sending state OFF
[12:15:10][D][switch:012]: 'ESP Station 12' Turning ON.
[12:15:10][D][switch:055]: 'ESP Station 12': Sending state ON
[12:15:10][D][main:679]: 20s timeout started for sprinker 12
[12:15:15][D][switch:016]: 'ESP Station 12' Turning OFF.
[12:15:15][D][switch:055]: 'ESP Station 12': Sending state OFF

Then there’s the two corresponding turn-offs at 0+20 seconds and the second at 10+20 seconds.

[12:15:20][D][main:686]: Auto-timeout sprinker 12
[12:15:20][D][switch:016]: 'ESP Station 12' Turning OFF.
[12:15:30][D][main:686]: Auto-timeout sprinker 12
[12:15:30][D][switch:016]: 'ESP Station 12' Turning OFF.

So, that does not work.

I want to turn a switch off if it has been running continuously for some length of time.

Sounds like what I need to do is use a script, and then on_turn_off kill the script with ’ script.stop’. Or is there another way?

I first started with a script. I thought I could use a single script and pass in the switch to check. But quickly learned you can’t use a template on switch.turn_off. I had started with something like this:

### Does NOT work
script:
  - id: auto_turn_off
    mode: parallel
    parameters:
      valve: string
    then:
      - delay: 10s
      - switch.turn_off: !lambda valve

So, if I can’t pass in the switch ID as a parameter to turn off I’d need 24 scripts. Is that correct?

Thanks.

I’m not sure if I’m following you completely. Anyway, if you want to have switch that increase the time every switch on, you can do it without script. I wouldn’t anyway because script gives you much better control. You can adjust it with mode and max_run parameters and with execute / stop however you want.

I personally don’t know a way to pass ID as parameter. Tmho long simple code is prettier than short and complicated. I’m not a coder… :wink:

1 Like

You can take a look at using packages as templates to help reduce repetition.

1 Like

You can use a script in restart mode to reset the timer like this:

(Edit: Whoops, reading on a bit I see you’ve already looked into this approach).

substitutions:
  static_ip_last_3chars: "132"
  plug_number: "4"
  plug_colour:  "White"
  device_description: "Bathroom Fan"
  auto_off_after_x_mins: 45min

packages:
  device_base: !include common/.arlec-smartplug-common.yaml

switch:
  - id: !extend switch_1
    on_turn_on:
      - script.execute: auto_off_timer

script:
  - id: auto_off_timer   # Turn off after some time.
    mode: restart        # Ensures the timer "restarts".
    then:
      - delay: ${auto_off_after_x_mins}
      - switch.turn_off: switch_1

So, if I can’t pass in the switch ID as a parameter to turn off I’d need 24 scripts. Is that correct?

Good Q. Not sure if you can access the current id and pass it to a script.

This seems to be the best I could come up with:

In my main config YAML file I have this:

substitutions:
  name: esp-24-sprinkler
  friendly_name: ESP-24-Sprinkler
  static_ip_address: 192.168.0.3


esp8266:
  board: esp01_1m

sn74hc595:
  - id: 'sn74hc595_hub'
    data_pin: GPIO14
    clock_pin: GPIO13
    latch_pin: GPIO12
    oe_pin: GPIO05
    sr_count: 3


packages:
  connect: !include includes/connections.yaml
  station_1: !include  { vars: { station: 1,  pin: 0  }, file: includes/package_sprinkler_switch.yaml }
  station_2: !include  { vars: { station: 2,  pin: 1  }, file: includes/package_sprinkler_switch.yaml }
  station_3: !include  { vars: { station: 3,  pin: 2  }, file: includes/package_sprinkler_switch.yaml }
  station_4: !include  { vars: { station: 4,  pin: 3  }, file: includes/package_sprinkler_switch.yaml }
  station_5: !include  { vars: { station: 5,  pin: 4  }, file: includes/package_sprinkler_switch.yaml }
  station_6: !include  { vars: { station: 6,  pin: 5  }, file: includes/package_sprinkler_switch.yaml }
  station_7: !include  { vars: { station: 7,  pin: 6  }, file: includes/package_sprinkler_switch.yaml }
  station_8: !include  { vars: { station: 8,  pin: 7  }, file: includes/package_sprinkler_switch.yaml }
  station_9: !include  { vars: { station: 9,  pin: 8  }, file: includes/package_sprinkler_switch.yaml }
  station_10: !include { vars: { station: 10, pin: 9  }, file: includes/package_sprinkler_switch.yaml }
  station_11: !include { vars: { station: 11, pin: 10  }, file: includes/package_sprinkler_switch.yaml }
  station_12: !include { vars: { station: 12, pin: 11  }, file: includes/package_sprinkler_switch.yaml }
  station_13: !include { vars: { station: 13, pin: 12  }, file: includes/package_sprinkler_switch.yaml }
  station_14: !include { vars: { station: 14, pin: 13  }, file: includes/package_sprinkler_switch.yaml }
  station_15: !include { vars: { station: 15, pin: 14  }, file: includes/package_sprinkler_switch.yaml }
  station_16: !include { vars: { station: 16, pin: 15  }, file: includes/package_sprinkler_switch.yaml }
  station_17: !include { vars: { station: 17, pin: 16  }, file: includes/package_sprinkler_switch.yaml }
  station_18: !include { vars: { station: 18, pin: 17  }, file: includes/package_sprinkler_switch.yaml }
  station_19: !include { vars: { station: 19, pin: 18  }, file: includes/package_sprinkler_switch.yaml }
  station_20: !include { vars: { station: 20, pin: 19  }, file: includes/package_sprinkler_switch.yaml }
  station_21: !include { vars: { station: 21, pin: 20  }, file: includes/package_sprinkler_switch.yaml }
  station_22: !include { vars: { station: 22, pin: 21  }, file: includes/package_sprinkler_switch.yaml }
  station_23: !include { vars: { station: 23, pin: 22  }, file: includes/package_sprinkler_switch.yaml }
  station_24: !include { vars: { station: 24, pin: 23  }, file: includes/package_sprinkler_switch.yaml }

Then my include is just this:

substitutions:
  max_time_on: 2h

switch:
  - platform: gpio
    name: "ESP Station $station"
    id: station_$station
    pin:
      sn74hc595: sn74hc595_hub
      number: $pin
    on_turn_on:
      - script.execute: on_turn_on_$station
    on_turn_off:
      - script.stop: on_turn_on_$station



script:
  - id: on_turn_on_$station
    mode: restart
    then:
      - logger.log: "$max_time_on timeout started for sprinker $station"
      - delay: $max_time_on
      - logger.log: "Auto-timeout sprinker $station after $max_time_on"
      - switch.turn_off: station_$station

I tried to figure out if I could do any bash math so I didn’t need to have a separate $pin variable, but no luck. Would be handy to have some kind of loop structure, but this is pretty easy to do.

Only addition might be to have a homeassistant.action: that I could use to have HA notify me if a switch was turned off in this way. Of course, the purpose of this is to turn off the switch IF Home Assistant is not connected.

2 Likes

Nice one. That’s not so bad I reckon.