My Horizontal Blind control with

Hello fellow HA freaks =:-)

Here after ending at a long road of home automatizations for my H-Blinds,
my solution off a collection of diff solutions from other users (just borrowed)
And finally my end solution for my home (7 H-Blinds).

As Hardware I use a

  • 12v power adapter
  • 12v power connector
  • 12v to 5v L7805CV convertor
  • 5v stepper 28BYJ-48
  • ESP32-C3 Super Mini (or XIAO-ESP32-C3 from seeed studio for better wifi)
  • A4988 stepper controller
  • Hall 3144 sensor
  • No print, direct wiring connection
  • My almost self 3d creation of the unit to make and build in your H-Blind
    3D Print
    My final (almost complete yaml file for ESPHome) :
    I still need a solution / help for that global variables how to put that value into the stepper configuration (max_speed for example)
# 1) Press button for > 1 second to enter setup mode
# 2) Press button again to start the blind closing
# 3) Press button again when closed and blind starts to open (actually resets the stepper position to 0)
# 4) Press button again when blind is fully open
# 5) Job Done

# Button is also used to open/close the blind (must be fully open/closed first)

substitutions:
  slug: luxaflex-2
  area: Hobbykamer
  friendly_name: Luxeflex 2
  statusled_pin: 8
  description: >
    ESP$new_ip_adress :
    $friendly_name
  encryption_key:   !secret esphome_luxeflex_2_encryption_key
  new_ip_adress:    !secret esphome_luxeflex_2_IP
  esphome_ssid:     !secret esphome_orb_ssid
  esphome_pass:     !secret esphome_orb_pass
  esphome_network:  !secret esphome_orb_network

  <<: !include onboot/luxeflex.yaml
  
packages:
  <<: !include_dir_named common

esp32:
  board: esp32-c3-devkitm-1
  framework:
    type: esp-idf

output:
  - platform: gpio
    id: setupLED
    pin: GPIO05
    inverted: False

stepper:
  - platform: a4988
    id: my_stepper
    dir_pin:
      number: GPIO01        # GPIO06 for seeed studio XIAO-ESP32-C3
      inverted: True
    step_pin: GPIO03        # GPIO07 for seeed studio XIAO-ESP32-C3
    sleep_pin: GPIO00       # GPIO21 for seeed studio XIAO-ESP32-C3
    max_speed: 500 steps/s # Set the speed of the motor

globals:
  - id: stepper_slow_speed
    type: float
    initial_value: '300'

  - id: stepper_normal_speed
    type: float
    initial_value: '500'

  - id: my_stepper_global # Integer for storing the stepper position in case of reboot
    type: int
    restore_value: True
    initial_value: '0'

  - id: openclosed # Boolean to store OPEN/CLOSED state
    type: bool
    restore_value: True
    initial_value: '0'

  - id: endstop # Variable for storing ENDSTOP (how far to move stepper)
    type: int
    restore_value: True
    initial_value: '10000'

  - id: settingmode # Integer for Setup Mode
    type: int
    restore_value: False
    initial_value: '0'

binary_sensor:
  - platform: gpio
    pin:
      number: GPIO04 # HALL Sensor # GPIO20 for seeed studio XIAO-ESP32-C3
      mode: INPUT_PULLUP
      inverted: True
    use_interrupt: True
    interrupt_type: ANY
    name: HALL_sensor
    id: hall_sensor
    internal: False
    on_press:
      then:
        - logger.log:
            format: "Hall Switch Triggered"
            level: ERROR
        - lambda: 'id(blinded).publish_state("0");'
        - stepper.report_position:
            id: my_stepper
            position: 0
        - if:
            condition:
              - lambda: 'return id(settingmode) == 2;'
            then:
              - script.execute: setupbutton
              

