ESPhome controlling a lot of blinds

Hi there,

I have 22 blinds that I would like to control using ESPHome. My system is centralized, with all the blinds connected to a central box in the house. For this setup, I’ve utilized one ESPHome board and MCP23017 GPIO expanders.

I have successfully created a working program for a few blinds, but now I need to scale it up to control all 22. While copying and pasting the code 22 times is an option, I prefer to do it in a more modular way.

I’ve tried using substitutions, and this is where I am currently. I have two files: one is the main configuration file, and the other is a general configuration file. In cover component, I do multiple includes and set proper variables for each include.
main.yaml

substitutions:
  open_duration: 10s
  close_duration: 10s
  mcp23017_adr_1: '20'
  mcp23017_adr_2: '21'


esphome:
  name: roletar
  friendly_name: roletar


esp32:
  board: denky32
  #board: esp32dev
  framework:
    type: arduino

# Enable logging
logger:

# Enable Home Assistant API
api:
  encryption:


ota:
  platform: esphome


wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Roletar Fallback Hotspot"


captive_portal:
    
i2c:
  sda: 21
  scl: 22
  scan: true
  id: bus_a

# MCP Config 
### MCP pini 0-3 & 8-11 so INPUT, pini 4-7 & 12-15 so OUTPUT ###
mcp23017:
  - id: "mcp23017_1"
    address: ${mcp23017_adr_1}
  - id: "mcp23017_2"
    address: ${mcp23017_adr_2}

# Cover with !include parameters
cover: 
  platform: time_based

  - !include
      file: roleta.yaml
      vars:
        roleta_id: 1
        roleta_name: "Roleta 1"
        pin_rele_up: 4
        pin_rele_down: 5
        pin_key_up: 0
        pin_key_down: 1
        open_duration: ${open_duration}
        close_duration: ${close_duration}
        mcp23017_address: ${mcp23017_adr_1}

  - !include
      file: roleta.yaml
      vars:
        roleta_id: 2
        roleta_name: "Roleta 2"
        pin_rele_up: 6
        pin_rele_down: 7
        pin_key_up: 2
        pin_key_down: 3
        open_duration: ${open_duration}
        close_duration: ${close_duration}
        mcp23017_address: ${mcp23017_adr_1}
  
  - !include
      file: roleta.yaml
      vars:
        roleta_id: 3
        roleta_name: "Roleta 3"
        pin_rele_up: 12
        pin_rele_down: 13
        pin_key_up: 8
        pin_key_down: 9
        open_duration: ${open_duration}
        close_duration: ${close_duration}
        mcp23017_address: ${mcp23017_adr_1}

And general file roleta.yaml

esphome:
    name: roleta

esp32:
  board: denky32
  #board: esp32dev
  framework:
    type: arduino

substitutions:   #this gets replaced with include vars
    roleta_id: '1'
    roleta_name: "Roleta 1"
    pin_rele_up: '4'
    pin_rele_down: '5'
    pin_key_up: '0'
    pin_key_down: '1'
    open_duration: 10s
    close_duration: 10s
    mcp23017_address: '20'

cover:
  - id: "roleta_${roleta_id}"
    platform: time_based
    name: "${roleta_name}"
    open_action:
        - switch.turn_on: "rele_${roleta_id}_up"
    open_duration: ${open_duration}
    close_action:
        - switch.turn_on: "rele_${roleta_id}_down"
    close_duration: ${close_duration}
    stop_action:
        - switch.turn_off: "rele_${roleta_id}_up"
        - switch.turn_off: "rele_${roleta_id}_down"
    on_closed:
        then:
        - switch.turn_on: "rele_${roleta_id}_down"
        - delay: 2s
        - switch.turn_off: "rele_${roleta_id}_down"

switch:
  - id: "rele_${roleta_id}_up"
    platform: gpio
    name: "Rele Gor ${roleta_id}"
    pin:
      mcp23xxx: ${mcp23017_address}
      number: ${pin_rele_up}
    inverted: false

  - id: "rele_${roleta_id}_down"
    platform: gpio
    name: "Rele Dol ${roleta_id}"
    pin:
      mcp23xxx: ${mcp23017_address}
      number: ${pin_rele_down}
    inverted: false

