I also used a 12V/2A power supply with a A4988 driver and NEMA 17 but it failed to lift the roller blind. With a luggage scale I measured that about 2.1/2.2kg is needed to lift the roller blind and I assume the NEMA should be able to do this? I put a custom made wheel directly on the NEMA which is driving the robe of the roller blind. Should this work without additional gears you think?
Depending on your blind and speed, maybe.
I needed the gearbox for my blind as the NEMA wasn’t able to lift the blind without it…
I’m trying this github project;
When i flash the NEMA_ROLLER_BLIND.ino to my WEMOS D1 Mini the AP is working but after connecting to my own wifi reconnecting al the time (looks like reboot).
When i flash the motor_on_a_roller_blind-ws_060918_Working.ino nothing works…
This project looks exact what i need, anyone the setup with the NEMA17 and L298N H Bridge?
Hello. I was trying to use this code, together with this example from the doc:
But I get an error from Cover Template:
position_action is an invalid option [cover.template]
What is going on?Was this option removed?I also don’t see it in the ESPHome documentation, which really sucks…
You need the 1.15-dev version of ESPHome for that.
So everything works almost perfectly, here is my code if anybody wants it. It also keeps track of the current position, so when the power is out or anything unexpected happens, the stepper position will be reset to the saved one. I also have different speeds for closing/opening, since I can close the blinds faster that opening due to them being quite heavy. I also have a relay connected to my heating, but that is another point.
esphome:
name: kitchen_blind
platform: ESP8266
board: nodemcuv2
on_boot:
priority: -100
then:
- stepper.report_position:
id: blind_stepper
position: !lambda "return id(current_position);"
- stepper.set_target:
id: blind_stepper
target: !lambda "return id(current_position);"
- stepper.set_speed:
id: blind_stepper
speed: 500 steps/s
- cover.template.publish:
id: kitchen_blind
current_operation: IDLE
position: !lambda 'return (float(float(id(blind_stepper).current_position) / float(id(open_position))));'
- output.turn_on: builtin_led
esp8266_restore_from_flash: true
wifi:
ssid: ""
password: ""
manual_ip:
static_ip: 192.168.1.3
gateway: 192.168.1.1
subnet: 192.168.1.0
# Enable fallback hotspot (captive portal) in case wifi connection fails
ap:
ssid: "Kitchen Blind Fallback Hotspot"
password: !secret esphome_password
ap_timeout: 1min
reboot_timeout: 2min
use_address: 192.168.1.3
captive_portal:
# web_server:
# port: 80
# Enable logging
logger:
ota:
password: !secret esphome_password
# Enable Home Assistant API
api:
services:
- service: control_stepper
variables:
target: int
then:
- stepper.set_target:
id: blind_stepper
target: !lambda 'return target;'
- globals.set:
id: current_position
value: !lambda 'return target;'
- output.turn_on: builtin_led
- service: set_speed
variables:
speed: int
then:
- stepper.set_speed:
id: blind_stepper
speed: !lambda 'return speed;'
- service: middle
then:
- stepper.set_speed:
id: blind_stepper
speed: !lambda |-
if (id(middle_position) >= id(blind_stepper).current_position) {
return 500;
} else {
return 800;
}
- stepper.set_target:
id: blind_stepper
target: !lambda 'return id(middle_position);'
- while:
condition:
lambda: |-
return id(blind_stepper).current_position != id(middle_position);
then:
- cover.template.publish:
id: kitchen_blind
current_operation: !lambda |-
if(id(middle_position) >= id(blind_stepper).current_position) {
return COVER_OPERATION_OPENING;
} else {
return COVER_OPERATION_CLOSING;
}
position: !lambda 'return (float(float(id(blind_stepper).current_position) / float(id(open_position))));'
- delay: 1000 ms
- cover.template.publish:
id: kitchen_blind
current_operation: IDLE
position: !lambda 'return (float(float(id(blind_stepper).current_position) / float(id(open_position))));'
- globals.set:
id: current_position
value: !lambda 'return id(middle_position);'
- output.turn_on: builtin_led
globals:
- id: open_position
type: int
initial_value: '9400'
- id: middle_position
type: int
initial_value: '3760'
- id: current_position
type: int
initial_value: '9400'
restore_value: true
output:
- platform: gpio
pin: GPIO2
id: builtin_led
switch:
- platform: gpio
name: "Heating Relay"
pin:
number: D6
inverted: yes
restore_mode: ALWAYS_OFF
stepper:
- platform: a4988
id: blind_stepper
step_pin: D3
dir_pin: D4
max_speed: 800 steps/s
sleep_pin:
number: D2
inverted: yes
acceleration: inf
deceleration: inf
status_led:
pin: D1
cover:
- platform: template
device_class: shade
name: Kitchen Blind
id: kitchen_blind
open_action:
- stepper.set_speed:
id: blind_stepper
speed: 500 steps/s
- stepper.set_target:
id: blind_stepper
target: !lambda "return id(open_position);"
- while:
condition:
lambda: |-
return id(kitchen_blind).position != 1;
then:
- cover.template.publish:
id: kitchen_blind
current_operation: !lambda |-
return COVER_OPERATION_OPENING;
position: !lambda 'return (float(float(id(blind_stepper).current_position) / float(id(open_position))));'
- delay: 1000 ms
- cover.template.publish:
id: kitchen_blind
current_operation: IDLE
position: !lambda 'return 1;'
- globals.set:
id: current_position
value: !lambda 'return id(open_position);'
- output.turn_on: builtin_led
close_action:
- stepper.set_speed:
id: blind_stepper
speed: 800 steps/s
- stepper.set_target:
id: blind_stepper
target: 0
- while:
condition:
lambda: |-
return id(kitchen_blind).position != 0;
then:
- cover.template.publish:
id: kitchen_blind
current_operation: !lambda |-
return COVER_OPERATION_CLOSING;
position: !lambda 'return (float(float(id(blind_stepper).current_position) / float(id(open_position))));'
- delay: 1000 ms
- cover.template.publish:
id: kitchen_blind
current_operation: IDLE
position: !lambda 'return 0;'
- globals.set:
id: current_position
value: !lambda 'return 0;'
- output.turn_on: builtin_led
stop_action:
- stepper.set_target:
id: blind_stepper
target: !lambda return id(blind_stepper).current_position;
- cover.template.publish:
id: kitchen_blind
current_operation: IDLE
position: !lambda 'return (float(float(id(blind_stepper).current_position) / float(id(open_position))));'
- globals.set:
id: current_position
value: !lambda 'return id(blind_stepper).current_position;'
- output.turn_on: builtin_led
has_position: true
I have a problem though. I don’t know why, but it seems like the stepper goes a full rotation, and then pauses for a fraction of a second, and this cycle repeats until the stepper gets to the desired position. Not sure what stepper library it is used, but I am guessing that my loop function takes to long to execute maybe?Or it might be because I use the a4988 driver with the sleep function in ESPHome, but the sleep pin is actually connected to the enable pin of the driver. But this worked well with my custom code, I woke the driver before I need to change position, and after the stepper was in the desired position, the driver was disabled, not sure how ESPHome does it.
How long are the wires between your a4988 and the motor? I’m seeing similar issues, still figuring out of I can extend the wires between the a4988 and my 28byj-48.
Quite short, it worked fine without Esphome before, so it has to be something to do with this. Also, I think that when I first started testing with only the a4988 esp home integration, it worked fine, so I am guessing the esp8266 is too slow to handle everything.
First of all this project is awesome! I followed this guide here: Motorized WiFi IKEA Roller Blind : 11 Steps (with Pictures) - Instructables
But it ended up being to weak, so I got a nema 17 stepper but it is capped at 350mAH…
I would like to mount the nema directly to the window ceiling and put on a cap on the nema to be inserted directly into the tube. (I have cut the blind according to the link above…)
I see a lot of people are doing the gear box version, is that the only way to get this to work?
Im just wondering if I again need to order a new stepper motor or if that will work to pull a 180x180 cm blind(https://cdn-shop.adafruit.com/product-files/324/C140-A+datasheet.jpg)
I plan to use a DRV8825 and a wemos d1 mini.
Hi @JBS,
I am using DRV8825 drivers with NEMA 17HS4401 in ESPHome (although not officially supported.) The DRV8825 is almost plug and play with the A4988 - there is just a minor difference in connections:
-
EN(able) pin is an active low input - the DRV8825 is enabled by default. To disable, you can pull it high. I leave mine detached.
-
SL(ee)P pin is also an active low input - the DRV8825 is in sleep mode by default. I’m wiring this to ESPHome’s sleep_pin for the A4988
-
R(e)S(e)T is again an active low input - the DRV8825 will ignore signalling on the ST(e)P pin unless you pull it high. Now here’s the part I’m not sure about: this pin is also used to reset the driver to HOME, but seeing as there is no VDD on the DRV8825 as there is on the A4988, I’m attaching this to 3.3V from the ESP8266, so keeping it high. Anything from 2.5V to 5V should be okay.
So far, so good. When SLP is low, there doesn’t appear to be any power going to the motor. It appears to be fine to leave RST on 3.3V.
I haven’t played with the F(au)LT pin at all, which is in the place of the VDD on the A4988. Everything else is pretty much the same.
I wound up with a few NEMA 17HS4401’s and the DRV8825 gets pretty hot even after just 15-20 revolutions - heat sinks are a must! This may be because my power supply is not rated for the 1.7A 17HS4401’s though. I’m just starting out with steppers, so still learning.
The 1.7A NEMA 17HS4401 is pretty strong when it pulls, but freewheels without power. Probably good for a stiff blind that is sprung to remain in position once moved.
Anyone else experimented with ESPHome and the DRV8825? Basic config is:
stepper:
- platform: a4988
id: stepper_one
step_pin: D1
dir_pin: D2
max_speed: 200 steps/s
sleep_pin: D5
acceleration: inf
deceleration: inf
- platform: a4988
id: stepper_two
step_pin: D6
dir_pin: D7
max_speed: 200 steps/s
sleep_pin: D8
acceleration: inf
deceleration: inf
Kind regards, Dave.
@dhartsmith Thanks for your explanation
Got my NEMA 17 and A4988 also wokring with ESPHome. Will try it with the DRV8825 when it is arrived from China.
Also need to print some new gear/wheels with a better balance between torque and grip.
Hi all,
Great great topic. I am planing to do a DIY project based on the NEMA 17 + A4988, i.e. a 1-axis solar tracker based on magnetic compass measurements and the sun azithmut course among the day.
I just compiled some first example found here in this topic with a cover… but my motor don’t move at all.
Since this project is an outdoor one, I am powering the electronic with a 3S LiPo battery (up to 12.4V). A 5V stepdown regulator is used to power the ESP8266+ QMC5883 compass.
Anyone can confirm the exact pinout I have to follow ? From my current ESPHome model, the SLP pin is D2. No need to use the RST pin too ? or the ENABLE ?
Thanks in advance…
EDIT: I think my A4988 is smoked
To be more precise … Since nothing worked with the pin D2 connected to SLP. I tried then connect RST & SLP togather as depicted in many blogs. It worked from HA with the cover object. then;… I decided to connected also D2 to SLP with still SLP & RST connected… then no more mouvements :(… I tried everything. Recheck 100x time connexion :(. I am afraid the A4988 is gone. There is a way to test it wiithout oscilloscope ?
Hi all, I’ve been reading this thread for a while and I though I’d give the blinds a go. I originally used the Motor on a roller blind and have 4 blinds running on it, however, I do prefer ESPhome.
THIS IS NOT A DIRECT REPLACEMENT FOR THE SAME WIRING AS THE ‘Motor on a roller blind’, the next post is.
The following sketch is complete, other than the passwords. It has one button and can be set up in situ. Hold the button down to enter setup mode, press again to start moving to the bottom, press at the bottom and it starts moving up, press again at the top and the blind is calibrated.
Location and action are stored in memory and it will remember where it should be, even after power loss.
I’ve have done a little testing and so far, so good. It could probably do with an LED to display setup mode, but it’ll be fine.
I wrote this and didn’t think of the 4 blinds I already have that have NO button, but I’ll sort it out
This setup uses a Robotdyne ESP8266 Wifi D1 Mini and a 28BYJ-48 stepper motor + ULN2003 driver board.
# Press button for > 1 second to enter setup mode
# Press button again to start the blind closing
# Press button again when closed and blind starts to open (actually resets the stepper position to 0)
# Press button again when blind is fully open
# Job Done
# Button is also used to open/close the blind (must be fully open/closed first)
# NOTE: If you find that your shades are going the wrong way, you can change the pin
# settings below or reverse the + and – wires for each of the A and B motor
# pairs on your driver and the motor will spin in the opposite direction.
substitutions:
devicename: bathroomleft
upper_devicename: Bathroom Left Blind
mystepper: my_stepper
pina: D5
pinb: D1
pinc: D2
pind: D3
# Wrong direction? Comment the previous 4 lines and uncomment the following 4
# pina: D1
# pinb: D5
# pinc: D3
# pind: D2
esphome:
name: $devicename
platform: ESP8266
board: d1_mini
esp8266_restore_from_flash: True
on_boot:
- priority: -200.0
then:
- stepper.report_position: # Set stepper to global variable
id: $mystepper
position: !lambda return id(${mystepper}_global);
- stepper.set_target: # Set stepper to global variable
id: $mystepper
target: !lambda return id(${mystepper}_global);
wifi:
networks:
- ssid: !secret wifi
password: !secret wifi_password
- ssid: !secret wifi2
password: !secret wifi_password2
ap:
ssid: $upper_devicename
password: !secret wifi_password
web_server:
port: 80
logger:
api:
password: !secret api_password
ota:
password: !secret ota_password
captive_portal:
stepper:
- platform: uln2003
id: $mystepper
pin_a: $pina
pin_b: $pinb
pin_c: $pinc
pin_d: $pind
max_speed: 300 steps/s # Speed of the motor
sleep_when_done: True
acceleration: inf
deceleration: inf
globals:
- id: ${mystepper}_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 OPEN (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'
- id: settingup # Boolean for Setup Active
type: bool
restore_value: no
initial_value: '0'
binary_sensor:
- platform: gpio
pin:
number: D7
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
- script.execute: moveit
- min_length: 1000ms
max_length: 3000ms
then: # Long press to Enter Setting Mode
- script.execute: setting
cover:
- platform: template
name: $upper_devicename
lambda: |-
if (id(openclosed)) {
return COVER_OPEN;
} else {
return COVER_CLOSED;
}
open_action:
- script.execute: openscript
close_action:
- script.execute: closescript
device_class: blind
script:
- id: moveit
then:
- if: # If settings variable is on
condition:
- lambda: 'return id(settingup);'
then: # Enter Setting Mode
- script.execute: setupbutton
else:
- if: # If blind is closed
condition:
- lambda: 'return id(openclosed) == 0;'
then: # Open blind
- script.execute: openscript
else: # Close blind
- script.execute: closescript
- id: openscript
then:
- logger.log: "Opening"
- stepper.set_target: # Send stepper to endstop
id: $mystepper
target: !lambda return id(endstop);
- wait_until: # Wait until endstop reached
lambda: 'return (id($mystepper).current_position == id(endstop));'
- globals.set: # Set global to current position
id: ${mystepper}_global
value: !lambda return id($mystepper).current_position;
- globals.set: # Set toggle to OPEN (No need for 'optimistic mode')
id: openclosed
value: '1'
- id: closescript
then:
- logger.log: "Closing"
- stepper.set_target: # Send stepper to 0
id: $mystepper
target: '0'
- wait_until: # Wait until 0 reached
lambda: 'return (id($mystepper).current_position == 0);'
- globals.set: # Set global to current position
id: ${mystepper}_global
value: !lambda return id($mystepper).current_position;
- globals.set: # Set toggle to CLOSED (No need for 'optimistic mode')
id: openclosed
value: '0'
- id: setting
then:
- logger.log: "Entered Settings Mode"
- globals.set:
id: settingup
value: '1'
- globals.set:
id: settingmode
value: '1'
- 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: $mystepper
target: !lambda return id($mystepper).current_position;
- globals.set: # Set Endstop Variable
id: endstop
value: !lambda return id($mystepper).current_position;
- globals.set: # Set Global stepper position
id: ${mystepper}_global
value: !lambda return id($mystepper).current_position;
- globals.set: # Reset Setting Mode
id: settingmode
value: '0'
- globals.set: # Reset Setting Mode
id: settingup
value: '0'
- globals.set: # Set toggle to Open
id: openclosed
value: '1'
- 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: $mystepper
position: '0'
- stepper.set_target: # Reset Stepper position to 0
id: $mystepper
target: '0'
- globals.set: # Move stepper to 0 (doesn't move it's already there!)
id: ${mystepper}_global
value: '0'
- stepper.set_target: # Reset Stepper position to 72000
id: $mystepper
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: $mystepper
position: '72000'
- globals.set: # Advance setup to next mode
id: settingmode
value: '2'
This IS a working replacement for the ‘Motor on a roller blind’ sketch. I have it set up on my kitchen blind that has been running the original ‘moarb’ sketch for a year or so.
I couldn’t find a way of sending it OTA, maybe someone else knows a way.
To upload via WIFI, go to this page and follow the instructions, took less than one minute to upload and configure!
Here it is for your scrutinisation
Also, On GitHub
EDIT: Positioning now works.
# This sketch will add 2 switches named <upper_devicename> Setup Switch and Setup Button
# Use your mobile or tablet and connect to http://<devicename>.local to set up the blind
# Turn on the Setup Switch to enter Setup Mode and use the Setup Button as shown below to setup blinds.
# 1) Turn on the Setup Switch to enter setup mode
# 2) Press Setup button to start the blind closing
# 3) Press Setup button again when closed and blind starts to open (actually resets the stepper position to 0)
# 4) Press Setup button again when blind is fully open
# 5) Job Done
# This sketch also includes a momentary button on D7 which can be used in the following way
# 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)
# NOTE: If you find that your shades are going the wrong way, you can change the pin
# settings below or reverse the + and – wires for each of the A and B motor
# pairs on your driver and the motor will spin in the opposite direction.
# The following pins are for the original 'Motor on a Roller Blind'
substitutions:
devicename: kitchen_blind
upper_devicename: Kitchen Blind
mystepper: my_stepper
pina: D1
pinb: D3
pinc: D2
pind: D4
# Wrong direction? Comment the previous 4 lines and uncomment the following 4
# pina: D3
# pinb: D1
# pinc: D4
# pind: D2
esphome:
name: $devicename
platform: ESP8266
board: d1_mini
esp8266_restore_from_flash: True
on_boot:
- priority: -200.0
then:
- stepper.report_position: # Set stepper to global variable
id: $mystepper
position: !lambda return id(${mystepper}_global);
- stepper.set_target: # Set stepper to global variable
id: $mystepper
target: !lambda return id(${mystepper}_global);
wifi:
networks:
- ssid: !secret wifi
password: !secret wifi_password
ap:
ssid: $upper_devicename
password: !secret wifi_password
web_server:
port: 80
logger:
api:
password: !secret api_password
ota:
password: !secret ota_password
captive_portal:
stepper:
- platform: uln2003
id: $mystepper
pin_a: $pina
pin_b: $pinb
pin_c: $pinc
pin_d: $pind
max_speed: 600 steps/s # Speed of the motor
sleep_when_done: True
acceleration: inf
deceleration: inf
globals:
- id: ${mystepper}_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'
- id: settingup # Boolean for Setup Active
type: bool
restore_value: no
initial_value: '0'
binary_sensor:
- platform: gpio
pin:
number: D7 # Connect Button to D7 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
- script.execute: moveit
- min_length: 1000ms
max_length: 3000ms
then: # Long press to Enter Setting Mode
- script.execute: setting
switch:
- platform: template
name: ${upper_devicename} Setup Switch # Switch to enter Setup Mode
id: setupswitch
lambda: |-
if (id(settingup)) {
return true;
} else {
return false;
}
turn_on_action:
- script.execute: setting
turn_off_action:
- logger.log: "Exiting Settings Mode"
- globals.set:
id: settingup
value: '0'
- globals.set:
id: settingmode
value: '0'
- platform: template
name: ${upper_devicename} Setup Button # Switch to replicate the Physical Button
id: hasetup
turn_on_action:
- if: # If settings variable is on
condition:
- lambda: 'return id(settingup);'
then: # Enter Setting Mode
- script.execute: setupbutton
- switch.turn_off: hasetup
cover:
- platform: template
name: $upper_devicename
id: blinded
lambda: |-
if (id(openclosed)) {
return COVER_OPEN;
} else {
return COVER_CLOSED;
}
open_action:
- script.execute: openscript
close_action:
- script.execute: closescript
position_action:
# - script.execute: positional
then:
- stepper.set_target:
id: $mystepper
target: !lambda return id(endstop) * pos;
- wait_until: # Wait until 0 reached
lambda: 'return id($mystepper).current_position == (id(endstop) * pos);'
- globals.set: # Set global to current position
id: ${mystepper}_global
value: !lambda return id($mystepper).current_position;
has_position: true
device_class: blind
script:
- id: moveit
then:
- if: # If settings variable is on
condition:
- lambda: 'return id(settingup);'
then: # Enter Setting Mode
- script.execute: setupbutton
else:
- if: # If blind is closed
condition:
- lambda: 'return id(openclosed) == 0;'
then: # Open blind
- script.execute: openscript
else: # Close blind
- script.execute: closescript
- id: openscript
then:
- logger.log: "Opening"
- stepper.set_target: # Send stepper to endstop
id: $mystepper
target: !lambda return id(endstop);
- wait_until: # Wait until endstop reached
lambda: 'return (id($mystepper).current_position == id(endstop));'
- globals.set: # Set global to current position
id: ${mystepper}_global
value: !lambda return id($mystepper).current_position;
- globals.set: # Set toggle to OPEN (No need for 'optimistic mode')
id: openclosed
value: '1'
- logger.log:
format: "The position is being reported as %d, endstop is %d"
args: [ 'id($mystepper).current_position','id(endstop)' ]
- id: closescript
then:
- logger.log: "Closing"
- stepper.set_target: # Send stepper to 0
id: $mystepper
target: '0'
- wait_until: # Wait until 0 reached
lambda: 'return (id($mystepper).current_position == 0);'
- globals.set: # Set global to current position
id: ${mystepper}_global
value: !lambda return id($mystepper).current_position;
- globals.set: # Set toggle to CLOSED (No need for 'optimistic mode')
id: openclosed
value: '0'
- id: setting
then:
- logger.log: "Entered Settings Mode"
- globals.set:
id: settingup
value: '1'
- globals.set:
id: settingmode
value: '1'
- 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: $mystepper
target: !lambda return id($mystepper).current_position;
- globals.set: # Set Endstop Variable
id: endstop
value: !lambda return id($mystepper).current_position;
- globals.set: # Set Global stepper position
id: ${mystepper}_global
value: !lambda return id($mystepper).current_position;
- globals.set: # Reset Setting Mode
id: settingmode
value: '0'
- globals.set: # Reset Setting Mode
id: settingup
value: '0'
- globals.set: # Set toggle to Open
id: openclosed
value: '1'
- 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: $mystepper
position: '0'
- stepper.set_target: # Reset Stepper position to 0
id: $mystepper
target: '0'
- globals.set: # Move stepper to 0 (doesn't move it's already there!)
id: ${mystepper}_global
value: '0'
- stepper.set_target: # Reset Stepper position to 72000
id: $mystepper
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: $mystepper
position: '72000'
- globals.set: # Advance setup to next mode
id: settingmode
value: '2'
Hey @RoadkillUK
Awesome work! Thank you so much for sharing!
I tested this out and wanted to report a few things and ask a few questions
- ESPHome is concerned about the ‘position_action’ of the cover and says is not supported. I had to comment the whole section out to get it validated.
- During the setup stepper starts moving the blind up instead of down in the first place in both pin configurations (readme says the opposite). The only problem with this is that 0 is always set for a fully open position from what I understand and so HA reverts open and close buttons.
- Is there no stop action? Unless ‘position_action’ is meant to achieve the same thing.
- Is there a way to configure open and closed positions steps manually?
- And is there a way to adjust this for two blinds?
-
Previously it would only compile on the ESPHome 1.15 Dev version, however there has just been an updated version of ESPHome Beta which is what I usually use.
-
Oh, I’m surprised, I’ve tested it and changing the pins should work, can anyone else confirm this please? (silly question, but have you wired the motor the same way as in the original MOARB?)
-
I’ve just added one, use the Github link.
-
Position now works, I have added the following to Lovelace and it shows a slider to adjust the blind, you will also need Lovelace Slider Entity.
- type: entities
name: Roller Blind
min: 0
max: 100
step: 10
entities:
- cover.roller_blind
- type: custom:slider-entity-row
entity: cover.roller_blind
- I’m sure there’s a way, but I’m not doing it
Regarding the positioning, it’s not 100%. When you press the stop button, the slider doesn’t change to the correct position. When not fully open or closed, it will only allow either up or down, this is to do with the single button function as it needs to know which way to go.
If anyone wants to take a look and improve it then please do
I think I’ll have to rewrite some of this as it’s mainly ‘functional’
Thanks @RoadkillUK
I’m using ESPhome 1.14, please disregard the rotation direction, I think it was late night and I messed up the connection after all experimenting. Thank you for everything else but I guess I’ll be moving on Need to configure 4 blinds (2 x 1 ESP). Will try to figure out your code for the parts I’m struggling with if you don’t mind
Right, I’ve done a big rewrite and it took me ages but at least I’m learning.
It still only controls one blind but it does it well, has positioning etc. and is a straight swap for MOARB.
Hi all,
I come in this topic because I see a lot of specialist of Esphome related with stepper motor. My goal is to buiild a 1-axis solar tracker via i) a magnetic compass, here a BMM150 and ii) a stepper motor with the a4988. In my idea, the control of the stepper motor (direction & step) must be done on the difference between the heading given by the compass and the solar heading given the sun object with my local GPS coordinates. So in theory it’s simple if the difference is +D then set the target of the stepper with int(D/delta_angle), if the difference is -D then set with -int(D/delta_angle).
Here my current code
esphome:
name: suiveur_1axe
platform: ESP8266
board: nodemcuv2
includes:
- BMM150_custom_sensor.h
wifi:
ssid: !secret esphome_ssid
password: !secret esphome_password
# Enable fallback hotspot (captive portal) in case wifi connection fails
ap:
ssid: "Suiveur 1Axe"
password: !secret esphome_password
captive_portal:
# Enable logging
logger:
# Enable Home Assistant API
api:
services:
- service: control_stepper
variables:
target: int
then:
- stepper.set_target:
id: stepper_suiveur_1axe
target: !lambda 'return target;'
ota:
sun:
latitude: !secret esphome_lat
longitude: !secret esphome_long
time:
- platform: homeassistant #sntp
id: homeassistant_time
output:
- platform: gpio
pin:
number: GPIO02
inverted: True
id: gpio_02
light:
- platform: binary
output: gpio_02
name: suiveur_1axe_led_status
id: suiveur_1axe_led_status
i2c:
sda: GPIO12
scl: GPIO14
id: bus_i2c
sensor:
- platform: sun
name: suiveur_1axe_elevation_soleil
type: elevation
- platform: sun
name: suiveur_1axe_azimuth_soleil
id: suiveur_1axe_azimuth_soleil
type: azimuth
update_interval: 1s
# - platform: qmc5883l
# address: 0x0D
# field_strength_x:
# name: "suiveur_1axe_X"
# id: suiveur_1axe_X
# field_strength_y:
# name: "suiveur_1axe_Y"
# id: suiveur_1axe_Y
# field_strength_z:
# name: "suiveur_1axe_Z"
# id: suiveur_1axe_Z
# heading:
# name: "suiveur_1axe_azimuth_plateau"
# id: suiveur_1axe_azimuth_plateau
# range: 200uT
# oversampling: 256x
# update_interval: 1s
- platform: custom
id: bmm150_id
lambda: |-
auto BMM150 = new BMM150CustomSensor();
App.register_component(BMM150);
return {BMM150->heading_sensor};
sensors:
- name: "suiveur_1axe_azimuth_plateau"
id: suiveur_1axe_azimuth_plateau
unit_of_measurement: "°"
accuracy_decimals: 1
icon: "mdi:compass-outline"
filters:
- sliding_window_moving_average:
window_size: 3
send_every: 1
# filters:
# - sliding_window_moving_average:
# window_size: 15
# send_every: 1
# - name: "suiveur_1axe_x"
# unit_of_measurement: "uT"
# accuracy_decimals: 2
# icon: "mdi:magnet"
# filters:
# - sliding_window_moving_average:
# window_size: 15
# send_every: 1
# - name: "suiveur_1axe_y"
# unit_of_measurement: "uT"
# accuracy_decimals: 2
# icon: "mdi:magnet"
# filters:
# - sliding_window_moving_average:
# window_size: 15
# send_every: 1
# - name: "suiveur_1axe_z"
# unit_of_measurement: "uT"
# accuracy_decimals: 2
# icon: "mdi:magnet"
# filters:
#- median:
# window_size: 7
# send_every: 1
# - sliding_window_moving_average:
# window_size: 15
# send_every: 1
# - platform: template
# id: suiveur_1axe_azimuth_plateau_smoothed
# name: suiveur_1axe_azimuth_plateau_smoothed
# update_interval: 1s
# unit_of_measurement: "°"
# lambda: return id(suiveur_1axe_azimuth_plateau).state + 0*180.0;
# filters:
# - sliding_window_moving_average:
# window_size: 5
# send_every: 1
- platform: template
id: suiveur_1axe_azimuth_difference
name: suiveur_1axe_azimuth_difference
update_interval: 1s
unit_of_measurement: "°"
lambda: return ( id(suiveur_1axe_azimuth_soleil).state - id(suiveur_1axe_azimuth_plateau).state);
filters:
- sliding_window_moving_average:
window_size: 5
send_every: 1
on_value:
- if:
condition:
and:
- lambda: |-
return (id(stepper_suiveur_1axe).current_position == id(stepper_suiveur_1axe).target_position );
- lambda: |-
return ( ( ( float(id(suiveur_1axe_azimuth_difference).state) < -2.0) | ( float(id(suiveur_1axe_azimuth_difference).state) > 2.0) ) );
#not:
# - sensor.in_range:
# id: suiveur_1axe_azimuth_difference
# below: -2.0
# above: 2.0
#lambda: |-
# return ( fabs(float(id(suiveur_1axe_azimuth_difference).state)) > 1.0 );
then:
#- delay: 30s
- light.turn_on:
id: suiveur_1axe_led_status
- stepper.set_target:
id: stepper_suiveur_1axe
target: !lambda |-
if (float(id(suiveur_1axe_azimuth_difference).state) > 10.0)
{
return -int( float(id( suiveur_1axe_azimuth_difference).state)/((1.8/20.0)) );
}
else if (float(id(suiveur_1axe_azimuth_difference).state) > 2.0)
{
return -int( float(id( suiveur_1axe_azimuth_difference).state)/((1.8/10.0)) );
}
else if (float(id(suiveur_1axe_azimuth_difference).state) < -2.0)
{
return int( float(id( suiveur_1axe_azimuth_difference).state)/((1.8/10.0)) );
}
else if (float(id(suiveur_1axe_azimuth_difference).state) < -10.0)
{
return int( float(id( suiveur_1axe_azimuth_difference).state)/((1.8/20.0)) );
}
#- delay: 10s
#- while:
# condition:
# lambda: 'return id(stepper_suiveur_1axe).current_position != id(stepper_suiveur_1axe).target_position;'
# then:
# - logger.log: "Rotation"
- light.turn_off:
id: suiveur_1axe_led_status
#- if:
#condition:
# Should return either true or false
# lambda: |-
# return ( ( ( float(id(suiveur_1axe_azimuth_difference).state) < -1.0) | ( float(id(suiveur_1axe_azimuth_difference).state) > 1.0) ) & (id(stepper_suiveur_1axe).current_position == id(stepper_suiveur_1axe).target_position ) );
# # return ( ( fabs(float(id(suiveur_1axe_azimuth_difference).state) ) > 1.0) & (id(stepper_suiveur_1axe).current_position == id(stepper_suiveur_1axe).target_position ) );
# then:
# - stepper.set_target:
# id: stepper_suiveur_1axe
# target: !lambda |-
# if (float(id(suiveur_1axe_azimuth_difference).state) > 0.0)
# {
# return -10;
# } else
# {
# return 10;
# }
#!lambda return ( -sign(float(id( suiveur_1axe_azimuth_difference).state)) ) ;
#target: !lambda return ( -int(float(id( suiveur_1axe_azimuth_difference).state)/((1.8/10))) ) ;
- platform: uptime
#name: "up_suiveur_1axe"
id: uptime_sec
- platform: wifi_signal
name: "WiFi puissance_suiveur_1axe"
update_interval: 10s
switch:
- platform: restart
name: "restart_suiveur_1axe"
stepper:
- platform: a4988
id: stepper_suiveur_1axe
step_pin:
number: D0
inverted: False
dir_pin:
number: D1
inverted: False
max_speed: 60 steps/s
# Optional:
sleep_pin:
number: D2
inverted: False
acceleration: inf
deceleration: inf
cover:
- platform: template
name: "suiveur_1axe"
id: suiveur_1axe
open_action:
- stepper.set_target:
id: stepper_suiveur_1axe
target: -1000
close_action:
- stepper.set_target:
id: stepper_suiveur_1axe
target: 1000
stop_action:
- stepper.set_target:
id: stepper_suiveur_1axe
target: !lambda return id(stepper_suiveur_1axe).current_position;
optimistic: true
binary_sensor:
- platform: status
name: "suiveur_1axe_status"
text_sensor:
- platform: template
name: suiveur_1axe__uptime
lambda: |-
int seconds = (id(uptime_sec).state);
int days = seconds / (24 * 3600);
seconds = seconds % (24 * 3600);
int hours = seconds / 3600;
seconds = seconds % 3600;
int minutes = seconds / 60;
seconds = seconds % 60;
return { (String(days) +"d " + String(hours) +"h " + String(minutes) +"m "+ String(seconds) +"s").c_str() };
icon: mdi:clock-start
update_interval: 113s
- platform: version
name: "suiveur_1axe__ESPHome_version"
In practice it’s not working … oscillating between the 0 value or even worser can converge the a non-zero value I think the problem comes from the fact that during the rotation of the motor, the template sensor associated with difference of heading is also changing and then a new set_target is set.
When I would like to write and I don’t know yet how is to
i) compute the difference of heading and be sure its value is stablized then if the motor is in IDLE
ii) send the command to rotate correspondly to this difference AND do not update the difference sensor and/or wait the rotation reaches the target_position in order to send a new rotation command.
I hope I was more or less clear…
I’m trying to build my own roller but I find the 28BYJ-48 stepper motor (in 12v) has not enough torque to move my blind (36’’ or 91 cm wide). What kind of motor do you use?