switch:
  - platform: template
    name: Roller Blind Setup Switch # Switch to enter Setup Mode
    id: setupswitch
    lambda: |-
      if (id(settingmode) != 0) {
        return true;
      } else {
        return false;
      }
    turn_on_action:
      then:
        - logger.log: "Entered Settings Mode"
        - globals.set:
            id: settingmode
            value:  '1'
    turn_off_action:
      then:
        - logger.log: "Exiting Settings Mode"
        - globals.set:
            id: settingmode
            value:  '0'
  - platform: template
    name: Roller Blind Setup Button # Switch to replicate the Physical Button
    id: hasetup
    turn_on_action:
      - if: # If settings variable is on
          condition:
            - lambda: 'return id(settingmode) != 0;'
          then: # Enter Setting Mode
            - output.turn_on : setupLED
            - script.execute: setupbutton
            - switch.turn_off: hasetup

cover:
  - platform: template
    name: ${slug}
    id: blinded
    open_action:
      then:
        - logger.log: "Opening"
        - stepper.set_target: # Send stepper to endstop
            id: my_stepper
            target: !lambda return id(endstop);
        - while:
            condition:
              lambda: 'return id(my_stepper).current_position != id(endstop);'
            then:
              - cover.template.publish:
                  id: blinded
                  position: !lambda 'return (float(float(id(my_stepper).current_position) / float(id(endstop))));'
                  current_operation: OPENING
              - delay: 1000 ms
        - globals.set: # Set global to current position
            id: my_stepper_global
            value: !lambda return id(my_stepper).current_position;
        - globals.set: # Set toggle to OPEN (No need for 'optimistic mode')
            id: openclosed
            value: '1'
        - cover.template.publish:
            id: blinded
            state: OPEN
            current_operation: IDLE
    close_action:
      then:
        - logger.log: "Closing"
        - stepper.set_target: # Send stepper to 0
            id: my_stepper
            target: '0'
        - while:
            condition:
              lambda: 'return id(my_stepper).current_position != 0;'
            then:
              - cover.template.publish:
                  id: blinded
                  position: !lambda 'return (float(float(id(my_stepper).current_position) / float(id(endstop))));'
                  current_operation: CLOSING
              - delay: 1000 ms
        - globals.set: # Set global to current position
            id: my_stepper_global
            value: !lambda return id(my_stepper).current_position;
        - globals.set: # Set toggle to CLOSED (No need for 'optimistic mode')
            id: openclosed
            value: '0'
        - cover.template.publish:
            id: blinded
            state: CLOSED
            current_operation: IDLE
    position_action:
      then:
        - stepper.set_target:
            id: my_stepper
            target: !lambda return int(id(endstop) * pos);
        - while:
            condition:
              lambda: 'return id(my_stepper).current_position != int(id(endstop) * pos);'
            then:
              - cover.template.publish:
                  id: blinded
                  position: !lambda 'return (float(float(id(my_stepper).current_position) / float(id(endstop))));'
              - delay: 1000 ms
        - globals.set: # Set global to current position
            id: my_stepper_global
            value: !lambda return id(my_stepper).current_position;
        - cover.template.publish:
            id: blinded
            position: !lambda 'return (float(float(id(my_stepper).current_position) / float(id(endstop))));'
            current_operation: IDLE
    stop_action:
      then:
        - stepper.set_target:
            id: my_stepper
            target: !lambda return id(my_stepper).current_position;
        - globals.set: # Set global to current position
            id: my_stepper_global
            value: !lambda return id(my_stepper).current_position;
        - cover.template.publish:
            id: blinded
            position: !lambda 'return (float(float(id(my_stepper).current_position) / float(id(endstop))));'
            current_operation: IDLE
    has_position: true
    device_class: blind

