So I have gotten my feedback cover for my gate working almost perfectly (there are a few unfinished provisions like the pulse counter). However I am running into a big issue, I have an ina260 reading forward and reverse current and its triggering a binary template sensor for obstacle detection. The cover sees the detection, issues a cover.stop. Then no rollback.
substitutions:
devicename: gate_controller
devicename_no_dashes: gate_controller
friendly_devicename: "Gate Controller"
restore_mode_setting: RESTORE_DEFAULT_OFF
purpose: entry
subject: gate
# WT32-ETH01 specific pins
open_pin: GPIO04 # Adjust based on your wiring PWM
close_pin: GPIO02 # Adjust based on your wiring PWM
pulse_pin: GPIO14 # Input only pin for hall effect sensor
i2c_sda: GPIO33
i2c_scl: GPIO32
esphome:
name: ${devicename}
friendly_name: ${friendly_devicename}
esp32:
board: wt32-eth01
framework:
type: arduino
packages:
device_base: !include /config/esphome/common/esp32common.yaml
# Ethernet configuration for WT32-ETH01
# ethernet:
# type: LAN8720
# mdc_pin: GPIO23
# mdio_pin: GPIO18
# clk_mode: GPIO17_OUT
# phy_addr: 1
# power_pin: GPIO16
# domain: .symtus.com
# enable_mdns: true
# Fallback WiFi configuration
wifi:
ssid: !secret wifi_ssid2
password: !secret wifi_password2
domain: .symtus.com
ap:
ssid: "Gate Controller Fallback"
password: "i6D5uFh1LG1A"
# API and OTA
api:
reboot_timeout: 0h
encryption:
key: !secret api_encryption_key
ota:
platform: esphome
password: !secret ota_password
# Enable web server
web_server:
port: 80
logger:
level: DEBUG # This will show INFO and higher (WARN, ERROR)
logs:
sensor: WARN # Make sensor logs less frequent
obstacle: INFO # Keep obstacle detection at INFO level
# I2C configuration for INA260
i2c:
sda: ${i2c_sda}
scl: ${i2c_scl}
scan: true
frequency: 400khz
mcp23017:
- id: gate_io_expander
# interrupt_timing: 10ms # Debounce timing for interrupts, adjust if needed
address: 0x20 # Default address, adjust if needed
# interrupt: GPIO35 # Using your existing pulse_pin for interrupt
# Global state variables
globals:
- id: last_direction
type: int
restore_value: yes # Remember across reboots
initial_value: "0" # 0=unknown, 1=was opening, 2=was closing
- id: soft_stop_percentage
type: float
restore_value: yes
initial_value: "0.90" # Start soft stop at 90% of travel
- id: pwm_level
type: float
restore_value: no
initial_value: "0.0"
- id: is_soft_starting
type: bool
restore_value: no
initial_value: "false"
- id: is_soft_stoping
type: bool
restore_value: no
initial_value: "false"
- id: gate_close_countdown
type: int
restore_value: no
initial_value: "0"
- id: auto_close_active
type: bool
restore_value: no
initial_value: "false"
#################################For possible rotation based stops##########################
- id: last_open_rotation_count
type: int
restore_value: yes
initial_value: "0"
- id: last_close_rotation_count
type: int
restore_value: yes
initial_value: "0"
- id: current_rotation_count
type: int
restore_value: yes
initial_value: "0"
#################################For possible rotation based stops##########################
# Output configuration for motor control
output:
- platform: ledc
pin: ${open_pin}
frequency: 10000Hz
id: gate_control_gate_open
- platform: ledc
pin: ${close_pin}
frequency: 10000Hz
id: gate_control_gate_close
# State display sensor ONLY A DISPLAY
text_sensor:
- platform: template
name: "Gate State"
id: gate_state_sensor
update_interval: never # faster update interval elsewhere
lambda: |-
if (id(obstacle_detected).state) return {"ERROR"};
if (id(gate_opening).state) return {"OPENING"};
if (id(gate_closing).state) return {"CLOSING"};
if (id(open_gate_endstop).state) return {"OPEN"};
if (id(close_gate_endstop).state) return {"CLOSED"};
if (id(gate_ajar).state) return {"AJAR"};
return {"STOPPED"};
# Binary sensors
binary_sensor:
# Limit switches
- platform: gpio
pin:
mcp23xxx: gate_io_expander
number: 0 # GPA0
mode:
input: true
pullup: true
inverted: true
id: gate_open_switch
name: "Gate is Fully Open"
filters:
- delayed_off: 50ms
on_press:
- logger.log: "Open endstop GPIO activated"
on_release:
- logger.log: "Open endstop GPIO released"
- platform: gpio
pin:
mcp23xxx: gate_io_expander
number: 1 # GPA1
mode:
input: true
pullup: true
inverted: true
id: gate_close_switch
name: "Gate is Fully Closed"
filters:
- delayed_off: 50ms
on_press:
- logger.log: "Close endstop GPIO activated"
on_release:
- logger.log: "Close endstop GPIO released"
# Toggle button on MCP23017
- platform: gpio
pin:
mcp23xxx: gate_io_expander
number: 2 # GPA2
mode:
input: true
pullup: true
inverted: true
id: gate_cycle_trigger
name: "Gate Cycle Trigger"
filters:
- delayed_on: 100ms
- delayed_off: 100ms
on_press:
- button.press: gate_cycle_button
- logger.log: "Gate cycle button pressed"
# OPEN button on MCP23017
- platform: gpio
pin:
mcp23xxx: gate_io_expander
number: 3 # GPA3
mode:
input: true
pullup: true
inverted: true
name: "Gate test OPEN "
filters:
- delayed_on: 100ms
- delayed_off: 10ms
on_press:
- button.press: Test_OPEN
- logger.log: "Gate test OPEN button pressed"
on_release:
output.turn_off: gate_control_gate_open
# CLOSE button on MCP23017
- platform: gpio
pin:
mcp23xxx: gate_io_expander
number: 4 # GPA
mode:
input: true
pullup: true
inverted: true
name: "Gate test CLOSE "
filters:
- delayed_on: 100ms
- delayed_off: 10ms
on_press:
- button.press: Test_CLOSE
- logger.log: "Gate test CLOSE button pressed"
on_release:
output.turn_off: gate_control_gate_close
- platform: template
id: open_gate_endstop
name: "Gate Open Multi Endstop Reached"
lambda: |-
return id(gate_open_switch).state;
- platform: template
id: close_gate_endstop
name: "Gate Closed Multi Endstop Reached"
lambda: |-
return id(gate_close_switch).state;
- platform: template
id: gate_ajar
name: "Gate is Ajar"
lambda: |-
return !(id(open_gate_endstop).state || id(close_gate_endstop).state);
- platform: template
id: gate_opening
name: "Gate Opening Current"
lambda: |-
float current = id(gate_power_sensor_current).state;
// Positive current indicates opening
return current > 0.01; // Small threshold to avoid noise
filters:
- delayed_off: 100ms # Only consider "off" if the condition is false for at least
- platform: template
id: gate_closing
name: "Gate Closing Current"
lambda: |-
float current = id(gate_power_sensor_current).state;
// Negative current indicates closing
return current < -0.010; // Small threshold to avoid noise
filters:
- delayed_off: 100ms
#######################################This works for a rollback but its super janky!!############################################
# - platform: template
# id: obstacle_detected
# name: "Obstacle Detected Current"
# publish_initial_state: True
# filters:
# - delayed_on: 50ms
# - delayed_off: 200ms
# lambda: |-
# // Only check for obstacles if gate is moving
# if (id(gate_cover).current_operation == COVER_OPERATION_IDLE) {
# return false;
# }
# float current_threshold = 0.20; // Base threshold
# if (id(is_soft_starting)) {
# current_threshold = current_threshold + (1.0 - pow(id(pwm_level), 2.0f)) * 0.3;
# }
# id(gate_power_sensor).update();
# float current = abs(id(gate_power_sensor_current).state);
# ESP_LOGD("obstacle", "Current: %.3fA, Threshold: %.3fA%s",
# current,
# current_threshold,
# id(is_soft_starting) ? " (Soft Start)" : "");
# return current > current_threshold;
# on_state:
# - if:
# condition:
# lambda: 'return id(gate_cover).current_operation == COVER_OPERATION_OPENING;' # Was opening
# then:
# - cover.stop: gate_cover
# - cover.close: gate_cover
# - delay: 4s
# - cover.stop: gate_cover
# - if:
# condition:
# lambda: 'return id(gate_cover).current_operation == COVER_OPERATION_CLOSING;' # Was closing
# then:
# - cover.stop: gate_cover
# - cover.open: gate_cover
# - delay: 4s
# - cover.stop: gate_cover
- platform: template
id: current_threshold_exceeded
internal: true
lambda: |-
bool is_opening = id(gate_cover).current_operation == COVER_OPERATION_OPENING;
float position = id(gate_cover).position;
float current_threshold = is_opening ? 0.17 : 0.17;
if (id(is_soft_starting)) {
current_threshold = current_threshold + (1.0 - pow(id(pwm_level), 2.0f)) * 0.4;
} else if (id(is_soft_stoping)) {
current_threshold = current_threshold + (1.0 - pow(id(pwm_level), 2.0f)) * 0.3;
}
if (position > 0.85 || position < 0.15) {
current_threshold *= 1.2;
}
float current = abs(id(gate_power_sensor_current).state);
ESP_LOGD("obstacle", "Current: %.3fA, Threshold: %.3fA, Position: %.2f%s%s",
current,
current_threshold,
position,
id(is_soft_starting) ? " (Soft Start)" : "",
id(is_soft_stoping) ? " (Soft Stop)" : "");
return current > current_threshold;
- platform: template
id: obstacle_detected
name: "Obstacle Detected"
publish_initial_state: true
lambda: |-
if (id(current_threshold_exceeded).state) {
return true; // Once true, stays true until reset
}
return id(obstacle_detected).state; // Maintain previous state
on_press:
- logger.log: "Obstacle detected!"
# Sensors
sensor:
# Current sensor
- platform: ina260
address: 0x40
id: gate_power_sensor
update_interval: 50ms # Will be updated by the interval component when needed
current:
id: gate_power_sensor_current
name: "Gate Motor Current"
accuracy_decimals: 2
filters:
- sliding_window_moving_average:
window_size: 30
send_every: 1
- platform: template
name: "Gate Raw Position"
unit_of_measurement: "RAW"
accuracy_decimals: 2
update_interval: 50ms
lambda: |-
return id(gate_cover).position;
- platform: template
name: "Gate Cover Operating State"
id: gate_cover_operation_state
lambda: |-
return id(gate_cover).current_operation;
unit_of_measurement: "state"
accuracy_decimals: 0
icon: "mdi:gate"
update_interval: 50ms
- platform: template
name: "Gate Closing Countdown"
id: gate_closing_countdown
unit_of_measurement: "Seconds"
accuracy_decimals: 0
icon: mdi:timer
lambda: |-
return id(gate_close_countdown);
# Display for the the global countdown value
- platform: template
name: "Last Open Rotation Count"
id: last_open_rotation_count_sensor
unit_of_measurement: "rotations"
accuracy_decimals: 0
lambda: |-
return id(last_open_rotation_count);
- platform: template
name: "Last Close Rotation Count"
id: last_close_rotation_count_sensor
unit_of_measurement: "rotations"
accuracy_decimals: 0
lambda: |-
return id(last_close_rotation_count);
# Rotation counter
- platform: pulse_meter
pin: ${pulse_pin}
id: gate_motor_rotations
internal: true
filters:
- debounce: 30ms
total:
name: "Motor Rotations"
id: gate_motor_rotations_total
on_value:
then:
- if:
condition:
binary_sensor.is_on: gate_opening
then:
- lambda: |-
id(current_rotation_count) = id(current_rotation_count) + 1;
- if:
condition:
binary_sensor.is_on: gate_closing
then:
- lambda: |-
id(current_rotation_count) = id(current_rotation_count) - 1;
- platform: homeassistant
id: ui_gate_duration
entity_id: input_number.${purpose}_gate_duration
on_value:
then:
- sensor.template.publish:
id: ${purpose}_gate_duration
state: !lambda return id(ui_gate_duration).state;
- platform: template
name: ${purpose} duration - gate
id: ${purpose}_gate_duration
accuracy_decimals: 0
unit_of_measurement: sec
icon: mdi:camera-timer
# Cover component
cover:
- platform: feedback
name: "Main Gate"
id: gate_cover
device_class: gate
update_interval: 1s
direction_change_wait_time: 1s
obstacle_rollback: 20%
open_endstop: open_gate_endstop
close_endstop: close_gate_endstop
open_duration: 53s
close_duration: 55s
open_obstacle_sensor: obstacle_detected
close_obstacle_sensor: obstacle_detected
open_action:
- if:
condition:
binary_sensor.is_on: open_gate_endstop
then:
- logger.log: "Cannot open - gate at open endstop" #this is for a bug in feedback cover
else:
- globals.set:
id: last_direction
value: "1" # Mark as opening
- globals.set:
id: is_soft_starting
value: "true"
- globals.set:
id: is_soft_stoping
value: "true"
- logger.log: "Starting soft start open sequence"
close_action:
- if:
condition:
binary_sensor.is_on: close_gate_endstop
then:
- logger.log: "Cannot close - gate at close endstop" #this is for a bug in feedback cover
else:
- globals.set:
id: last_direction
value: "2" # Mark as closing
- globals.set:
id: is_soft_starting
value: "true"
- globals.set:
id: is_soft_stoping
value: "true"
- globals.set:
id: gate_close_countdown
value: "0"
- component.update: gate_closing_countdown
- logger.log: "Starting soft start for closing gate"
on_open: # When successfully reaches open position
- globals.set:
id: last_open_rotation_count
value: id(current_rotation_count)
- logger.log:
format: "Gate successfully opened. Final rotation count: %d"
args: ['id(current_rotation_count)']
- pulse_meter.set_total_pulses:
id: gate_motor_rotations
value: 0
- globals.set:
id: current_rotation_count
value: '0'
- component.update: gate_state_sensor
on_closed: # When successfully reaches closed position
- globals.set:
id: last_close_rotation_count
value: id(current_rotation_count)
- logger.log:
format: "Gate successfully closed. Final rotation count: %d"
args: ['id(current_rotation_count)']
- pulse_meter.set_total_pulses:
id: gate_motor_rotations
value: 0
- globals.set:
id: current_rotation_count
value: '0'
- globals.set:
id: auto_close_active
value: 'false'
# - delay: 1s
- component.update: gate_state_sensor
stop_action: #stop everything!!
- globals.set: #just in case stop was called before ramp up was complete
id: is_soft_starting
value: "false"
- globals.set: #just in case stop was called before ramp down was complete
id: is_soft_stoping
value: "false"
- globals.set: #OFF
id: pwm_level
value: "0.0"
- globals.set:
id: gate_close_countdown #if something was counting down, stop it
value: "0"
# - globals.set:
# id: auto_close_active # Cancel any active auto-close sequence
# value: "false"
- output.turn_off: gate_control_gate_open #Stop both outputs
- output.turn_off: gate_control_gate_close
- delay: 1s
- component.update: gate_state_sensor
# Update interval
interval:
##########################################DEBUG##############################################################
- interval: 10s
then:
- lambda: |-
ESP_LOGD("state_monitor", "State Check - Operation: %d, Soft Start: %d, Soft Stop: %d, Auto Close: %d, Last Direction: %d, PWM: %.2f, Position: %.2f",
id(gate_cover).current_operation,
id(is_soft_starting),
id(is_soft_stoping),
id(auto_close_active),
id(last_direction),
id(pwm_level),
id(gate_cover).position);
#######################################END###DEBUG##############################################################
############################################100ms SOFT START INTERVAL###################################
- interval: 100ms
then:
# First check endstop safety
- if:
condition:
or:
- and:
- binary_sensor.is_on: open_gate_endstop
- lambda: 'return id(gate_cover).current_operation == COVER_OPERATION_OPENING;'
- and:
- binary_sensor.is_on: close_gate_endstop
- lambda: 'return id(gate_cover).current_operation == COVER_OPERATION_CLOSING;'
then:
- logger.log: "Safety stop: Cover operating against endstop"
- cover.stop: gate_cover
- if:
condition:
binary_sensor.is_on: open_gate_endstop
then:
- cover.close: gate_cover
else:
- if:
condition:
binary_sensor.is_on: close_gate_endstop
then:
- cover.open: gate_cover
# Only perform this logic if soft start is active
- if:
condition:
lambda: 'return id(is_soft_starting);'
then:
# Increment soft start level until we reach full power
- lambda: |-
if (id(pwm_level) < 1.0) {
id(pwm_level) = std::min(1.0f, id(pwm_level) + 0.02f);
}
# Determine gate direction and apply corresponding PWM
- if:
condition:
lambda: 'return id(gate_cover).current_operation == COVER_OPERATION_OPENING;' # Opening
then:
- output.set_level:
id: gate_control_gate_open
level: !lambda 'return pow(id(pwm_level), 2.0f);'
# - logger.log:
# format: "Applying PWM to OPEN direction. Level: %.2f"
# args: ['pow(id(pwm_level), 2.0f)']
- if:
condition:
lambda: 'return id(gate_cover).current_operation == COVER_OPERATION_CLOSING;'
then:
- output.set_level:
id: gate_control_gate_close
level: !lambda 'return pow(id(pwm_level), 2.0f);'
# - logger.log:
# format: "Applying PWM to CLOSE direction. Level: %.2f"
# args: ['pow(id(pwm_level), 2.0f)']
# When soft start is complete, deactivate the soft start mode
- if:
condition:
lambda: 'return id(pwm_level) == 1.0;'
then:
- globals.set:
id: is_soft_starting
value: "false"
- logger.log: "Ramp complete - Motor at full power - Deactivating soft start"
############################################50ms SOFT STOP INTERVAL###################################
- interval: 50ms
then:
- if:
condition:
and:
#Check if is_soft_starting is false becasue soft start has been completed
- lambda: 'return !id(is_soft_starting);'
- lambda: 'return id(is_soft_stoping);'
# Condition: Gate position and direction checks
- or:
- and:
# Check if the gate is opening and the position is >= 99%
- lambda: 'return id(gate_cover).current_operation == COVER_OPERATION_OPENING;'
- lambda: 'return id(gate_cover).position >= .99;'
- and:
# Check if the gate is closing and the position is <= 1%
- lambda: 'return id(gate_cover).current_operation == COVER_OPERATION_CLOSING;'
- lambda: 'return id(gate_cover).position <= .01;'
then:
# - logger.log: "Ramp down starting - soft stop"
# Decrement soft stop level until we reach minimum power
- lambda: |-
if (id(pwm_level) > 0.35) {
id(pwm_level) = std::max(0.1f, id(pwm_level) - 0.01f);
}
# - logger.log:
# format: "Ramp down progress: %.0f%%, Level: %.2f, Smoothed: %.2f"
# args: [
# "id(pwm_level) * 100",
# 'id(pwm_level)',
# 'pow(id(pwm_level), 2.0f)'
# ]
# Apply PWM to active output
- if:
condition:
binary_sensor.is_on: gate_opening
then:
- output.set_level:
id: gate_control_gate_open
level: !lambda 'return std::max(0.25f, pow(id(pwm_level), 2.0f));'
- if:
condition:
binary_sensor.is_on: gate_closing
then:
- output.set_level:
id: gate_control_gate_close
level: !lambda 'return std::max(0.25f, pow(id(pwm_level), 2.0f));'
#####################################ONE SECOND COUNTDOWN######################################
- interval: 1s
then:
- if:
condition:
lambda: 'return id(gate_close_countdown) > 0;'
then:
- lambda: |-
id(gate_close_countdown)--;
ESP_LOGD("countdown", "Countdown: %d seconds remaining", id(gate_close_countdown));
- component.update: gate_closing_countdown
button:
##########################GATE CYCLE LOGIC BUTTONS#################################
- platform: template
name: "Gate Cycle"
id: gate_cycle_button
on_press:
- if:
condition:
lambda: 'return id(gate_cover).current_operation != COVER_OPERATION_IDLE;'
then:
- cover.stop: gate_cover #Just STOP
- logger.log: "STOP!!!"
else:
- logger.log: "Condition: Gate is idle, proceeding with checks."
- if:
condition:
lambda: 'return id(gate_cover).position == COVER_OPEN;'
then:
- logger.log: "Condition: Gate is at OPEN position, starting close."
- cover.close: gate_cover #Just CLOSE
else:
- if:
condition:
# Modified condition to be more explicit about valid states for opening
lambda: 'return (id(gate_cover).position == COVER_CLOSED);'
then:
- logger.log: "Starting open cycle from CLOSED position"
- cover.open: gate_cover #Open the cover
- globals.set:
id: auto_close_active #set auto close bit
value: "true"
- wait_until:
condition:
lambda: 'return id(gate_cover).position == COVER_OPEN;'
- lambda: |-
id(gate_close_countdown) = int(id(ui_gate_duration).state);
- wait_until:
condition:
lambda: 'return id(gate_close_countdown) <= 0 && id(auto_close_active);'
- cover.close: gate_cover
- globals.set:
id: auto_close_active #set auto close bit
value: "false"
else:
- logger.log: "Position uncertain or was opening, defaulting to close"
- cover.close: gate_cover
# ####################################################TESTING BUTTONS
- platform: template
name: "Test Open Output"
id: Test_OPEN
on_press:
- if:
condition:
binary_sensor.is_on: open_gate_endstop
then:
- logger.log: "Cannot activate open output - Gate already at open endstop!"
else:
- output.set_level:
id: gate_control_gate_open
level: 1.0
- logger.log: "Setting open output to full"
- platform: template
name: "Test Close Output"
id: Test_CLOSE
on_press:
- if:
condition:
binary_sensor.is_on: close_gate_endstop
then:
- logger.log: "Cannot activate close output - Gate already at close endstop!"
else:
- output.set_level:
id: gate_control_gate_close
level: 1.0
- logger.log: "Setting close output to full"
# Optional: Add auto-shutoff after brief period
# ####################################################TESTING BUTTONS