Using substitutions in a lamda function for a stepper motor control

I am trying to configure a stepper motor to run a certain number of steps determined by the value of an input_number.

But I also would like the input_number entity to be configurable with different entities in different configurations via substitutions.

here is the binary_sensor config in the common_nodemcu_stepper_motor.yaml:

binary_sensor:
  - platform: homeassistant
    #name: "Input Boolean From Home Assistant"
    id: tank_feed_start
    entity_id: ${cycle_start}
    on_press:
      then:
        - stepper.report_position:
            id: ${id}
            position: 0
        - stepper.set_target:
            id: ${id}
            target: !lambda |-
              return (${cycle_steps}.state);

and here is the substitution:

substitutions:
  name: tank_feeder
  ip: 192.168.1.85
  ap: tank_feeder_ap
  friendly_name: Tank Feeder
  id: tank_feeder
  cycle_start: input_boolean.tank_feed_cycle_start
  cycle_steps: input_number.tank_feeder_step_counts # '150'
<<: !include common_nodemcu_stepper_motor.yaml

Any hints?

Have you verified the code without substitution first? cycle_start and cycle_steps shouldn’t include the device class (the period and the text before the period), for example:

  cycle_start: tank_feed_cycle_start
  cycle_steps: tank_feeder_step_counts # '150'

Because when you refer to a device in the lambda, it should be:

id(tank_feed_cycle_start)

so your lambda would be

return id(tank_feed_cycle_start).state;

or, with substitutions:

id($cycle_start)

and

return id($cycle_start).state;

I did a validation and everything checked ok.

But when it was compiling it threw an error:

I’ll see if i can decipher your suggestion :wink: and make changes.

Making the changes I suggested should resolve that.

binary_sensor:
  - platform: homeassistant
    #name: "Input Boolean From Home Assistant"
    id: tank_feed_start
    entity_id: ${cycle_start}
    on_press:
      then:
        - stepper.report_position:
            id: ${id}
            position: 0
        - stepper.set_target:
            id: ${id}
            target: !lambda |-
              return (id(${cycle_steps}).state);

substitutions:
  name: tank_feeder
  ip: 192.168.1.85
  ap: tank_feeder_ap
  friendly_name: Tank Feeder
  id: tank_feeder
  cycle_start: tank_feed_cycle_start
  cycle_steps: tank_feeder_step_counts # '150'
<<: !include common_nodemcu_stepper_motor.yaml

How does esphome know which domain the entities are in HA? Does that have to be defined somewhere?

And thanks for your help on this.

and I just tried it with your code and it doesn’t validate:

You define the domain in your yaml. In your code you already posted you have defined that “tank_feed_start” is the ID for a binary_sensor device. ID’s are required to be unique. Elsewhere in your code I have assumed you’ve already defined what domain tank_feed_cycle_start and tank_feeder_step_counts are in.

Regarding being unique: In your code, try to create a duplicate ID by adding the code below and see what happens – you should get an error complaining about the ID being reused:

button:
  - platform: template
    name: Test Button
    id: tank_feed_start

Maybe I should just post the whole common file so we are on the same page:

esphome:
  name: ${name}
  platform: ESP8266
  board: esp01_1m
  board_flash_mode: dout

wifi:
  #hostname: ${name}
  networks:
    - ssid: "secret"
      password: !secret wifi_pwd
    - ssid: "secret too"
      password: !secret wifi_pwd
  reboot_timeout: 0s
  #fast_connect: true
  manual_ip:
    static_ip: ${ip}
    gateway: 192.168.1.1
    subnet: 255.255.255.0
  ap: 
    ssid: ${ap}
    
# Enable logging
logger:

# Enable Home Assistant API
api:
  #password: !secret esphome_api_pwd
  reboot_timeout: 0s
  
  services:
    - service: control_stepper
      variables:
        target: int
      then:
        - stepper.set_target:
            id: ${id} # tank_feeder
            target: !lambda 'return target;'