binary_sensor:
  - id: "tipka_${roleta_id}_up"
    platform: gpio
    name: "Tipka Gor ${roleta_id}"
    pin:
      mcp23xxx: ${mcp23017_address}
      number: ${pin_key_up}
    inverted: false
    on_click:
      then:
        - cover.open: "roleta_${roleta_id}"

  - id: "tipka_${roleta_id}_down"
    platform: gpio
    name: "Tipka Dol ${roleta_id}"
    pin:
      mcp23xxx: ${mcp23017_address}
      number: ${pin_key_down}
    inverted: false
    on_click:
      then:
        - cover.close: "roleta_${roleta_id}"

I’m brain-dead at this point, tried too many things, could not get it to validate. I even used chatgpt.
Main question for me now is, is it even possible to do this on ESPHome?

Did you find this?

Packages as Templates

Thank you! I saw it before, but I dismissed it as a variant of substitutions. Since I’m weak in coding, the sample provided didn’t give me enough info. However, after revisiting it, I tried again, and after a few days it’s working now. I’ve pasted the code below in case it might be useful for someone else.

substitutions:
  open_duration: 10s
  close_duration: 10s
  mcp23017_adr_1: "0x20"  # 1st MCP23017
  mcp23017_adr_2: "0x21"  # 2nd MCP23017


esphome:
  name: roletar
  friendly_name: roletar


esp32:
  board: denky32
  #board: esp32dev
  framework:
    type: arduino

# Enable logging
logger:

# Enable Home Assistant API
api:

ota:
  platform: esphome

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Roletar Fallback Hotspot"

captive_portal:
    
i2c:
  sda: 21
  scl: 22
  scan: true
  id: bus_a

# MCP Config 
### MCP pini 0-3 & 8-11 so INPUT, pini 4-7 & 12-15 so OUTPUT ###
mcp23017:
  - id: "mcp23017_1"
    address:  ${mcp23017_adr_1}
  - id: "mcp23017_2"
    address:  ${mcp23017_adr_2}


# Packages (cover) with !include parameters
packages:
  roleta_1: !include
    file: packages/roleta.yaml
    vars:
      roleta_id: 1
      roleta_name: "Roleta 1"
      pin_key_up: 0
      pin_key_down: 1
      pin_rele_up: 4
      pin_rele_down: 5
      open_duration: ${open_duration}
      close_duration: ${close_duration}
      mcp23017_id: mcp23017_1

  roleta_2: !include
    file: packages/roleta.yaml
    vars:
      roleta_id: 2
      roleta_name: "Roleta 2"
      pin_key_up: 2
      pin_key_down: 3
      pin_rele_up: 6
      pin_rele_down: 7
      open_duration: ${open_duration}
      close_duration: ${close_duration}
      mcp23017_id: mcp23017_1

  roleta_3: !include
    file: packages/roleta.yaml
    vars:
      roleta_id: 3
      roleta_name: "Roleta 3"
      pin_key_up: 8
      pin_key_down: 9
      pin_rele_up: 12
      pin_rele_down: 13
      open_duration: ${open_duration}
      close_duration: ${close_duration}
      mcp23017_id: mcp23017_1

  roleta_4: !include
    file: packages/roleta.yaml
    vars:
      roleta_id: 4
      roleta_name: "Roleta 4"
      pin_key_up: 10
      pin_key_down: 11
      pin_rele_up: 14
      pin_rele_down: 15
      open_duration: ${open_duration}
      close_duration: ${close_duration}
      mcp23017_id: mcp23017_1

  roleta_5: !include
    file: packages/roleta.yaml
    vars:
      roleta_id: 5
      roleta_name: "Roleta 5"
      pin_key_up: 0
      pin_key_down: 1
      pin_rele_up: 4
      pin_rele_down: 5
      open_duration: ${open_duration}
      close_duration: ${close_duration}
      mcp23017_id: mcp23017_2

  roleta_6: !include
    file: packages/roleta.yaml
    vars:
      roleta_id: 6
      roleta_name: "Roleta 6"
      pin_key_up: 2
      pin_key_down: 3
      pin_rele_up: 6
      pin_rele_down: 7
      open_duration: ${open_duration}
      close_duration: ${close_duration}
      mcp23017_id: mcp23017_2

  roleta_7: !include
    file: packages/roleta.yaml
    vars:
      roleta_id: 7
      roleta_name: "Roleta 7"
      pin_key_up: 8
      pin_key_down: 9
      pin_rele_up: 12
      pin_rele_down: 13
      open_duration: ${open_duration}
      close_duration: ${close_duration}
      mcp23017_id: mcp23017_2

  roleta_8: !include
    file: packages/roleta.yaml
    vars:
      roleta_id: 8
      roleta_name: "Roleta 8"
      pin_key_up: 10
      pin_key_down: 11
      pin_rele_up: 14
      pin_rele_down: 15
      open_duration: ${open_duration}
      close_duration: ${close_duration}
      mcp23017_id: mcp23017_2