script:
  - id: setupbutton
    then:
      - if:
          condition:
            - lambda: 'return (id(settingmode) == 3);'
          then:
            - output.turn_off : setupLED
            - logger.log: "-= Setup Button: Mode 3 =- (end setup)"
            - stepper.set_target: # Set Stepper position
                id: my_stepper
                target: !lambda return id(my_stepper).current_position;
            - globals.set: # Set Endstop Variable
                id: endstop
                value: !lambda return id(my_stepper).current_position;
            - globals.set: # Set Global stepper position
                id: my_stepper_global
                value: !lambda return id(my_stepper).current_position;
            - globals.set: # Reset Setting Mode
                id: settingmode
                value:  '0'
            - globals.set: # Set toggle to Open
                id: openclosed
                value: '1'
            - cover.template.publish:
                id: blinded
                state: OPEN
                current_operation: IDLE
            - logger.log: "Exiting Setting Mode"
            - stepper.set_speed:
                id: my_stepper
                speed: !lambda 'return id(stepper_normal_speed);'
      - if:
          condition:
            - lambda: 'return (id(settingmode) == 2);'
          then:
            - logger.log: "-= Setup Button: Mode 2 =-"
            - stepper.set_speed:
                id: my_stepper
                speed: !lambda 'return id(stepper_slow_speed);'
            - stepper.report_position: # Reset Stepper position to 0
                id: my_stepper
                position: '0'
            - stepper.set_target: # Reset Stepper position to 0
                id: my_stepper
                target: '0'
            - globals.set: # Move stepper to 0 (doesn't move it's already there!)
                id: my_stepper_global
                value: '0'
            - delay: 100ms
            - stepper.set_target: # Reset Stepper position to 72000
                id: my_stepper
                target: '72000'
            - globals.set: # Advance setup to next mode
                id: settingmode
                value:  '3'
      - if:
          condition:
            - lambda: 'return (id(settingmode) == 1);'
          then:
            - logger.log: "-= Setup mode: Mode 1 =-"
            - stepper.report_position: # Set Stepper position to 72000, makes it move to 0 (Closed)
                id: my_stepper
                position: '72000'
            - globals.set: # Advance setup to next mode
                id: settingmode
                value:  '2'

button:
  - platform: template
    name: "Reset to '0'"
    internal: False
    on_press: 
      - if:
          condition:
            binary_sensor.is_off: hall_sensor
          then:
            - stepper.report_position: # Set Stepper position to 72000, makes it move to 0 (Closed)
                id: my_stepper
                position: '72000'
            - globals.set: # Set toggle to CLOSED (No need for 'optimistic mode')
                id: openclosed
                value: '0'
            - cover.template.publish:
                id: blinded
                state: CLOSED
                current_operation: IDLE

If you where looking for such solution too (like me for a while and finally made my almost own gathered and self creation) :

Good luck and have FUN building it !!
If I forget something … just ask me.

GreetingZzz John,
-= May the automatizations be with you ! =-

PS I forget I have a non standard HA structure and I hope that this code works on a standard HA structure what I cannot test here :slight_smile:

# 1) Press button for > 1 second to enter setup mode
# 2) Press button again to start the blind closing
# 3) Press button again when closed and blind starts to open (actually resets the stepper position to 0)
# 4) Press button again when blind is fully open
# 5) Job Done

# Button is also used to open/close the blind (must be fully open/closed first)

substitutions:
  slug: luxaflex-2
  area: Hobbykamer
  friendly_name: Luxeflex 2
  statusled_pin: 8  # depend on board-type
  description: >
    ESP$new_ip_adress :
    $friendly_name
  encryption_key:   !secret esphome_luxeflex_2_encryption_key
  new_ip_adress:    !secret esphome_luxeflex_2_IP
  esphome_ssid:     !secret esphome_orb_ssid
  esphome_pass:     !secret esphome_orb_pass
  esphome_network:  !secret esphome_orb_network

esphome:
  name: "${slug}"
  comment: "${description}"
  build_path: "./.build/${slug}/"
