The secrets.yaml file contains the actual values for everything marked as !secret in the master yaml file. You don’t need it if you just replace all the !secret marks with their appropriate values. As for mqtt, if you’re not using one then I think you can not have those lines and rely on HA instead
So i got your setup build and now testing, calibration mode seems to be very hard to get to
esphome:
name: gardin_kontor
platform: ESP8266
board: nodemcuv2
esp8266_restore_from_flash: true
on_boot:
then:
- if:
condition:
# shade thinks it's at 0 but endstop is not triggered
lambda: "if ((id(stepper_position) == 0) && (id(endstop).state == 0)) { return 1; } else { return 0; }"
then:
# open to blind to calibrate the endstop, remember the steps to do so, put the blind back to where it was
- globals.set:
id: stepper_state
value: '1'
# set the assumed stepper position to stepper_steps + 4000 and have it travel to 0
- stepper.report_position:
id: my_stepper
position: !lambda "return id(stepper_steps) + 4000;"
- stepper.set_target:
id: my_stepper
target: 0
# wait until the stepper is no longer moving
- wait_until:
- lambda: "if (id(my_stepper).current_position == id(my_stepper).target_position) { return 1; } else { return 0; }"
# now move the blind back to however many steps it took to reach the endstop
- stepper.set_target:
id: my_stepper
target: !lambda "return id(stepper_steps) + 4000 - id(stepper_prevpos);"
- globals.set:
id: stepper_state
value: '1'
- delay: !lambda "return ((id(my_stepper).target_position - 834) * 2) + 3334;"
- globals.set:
id: stepper_position
value: !lambda "return id(my_stepper).target_position;"
- globals.set:
id: stepper_state
value: '0'
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
ap:
ssid: !secret ap_ssid
password: !secret ap_password
# Enable Home Assistant API
api:
captive_portal:
web_server:
port: 80
# Enable logging
logger:
ota:
globals:
- id: stepper_steps
type: int
initial_value: '0'
restore_value: true
- id: stepper_prevpos
type: float
initial_value: '0'
- id: stepper_position
type: float
initial_value: '0'
- id: stepper_state
type: int
initial_value: '0'
- id: calib_state
type: int
initial_value: '0'
sensor:
- platform: uptime
name: "Node uptime"
switch:
- platform: restart
name: "Restart"
binary_sensor:
- platform: gpio
pin:
number: D5
mode: INPUT_PULLUP
name: "Open"
filters:
- invert:
- delayed_on: 10ms
on_click:
- min_length: 50ms
max_length: 1000ms
then:
- lambda: |
if ((id(stepper_state) == 0) && (id(endstop).state == 0)) {
//shade is stopped and not already open at endstop
id(blind).open();
} else {
//shade is moving
id(blind).stop();
}
- platform: gpio
pin:
number: D6
mode: INPUT_PULLUP
name: "Close"
filters:
- invert:
- delayed_on: 10ms
on_click:
- min_length: 50ms
max_length: 1000ms
then:
- lambda: |
if ((id(stepper_state) == 0) && (id(stepper_position) < id(stepper_steps))) {
//shade is stopped and not already closed
id(blind).close();
} else {
//shade is moving
id(blind).stop();
}
# a 10s press on the down button will enter calibration mode
# shutter goes all the way up and then travels down until a button is pressed
# where shutter is stopped is the new stepper_steps value
- min_length: 5000ms
max_length: 15000ms
then:
- lambda: |
id(calib_state) = 1;
if ((id(stepper_state) == 0) && (id(endstop).state == 0)) {
//shade is stopped and not already open at endstop
id(blind).open();
} else if ((id(stepper_state) == 0) && (id(endstop).state == 1)) {
//shade is stopped and already open at endstop
id(blind).close();
}
- platform: gpio
pin:
number: D7
mode: INPUT_PULLUP
name: "Endstop"
id: endstop
filters:
- invert:
on_press:
then:
# hit the endstop, store the current steps value and reset stepper position to 0
- globals.set:
id: stepper_prevpos
value: !lambda "return id(my_stepper).current_position;"
- stepper.report_position:
id: my_stepper
position: 0
# just in case, also set the target to 0
- stepper.set_target:
id: my_stepper
target: 0
- lambda: |
if (id(stepper_state) == 1) {
//shade is moving
id(blind).stop();
}
cover:
- platform: template
name: "Gardin Kontor"
id: blind
open_action:
- globals.set:
id: stepper_state
value: '1'
# overshoot past the endstop to make sure it's hit (endstop will stop blind)
- stepper.set_target:
id: my_stepper
target: -4000
# wait until the stepper is no longer moving
- wait_until:
- lambda: "if (id(my_stepper).current_position == id(my_stepper).target_position) { return 1; } else { return 0; }"
- globals.set:
id: stepper_position
value: !lambda "return id(my_stepper).target_position;"
- globals.set:
id: stepper_state
value: '0'
- if:
condition:
# reached past the 'top' but still haven't triggered the endstop yet
lambda: "if(id(stepper_position) == -4000 && id(endstop).state == 0) { return 1; } else { return 0; }"
then:
# reset stepper position to 0
- stepper.report_position:
id: my_stepper
position: 0
# just in case, also set the target to 0
- stepper.set_target:
id: my_stepper
target: 0
close_action:
- globals.set:
id: stepper_state
value: '1'
- stepper.set_target:
id: my_stepper
# for calibration the number of steps is doubled
target: !lambda "if(id(calib_state) == 0) { return id(stepper_steps); } else { return id(stepper_steps) * 2; }"
# wait until the stepper is no longer moving
- wait_until:
- lambda: "if (id(my_stepper).current_position == id(my_stepper).target_position) { return 1; } else { return 0; }"
- globals.set:
id: stepper_position
value: !lambda "return id(my_stepper).target_position;"
- globals.set:
id: stepper_state
value: '0'
- lambda: |
if (id(calib_state) == 1) {
//reached double the current stepper distance
id(stepper_steps) = id(stepper_position);
//continue with calibration
id(blind).close();
}
stop_action:
- stepper.set_target:
id: my_stepper
target: !lambda "return id(my_stepper).current_position;"
- globals.set:
id: stepper_position
value: !lambda "return id(my_stepper).current_position;"
- globals.set:
id: stepper_state
value: '0'
- lambda: |
if (id(calib_state) == 1 && id(endstop).state == 0) {
//stopping at something other than the endstop
id(calib_state) = 0;
id(stepper_steps) = id(stepper_position);
}
- cover.template.publish:
id: blind
position: !lambda "return 1 - (id(stepper_position) / id(stepper_steps));"
position_action:
- stepper.set_target:
id: my_stepper
target: !lambda "return id(stepper_steps) * (1 - pos);"
optimistic: true
assumed_state: true
has_position: true
stepper:
- platform: a4988
id: my_stepper
dir_pin: D3
step_pin: D2
max_speed: 500 steps/s
# Optional:
sleep_pin: D1
acceleration: 300
deceleration: 300
the switch’s do register correct in the small web portal ie up/down and endstop
but holding the down for 10 sec with the endstop activated does nothing
If anyone is interested, I’ve had this setup working reliably for two months or so.
esphome:
name: projector_vs_screen
platform: ESP32
board: nodemcu-32s
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_pass
# manual_ip:
# static_ip: 192.168.107.22
# gateway: 192.168.107.1
# subnet: 255.255.255.0
ap:
ssid: "Projector Screen Basement (VS)"
password: "XXXX"
captive_portal:
api:
reboot_timeout: 60s
ota:
logger:
output:
- platform: gpio
id: down
pin: GPIO12
- platform: gpio
id: up
pin: GPIO14
- platform: ledc
id: pwm
pin: GPIO27
switch: #drive screen up or down at full speed, when so desired
- platform: output
name: "Lower"
id: down_1
output: down
on_turn_on:
then:
- output.turn_on: pwm
- platform: output
name: "Raise"
id: up_1
output: up
on_turn_on:
then:
- output.turn_on: pwm
- platform: restart
name: "Reboot Projector Node"
sensor: #encoder
- platform: rotary_encoder
name: "projector encoder"
pin_a:
number: GPIO5
mode: INPUT_PULLDOWN
pin_b:
number: GPIO17
mode: INPUT_PULLDOWN
id: step_counter
internal: true #having it not show up in the HA GUI seems to help with capture accuracy
resolution: 1
cover: #screen motor control
- platform: template
name: "Projector"
id: projector_screen
optimistic: true
open_action:
- output.turn_off: down
- output.turn_on: pwm
- output.set_level:
id: pwm
level: "80%"
- output.turn_on: up
- wait_until:
lambda: 'return id(step_counter).state >= 0;' #rolled up
- switch.turn_off: up_1
- delay: 1s
- sensor.rotary_encoder.set_value:
id: step_counter
value: 0
close_action:
- output.turn_off: up
- output.turn_on: pwm
- output.set_level:
id: pwm
level: "50%"
- output.turn_on: down
- wait_until:
lambda: 'return id(step_counter).state <= -8000;'
- switch.turn_off: down_1
- delay: 1s
- sensor.rotary_encoder.set_value:
id: step_counter
value: -8000 #fully unrolled
stop_action:
- output.turn_off: pwm
- output.turn_off: down
- output.turn_off: up
It’s driving a 100inch (diagonal) projector screen, which is clearly a lot heavier than your typical shade, so your motor wouldn’t need the level of gearing mine has.
Basically, I have a 24v DC motor with a worm gear box and shaft encoder connected to one end of the projector. Using an esp32 and a VNH5019 motor driver (which is more powerful than I need, but it was the only 24v capable driver I had on hand), I setup a cover to open/close (i.e.,roll down/roll up) the screen. In my case, 0 is ‘rolled up’ and -8000 is rolled down. The encoder on the shaft tracks these numbers (my cover does reset the encoder’s count to be exactly the 0 or -8000, cause it does over/undershoot by a few steps, but not to the degree where it is visibly noticeable).
LEDC is a PWM implementation specific to the esp32: I don’t believe you can use it with an 8266, but esphome has a comparable PWM component for 8266. LEDC basically let’s be manipulate the PWM frequency and/or duty cycle. In my case, you can see me changing the duty cycle in the cover actions (e.g., level: “50%” is a 50% duty cycle). I found that, by reducing the duty cycle (which you can kind of think of like reducing the “power”), I get a more accurate reading from the encoder. Obviously I give a high duty cycle when the motor is trying to roll the project screen back up vs. lowering it (80% vs. 50%).
Like I said, it works consistently. I’m still experimenting with getting a proper PID in place, but in the meantime…
Items:
- Motor with Shaft Encoder: https://www.amazon.com/gp/product/B0788B73HP/ref=ppx_yo_dt_b_search_asin_title?ie=UTF8&psc=1
- Motor Driver: https://www.amazon.com/gp/product/B081YRYWMV/ref=ppx_yo_dt_b_search_asin_title?ie=UTF8&psc=1
- Esp32: https://www.amazon.com/HiLetgo-ESP-WROOM-32-Development-Microcontroller-Integrated/dp/B0718T232Z/ref=sr_1_1_sspa?dchild=1&keywords=NodeMCU+esp32&qid=1609434148&sr=8-1-spons&psc=1&spLa=ZW5jcnlwdGVkUXVhbGlmaWVyPUEzVkdSMTE3TUNENExJJmVuY3J5cHRlZElkPUEwMTk4NTc1Mk5NUFBJQk5GNzJYRCZlbmNyeXB0ZWRBZElkPUEwMjA4NDMxMk8zUVNWVlRSVEExTiZ3aWRnZXROYW1lPXNwX2F0ZiZhY3Rpb249Y2xpY2tSZWRpcmVjdCZkb05vdExvZ0NsaWNrPXRydWU=
- lambda: |
id(calib_state) = 1;
if ((id(stepper_state) == 0) && (id(endstop).state == 0)) {
//shade is stopped and not already open at endstop
id(blind).open();
} else if ((id(stepper_state) == 0) && (id(endstop).state == 1)) {
//shade is stopped and already open at endstop
id(blind).close();
}
when i start calibration with endstop active nothing happens
with the endstop not active it does go up but it never goes down to do the actual calibration
Calibration Mode: With the blind at full open, press and hold the Down (close) button for 10s. The blind will start going Down (close) until a button is pressed.
that is not the case, at full open the endstop would be active, and if it is and i enter calibration mode it will not go anywhere
I’m having the same issue, did you manage to find any resolution?
i found this github repro: https://github.com/RoadkillUK/Motor-on-a-Roller-Blind-for-ESPHOME
i combined the tesblind.yaml and common_blind.yaml to one file and came up with this:
# 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)
esphome:
name: gardin_kontor
platform: ESP8266
board: nodemcuv2
esp8266_restore_from_flash: True
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
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
ap:
ssid: Roller Blind
password: !secret ap_password
web_server:
port: 80
logger:
api:
ota:
captive_portal:
stepper:
- platform: a4988
id: my_stepper
dir_pin:
number: D3
inverted: True
step_pin: D2
sleep_pin: D1
max_speed: 500 steps/s # Set the speed of the motor
acceleration: 300
deceleration: 300
globals:
- 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: '1000'
- id: settingmode # Integer for Setup Mode
type: int
restore_value: no
initial_value: '0'
binary_sensor:
- platform: gpio
pin:
number: D6 # Connect Button to D6 and GND
mode: INPUT_PULLUP
inverted: True
name: Button
internal: True
on_click:
- min_length: 50ms
max_length: 500ms
then: # Short press to OPEN/CLOSE blinds and also for setting up
- if: # If settings variable is on
condition:
- lambda: 'return id(settingmode) != 0;'
then: # Enter Setting Mode
- script.execute: setupbutton
else:
- if: # If blind is closed
condition:
- lambda: 'return id(openclosed) == 0;'
then: # Open blind
- cover.open: blinded
else: # Close blind
- cover.close: blinded
- min_length: 1000ms
max_length: 3000ms
then: # Long press to Enter Setting Mode
- logger.log: "Entered Settings Mode"
- globals.set:
id: settingmode
value: '1'
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
- script.execute: setupbutton
- switch.turn_off: hasetup
cover:
- platform: template
name: "Gardin Kontor"
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:
- logger.log: "Pressed Setup Button: Mode 3"
- 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"
- if:
condition:
- lambda: 'return (id(settingmode) == 2);'
then:
- logger.log: "Pressed Setup Button: Mode 2"
- 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'
- 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: "Pressed Setup Button: 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'
the thing that is lost is that it goes up to endstop on boot, but the calibration works
and there is only one hardware button. So in the end not a great stepback considered i have tested it
the only thing to test is whatever one or 2 stepper motors are needed.
the printed parts i found on thingiverse but cant remember what thing.
EDIT: https://www.thingiverse.com/thing:2065722 was the one, i dont use the box thou.
and the part that goes in to the roller blind has to be adjusted to fit. if only one stepper is needed i also made a “dummy” version that just has a 608 bearing in it. in that way its the same in both ends. looks symetric. i then designed a bracket that can holde the stepper. the bracket is 2 parts where one parts slides in sideways. that makes it easy to the the whole blind down when cleaning
Glad I could help
yeah, i’m no good at programming but i managed to melt the 2 files together
i thought for a long time that it would be a shame to loose the endstop but not when you think long enough. and it makes the wiring more simple
If you put the 2 files in esphome directory, you only have to change/copy the testblinds.yaml to add more blinds (seperate esp’s)
I’m not the best programmer, I forget more than I learn
I don’t actually use any physical buttons on the blind itself, I just use the webpage instead. It actually works quite well as I use it on 4 blinds and one of them got stuck last week and lost it’s calibration, sorted in no time at all.
The repo you found is actually further up in this post
Im using this print I put together from others I found. I use it to pull my heave blinds.
Is everyone having the same jittering when using stepper motors? like it stops for a split second after every turn?
What sketch/repo are you using?
I think you’ll find it’s this line (and similar ones further down) that cause the delay as the blind reports it’s position, I’m not sure there’s a way around it (or I’ve not really looked that hard )
I’ve ordered all my supplies to start building my own blinds now. Cant wait to get started. I have windows side by side at each location which require 2 blinds. Does anyone know if i can use a single esp board to control them individually or will i need to have 2?
What have you ordered for motor and steering?
I’ve ordered 12v 28BYJ-48 and a ULN2003
And that together with the thingiverse? Or?
Let us know the result!