And roleta.yaml

substitutions:
  roleta_id: '0'
  roleta_name: "Default Roleta"
  pin_rele_up: '0'
  pin_rele_down: '1'
  pin_key_up: '2'
  pin_key_down: '3'
  open_duration: 10s
  close_duration: 10s
  mcp23017_id: mcp23017_1


cover:
  - id: "cover_roleta_${roleta_id}"
    platform: time_based
    name: "${roleta_name}"
    open_action:
      - switch.turn_on: "rele_up_roleta_${roleta_id}"
    open_duration: ${open_duration}
    close_action:
      - switch.turn_on: "rele_down_roleta_${roleta_id}"
    close_duration: ${close_duration}
    stop_action:
      - switch.turn_off: "rele_up_roleta_${roleta_id}"
      - switch.turn_off: "rele_down_roleta_${roleta_id}"
    on_closed: #for making sure that it is closed
      then:
        - switch.turn_on: "rele_down_roleta_${roleta_id}"
        - delay: 2s
        - switch.turn_off: "rele_down_roleta_${roleta_id}"

switch:
  - id: "rele_up_roleta_${roleta_id}"
    platform: gpio
    name: "Rele Gor Roleta ${roleta_id}"
    pin:
      mcp23xxx: ${mcp23017_id}
      number: ${pin_rele_up}
      inverted: false
    restore_mode: RESTORE_DEFAULT_OFF
    interlock: ["rele_down_roleta_${roleta_id}"]

  - id: "rele_down_roleta_${roleta_id}"
    platform: gpio
    name: "Rele Dol Roleta ${roleta_id}"
    pin:
      mcp23xxx: ${mcp23017_id}
      number: ${pin_rele_down}
      inverted: false
    restore_mode: RESTORE_DEFAULT_OFF
    interlock: ["rele_up_roleta_${roleta_id}"]

binary_sensor:
  - id: "tipka_up_roleta_${roleta_id}"
    platform: gpio
    name: "Tipka Gor Roleta ${roleta_id}"
    pin:
      mcp23xxx: ${mcp23017_id}
      number: ${pin_key_up}
      mode:
        input: true
        pullup: true
      inverted: false
    on_click: 
      min_length: 50ms
      max_length: 500ms
      then:
        - cover.open: "cover_roleta_${roleta_id}"
    on_multi_click: 
    - timing:
      - ON for at least 500ms
      then: 
        - cover.open: "cover_roleta_${roleta_id}"
        - wait_until:
            condition:
              binary_sensor.is_off: "tipka_up_roleta_${roleta_id}"
        - cover.stop: "cover_roleta_${roleta_id}"

  - id: "tipka_down_roleta_${roleta_id}"
    platform: gpio
    name: "Tipka Dol Roleta ${roleta_id}"
    pin:
      mcp23xxx: ${mcp23017_id}
      number: ${pin_key_down}
      mode:
        input: true
        pullup: false
      inverted: false
    on_click: 
      min_length: 50ms
      max_length: 500ms
      then:
        - cover.close: "cover_roleta_${roleta_id}"
    on_multi_click: 
    - timing:
      - ON for at least 500ms
      then: 
        - cover.close: "cover_roleta_${roleta_id}"
        - wait_until:
            condition:
              binary_sensor.is_off: "tipka_down_roleta_${roleta_id}"
        - cover.stop: "cover_roleta_${roleta_id}"
1 Like