#  arduino_version: latest
  on_boot:
    - priority: -200.0
      then:
        - stepper.report_position: # Set stepper to global variable
            id: my_stepper
            position: !lambda return id(my_stepper_global);
        - stepper.set_target: # Set stepper to global variable
            id: my_stepper
            target: !lambda return id(my_stepper_global);
        - if: # If blind is Closed
            condition:
              - lambda: 'return id(my_stepper_global) == 0;'
            then: # Publish state etc.
              - cover.template.publish:
                  id: blinded
                  state: CLOSED
                  current_operation: IDLE
        - if: # If blind is Open
            condition:
              - lambda: 'return id(my_stepper_global) == id(endstop);'
            then: # Publish state etc.
              - cover.template.publish:
                  id: blinded
                  state: OPEN
                  current_operation: IDLE
        - if: # If blind is Neither
            condition:
              - lambda: 'return (id(my_stepper_global) != 0) && (id(my_stepper_global) != id(endstop));'
            then: #  # Publish state etc.
              - cover.template.publish:
                  id: blinded
                  position: !lambda 'return (float(float(id(my_stepper).current_position) / float(id(endstop))));'
                  current_operation: IDLE

preferences:
  flash_write_interval: 5min

esp32:
  board: esp32-c3-devkitm-1
  framework:
    type: esp-idf

api:
  encryption:
    key: "V71q0lU1V+7wG7GInlBh1yBXWYid2XFAqiWSHcw5XcQ="
  reboot_timeout: 5min

wifi:
  networks:
  - id: component_wifi
  - ssid: $esphome_ssid
    password: $esphome_pass
  power_save_mode: none
  fast_connect: true
  min_auth_mode: WPA2
  manual_ip:
    # Set this to the IP of the ESP
    static_ip: "0.0.0.0"
    # Set this to the IP address of the router. Often ends with .1
    gateway: "0.0.0.0"
    # The subnet of the network. 255.255.255.0 works for most home networks.
    subnet: 255.255.255.0
#  use_address: "${slug}.local" # unmark this line if IP-adres was changed for some reason
  reboot_timeout: 10min
  ap:
    ssid: $slug
    password: !secret esphome_fbw_password

logger:
  id: component_logger
  level: VERY_VERBOSE
  initial_level: DEBUG
  # Disabling logging over UART by default.
  # Some devices this is needed, and in general,
  # I really don't look at it.
  baud_rate: 0
select:
  - platform: logger
    name: "Logger select"

ota:
  - platform: esphome
    id: my_ota
    password: "${ota_password}"

safe_mode:
  reboot_timeout: 10min
  num_attempts: 5
# unmark if changed the OTA password :
#esphome:
#  on_boot:
#    - lambda: |-
#        id(my_ota).set_auth_password("");

web_server:
  port: 80
  auth:
    username: !secret esphome_web_server_username
    password: !secret esphome_web_server_password


captive_portal: ~

output:
  - platform: gpio
    id: setupLED
    pin: GPIO05
    inverted: False

light:
  - platform: status_led
    name: "Statusled"
    id: statusled
    icon: mdi:led-outline
    internal: False
    pin:
      number: $statusled_pin
      inverted: true

stepper:
  - platform: a4988
    id: my_stepper
    dir_pin:
      number: GPIO01        # GPIO06 for seeed studio XIAO-ESP32-C3
      inverted: True
    step_pin: GPIO03        # GPIO07 for seeed studio XIAO-ESP32-C3
    sleep_pin: GPIO00       # GPIO21 for seeed studio XIAO-ESP32-C3
    max_speed: 500 steps/s # Set the speed of the motor

globals:
  - id: stepper_slow_speed
    type: float
    initial_value: '300'

  - id: stepper_normal_speed
    type: float
    initial_value: '500'

  - id: my_stepper_global # Integer for storing the stepper position in case of reboot
    type: int
    restore_value: True
    initial_value: '0'

  - id: openclosed # Boolean to store OPEN/CLOSED state
    type: bool
    restore_value: True
    initial_value: '0'

  - id: endstop # Variable for storing ENDSTOP (how far to move stepper)
    type: int
    restore_value: True
    initial_value: '10000'

  - id: settingmode # Integer for Setup Mode
    type: int
    restore_value: False
    initial_value: '0'