ota:

stepper:
  - platform: uln2003
    id: ${id}  #tank_feeder
    pin_a: GPIO0 #D3
    pin_b: GPIO2 #D4
    pin_c: GPIO4 #D2
    pin_d: GPIO5 #D1
    max_speed: 100 steps/s

    # Optional:
    acceleration: inf
    deceleration: inf
    
binary_sensor:
  - platform: homeassistant
    #name: "Input Boolean From Home Assistant"
    id: tank_feed_start
    entity_id: ${cycle_start}
    on_press:
      then:
        - stepper.report_position:
            id: ${id}
            position: 0
        - stepper.set_target:
            id: ${id}
            target: !lambda |-
              return (id(${cycle_steps}).state);
     
switch:
  - platform: restart
    name: ${friendly_name} Restart
    
text_sensor:
  - platform: version
    name: ${friendly_name} ESPHome Version
    
sensor:
  - platform: wifi_signal
    name: ${friendly_name} WiFi Signal Strength
    update_interval: 60s

and the substitutions file:

substitutions:
  name: tank_feeder
  ip: 192.168.1.85
  ap: tank_feeder_ap
  friendly_name: Tank Feeder
  id: tank_feeder
  cycle_start: input_boolean.tank_feed_cycle_start
  # cycle_steps: input_number.tank_feeder_step_counts # '150'
  #cycle_start: tank_feed_cycle_start
  cycle_steps: tank_feeder_step_counts # '150'
<<: !include common_nodemcu_stepper_motor.yaml

and reading your post above I may be misunderstanding something else.

I want to set the start bit and the number of counts in HA. So in HA I created an input_boolean for the start cycle (input_boolean.tank_feed_cycle_start) and an input_number for the counts (input_number.tank_feeder_step_counts).

then I called those entities in my substitutions file and referenced them in the binary_sensor in the ESPHome yaml.

so cycle_start in ESPHome should equal the HA entity “input_boolean.tank_feed_cycle_start”

and cycle_steps in ESPHome should equal the HA entity “input_number.tank_feeder_step_counts”

is that not the correct way of doing that?

Sorry, I’m not understanding. The ESPHome docs are fairly vague.

OK I see what you are trying to do. You have to create a sensor within your ESP device that will take its state from an existing entity in Home Assistant. You can’t simply reference a home assistant entity anywhere in your ESP’s yaml code.

Home Assistant Sensor

So if you have created an input number entity in Home Assistant called input_number.tank_feed_cycle_start and you want your ESP to know about it, you have to have this in your code:

sensor:
  - platform: homeassistant
    id: HA_tank_feed_start_input
    entity_id: input_boolean.tank_feed_cycle_start

Then elsewhere in your yaml code when you want to refer to it, you’ll use the ID that was defined there: HA_tank_feed_start_input

Ok I think I get it now.

Would an input_boolean be a sensor or a binary_sensor?

I think the input_boolean should be a binary sensor and the input_number should be a sensor. Is that correct?

then I can use the id’s I create there in the stepper motor control?

Validation does not check lambdas.

That would explain that then.

So far I got the code to compile and flashed but I don’t have all of the hardware wired up yet. as soon as I do and it works as expected I’ll post the results here along with the full working code.

I think I’ll change the title to better reflect the full project instead of just the substitution aspect.

yes to everything, sounds like you’ve got it

1 Like

it worked!

here is the full working code.

nodemcu_tank_feeder.yaml (for substitutions):

substitutions:
  name: tank_feeder
  ip: 192.168.1.85
  ap: tank_feeder_ap
  friendly_name: Tank Feeder
  id: tank_feeder
  cycle_start: input_boolean.tank_feed_cycle_start
  cycle_steps: input_number.tank_feeder_step_counts
<<: !include common_nodemcu_stepper_motor.yaml

common_nodemcu_stepper_motor.yaml:

esphome:
  name: ${name}
  platform: ESP8266
  board: esp01_1m
  board_flash_mode: dout

