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…