sensor:
  - platform: wifi_signal # Reports the WiFi signal strength in %
    name: "$slug signal"
    id: wifi_signal_percent
    update_interval: 60s
    filters:
        - lambda: return min(max(2 * (x + 100.0), 0.0), 100.0);
        - heartbeat: 30s
    unit_of_measurement: " %"
    entity_category: diagnostic

binary_sensor:
  - platform: gpio
    pin:
      number: GPIO04 # HALL Sensor # GPIO20 for seeed studio XIAO-ESP32-C3
      mode: INPUT_PULLUP
      inverted: True
    use_interrupt: True
    interrupt_type: ANY
    name: HALL_sensor
    id: hall_sensor
    internal: False
    on_press:
      then:
        - logger.log:
            format: "Hall Switch Triggered"
            level: ERROR
        - lambda: 'id(blinded).publish_state("0");'
        - stepper.report_position:
            id: my_stepper
            position: 0
        - if:
            condition:
              - lambda: 'return id(settingmode) == 2;'
            then:
              - script.execute: setupbutton
              

switch:
  - platform: template
    name: Roller Blind Setup Switch # Switch to enter Setup Mode
    id: setupswitch
    lambda: |-
      if (id(settingmode) != 0) {
        return true;
      } else {
        return false;
      }
    turn_on_action:
      then:
        - logger.log: "Entered Settings Mode"
        - globals.set:
            id: settingmode
            value:  '1'
    turn_off_action:
      then:
        - logger.log: "Exiting Settings Mode"
        - globals.set:
            id: settingmode
            value:  '0'
  - platform: template
    name: Roller Blind Setup Button # Switch to replicate the Physical Button
    id: hasetup
    turn_on_action:
      - if: # If settings variable is on
          condition:
            - lambda: 'return id(settingmode) != 0;'
          then: # Enter Setting Mode
            - output.turn_on : setupLED
            - script.execute: setupbutton
            - switch.turn_off: hasetup

cover:
  - platform: template
    name: ${slug}
    id: blinded
    open_action:
      then:
        - logger.log: "Opening"
        - stepper.set_target: # Send stepper to endstop
            id: my_stepper
            target: !lambda return id(endstop);
        - while:
            condition:
              lambda: 'return id(my_stepper).current_position != id(endstop);'
            then:
              - cover.template.publish:
                  id: blinded
                  position: !lambda 'return (float(float(id(my_stepper).current_position) / float(id(endstop))));'
                  current_operation: OPENING
              - delay: 1000 ms
        - globals.set: # Set global to current position
            id: my_stepper_global
            value: !lambda return id(my_stepper).current_position;
        - globals.set: # Set toggle to OPEN (No need for 'optimistic mode')
            id: openclosed
            value: '1'
        - cover.template.publish:
            id: blinded
            state: OPEN
            current_operation: IDLE
    close_action:
      then:
        - logger.log: "Closing"
        - stepper.set_target: # Send stepper to 0
            id: my_stepper
            target: '0'
        - while:
            condition:
              lambda: 'return id(my_stepper).current_position != 0;'
            then:
              - cover.template.publish:
                  id: blinded
                  position: !lambda 'return (float(float(id(my_stepper).current_position) / float(id(endstop))));'
                  current_operation: CLOSING
              - delay: 1000 ms
        - globals.set: # Set global to current position
            id: my_stepper_global
            value: !lambda return id(my_stepper).current_position;
        - globals.set: # Set toggle to CLOSED (No need for 'optimistic mode')
            id: openclosed
            value: '0'
        - cover.template.publish:
            id: blinded
            state: CLOSED
            current_operation: IDLE
    position_action:
      then:
        - stepper.set_target:
            id: my_stepper
            target: !lambda return int(id(endstop) * pos);
        - while:
            condition:
              lambda: 'return id(my_stepper).current_position != int(id(endstop) * pos);'
            then:
              - cover.template.publish:
                  id: blinded
                  position: !lambda 'return (float(float(id(my_stepper).current_position) / float(id(endstop))));'
              - delay: 1000 ms
        - globals.set: # Set global to current position
            id: my_stepper_global
            value: !lambda return id(my_stepper).current_position;
        - cover.template.publish:
            id: blinded
            position: !lambda 'return (float(float(id(my_stepper).current_position) / float(id(endstop))));'
            current_operation: IDLE
    stop_action:
      then:
        - stepper.set_target:
            id: my_stepper
            target: !lambda return id(my_stepper).current_position;
        - globals.set: # Set global to current position
            id: my_stepper_global
            value: !lambda return id(my_stepper).current_position;
        - cover.template.publish:
            id: blinded
            position: !lambda 'return (float(float(id(my_stepper).current_position) / float(id(endstop))));'
            current_operation: IDLE
    has_position: true
    device_class: blind