wifi:
  networks:
    - ssid: "secret"
      password: !secret wifi_pwd
    - ssid: "secret too"
      password: !secret wifi_pwd
  reboot_timeout: 0s
  manual_ip:
    static_ip: ${ip}
    gateway: 192.168.1.1
    subnet: 255.255.255.0
  ap: 
    ssid: ${ap}
    
# Enable logging
logger:

# Enable Home Assistant API
api:
  reboot_timeout: 0s
  
  services:
    - service: control_stepper
      variables:
        target: int
      then:
        - stepper.set_target:
            id: ${id} # tank_feeder
            target: !lambda 'return target;'

ota:

# 2048 steps per revolution of output shaft in full step mode
# 4096 steps per revolution of output shaft in half step mode

stepper:
  - platform: uln2003
    id: ${id}  #tank_feeder
    pin_a: GPIO0 #D3
    pin_b: GPIO2 #D4
    pin_c: GPIO4 #D2
    pin_d: GPIO5 #D1
    max_speed: 100 steps/s

    # Optional:
    acceleration: inf
    deceleration: inf
    
binary_sensor:
  - platform: homeassistant
    id: tank_feed_start
    entity_id: ${cycle_start}
    on_press:
      then:
        - stepper.report_position:
            id: ${id}
            position: 0
        - stepper.set_target:
            id: ${id}
            target: !lambda |-
              return (id(feed_cycle_steps).state);
    
switch:
  - platform: restart
    name: ${friendly_name} Restart
    
text_sensor:
  - platform: version
    name: ${friendly_name} ESPHome Version
    
sensor:
  - platform: homeassistant
    id: feed_cycle_steps
    entity_id: ${cycle_steps}

  - platform: wifi_signal
    name: ${friendly_name} WiFi Signal Strength
    update_interval: 60s

Now I’m going to use it to drive sweeper that will move a certain number of steps once a day that will discharge fish food into my aquarium for use when we go on vacation, etc.

Thanks again for your help.

1 Like

Hello, thank you for this efforts and for sharing here your solution. I think it might be very helpful for the issue I’m currently facing in setting up a stepper motor based fertilizers dosing system, but I simply have no idea of what “substitutions” are, and this sensor inside a ESPHome device is something I’m also unfamiliar with. Maybe someone here can give me an hand?

Here is the code I currently have for my ESPHome device:

esphome:
  name: stepper1

esp8266:
  board: d1_mini

# Enable logging
logger:

# Enable Home Assistant API
api:
  encryption:
    key: "xxxxxxxxxxxxxxxxxxxxxxxxxxxx"
  services:
    - service: control_stepper
      variables:
        target: int
      then:
        - logger.log: "Speed updated"
        - stepper.set_target:
            id: stepper1
            target: !lambda 'return target;'

stepper:
  - platform: uln2003
    id: stepper1
    sleep_when_done: true
    pin_a: D1
    pin_b: D2
    pin_c: D3
    pin_d: D4
    max_speed: 250 steps/s 
    
ota:
  password: "xxxxxxxxxxxxxxxxxxxxxx"

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

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

captive_portal:

My configuration.yaml code for this:

# STEPPERS
input_number: 
  stepper_control:
    name: Stepper Control
    initial: 0
    min: -1000
    max: 1000
    step: 1
    mode: slider

And my automations.yaml file got this:

- id: '1661897634736'
  alias: Control Stepper
  trigger:
    platform: state
    entity_id: input_number.stepper_control
  action:
  - service: esphome.stepper1
    data_template:
        target: "{{ trigger.to_state.state | int }}"

The current look of my dashboard:
Untitled

My final objective is to be able to control the amount (and direction) of steps I need a motor to perform, through an input number (instead of a finite and fiddly slider).
Also, it would be great to have a simple control with a start/stop button for non-automatized purposes.
From this, I think I have what I need to create the automatizations and expansions I will need for the whole project.
Thank you in advance for putting your time on this.