script:
  - id: setupbutton
    then:
      - if:
          condition:
            - lambda: 'return (id(settingmode) == 3);'
          then:
            - output.turn_off : setupLED
            - logger.log: "-= Setup Button: Mode 3 =- (end setup)"
            - stepper.set_target: # Set Stepper position
                id: my_stepper
                target: !lambda return id(my_stepper).current_position;
            - globals.set: # Set Endstop Variable
                id: endstop
                value: !lambda return id(my_stepper).current_position;
            - globals.set: # Set Global stepper position
                id: my_stepper_global
                value: !lambda return id(my_stepper).current_position;
            - globals.set: # Reset Setting Mode
                id: settingmode
                value:  '0'
            - globals.set: # Set toggle to Open
                id: openclosed
                value: '1'
            - cover.template.publish:
                id: blinded
                state: OPEN
                current_operation: IDLE
            - logger.log: "Exiting Setting Mode"
            - stepper.set_speed:
                id: my_stepper
                speed: !lambda 'return id(stepper_normal_speed);'
      - if:
          condition:
            - lambda: 'return (id(settingmode) == 2);'
          then:
            - logger.log: "-= Setup Button: Mode 2 =-"
            - stepper.set_speed:
                id: my_stepper
                speed: !lambda 'return id(stepper_slow_speed);'
            - stepper.report_position: # Reset Stepper position to 0
                id: my_stepper
                position: '0'
            - stepper.set_target: # Reset Stepper position to 0
                id: my_stepper
                target: '0'
            - globals.set: # Move stepper to 0 (doesn't move it's already there!)
                id: my_stepper_global
                value: '0'
            - delay: 100ms
            - stepper.set_target: # Reset Stepper position to 72000
                id: my_stepper
                target: '72000'
            - globals.set: # Advance setup to next mode
                id: settingmode
                value:  '3'
      - if:
          condition:
            - lambda: 'return (id(settingmode) == 1);'
          then:
            - logger.log: "-= Setup mode: Mode 1 =-"
            - stepper.report_position: # Set Stepper position to 72000, makes it move to 0 (Closed)
                id: my_stepper
                position: '72000'
            - globals.set: # Advance setup to next mode
                id: settingmode
                value:  '2'

button:
  - platform: restart
    id: button_restart
    name: "Restart"

  - platform: template
    name: "Reset to '0'"
    internal: False
    on_press: 
      - if:
          condition:
            binary_sensor.is_off: hall_sensor
          then:
            - stepper.report_position: # Set Stepper position to 72000, makes it move to 0 (Closed)
                id: my_stepper
                position: '72000'
            - globals.set: # Set toggle to CLOSED (No need for 'optimistic mode')
                id: openclosed
                value: '0'
            - cover.template.publish:
                id: blinded
                state: CLOSED

text_sensor:
  - platform: wifi_info
    ip_address:
      name: "IP Address"
      icon: mdi:ip-network
    ssid:
      name: "SSID"
      icon: mdi:wifi-settings
    bssid:
      name: "BSSID"
      icon: mdi:identifier
  - platform: version
    id: text_sensor_version
    name: "ESPHome Version"
    hide_timestamp: true