esphome: name: garage-door-controller friendly_name: Garage door controller packages: beacon: !include { file: packages/beacon.yaml } wifi: !include { file: packages/wifi.yaml, vars: { ssid: "garage-door-controller" }} external_components: - source: github://jdillenburg/esphome@main components: [ tfmini ] substitutions: led_count: "60" esp32: board: esp32dev framework: type: esp-idf # Enable logging logger: level: INFO # Enable Home Assistant API api: encryption: key: !secret garage_door_api_key ota: - platform: esphome password: !secret garage_door_ota_password globals: - id: sensor_counter type: int initial_value: '0' - id: effect_counter type: int initial_value: '0' - id: safe_zone type: bool initial_value: 'false' # Garage door button on GPIO16 switch: - platform: gpio device_class: switch pin: GPIO17 id: relay name: "Garage Door Relay" restore_mode: ALWAYS_OFF on_turn_on: then: - delay: 500ms - switch.turn_off: relay # configurable distance thresholds number: - platform: template name: Minimum distance id: min_distance icon: "mdi:cogs" optimistic: true restore_value: true initial_value: "0.0" min_value: 0.0 max_value: 20000.0 step: 1.0 unit_of_measurement: cm - platform: template name: Maximum distance id: max_distance icon: "mdi:cogs" optimistic: true restore_value: true initial_value: "10000.0" min_value: 0.0 max_value: 20000.0 step: 1.0 unit_of_measurement: cm - platform: template name: Buffer distance id: buffer_distance icon: "mdi:cogs" optimistic: true restore_value: true initial_value: "10.0" min_value: 0.0 max_value: 20000.0 step: 1.0 unit_of_measurement: cm #**************** UART ******************** uart: tx_pin: GPIO21 #TXD rx_pin: GPIO22 #RXD baud_rate: 115200 id: uart_bus debug: direction: BOTH dummy_receiver: false after: delimiter: "\n" sequence: - lambda: UARTDebug::log_string(direction, bytes); #******************** Binary Sensor ********************* binary_sensor: - platform: gpio pin: GPIO23 id: closed_sensor internal: True - platform: template id: parked_sensor name: "Car parked" - platform: template id: car_exiting name: "Car exiting" condition: sensor.in_range: id: vehicle_average_speed above: 1.0 - platform: template id: car_entering name: "Car entering" condition: sensor.in_range: id: vehicle_average_speed below: -1.0 #******************** Sensor ********************* sensor: - platform: tfmini id: distance_sensor_raw internal: True distance_unit: cm name: "Distance sensor raw" unit_of_measurement: "cm" on_value: then: - sensor.template.publish: id: distance_sensor state: !lambda 'return x;' - sensor.template.publish: id: distance_sensor_leds state: !lambda 'return x;' - lambda: |- id(sensor_counter) = id(sensor_counter) + 1; # - sensor.template.publish: # id: reading_sensor_counter # state: !lambda 'return id(reading_counter).state + 1;' - platform: template name: "Distance sensor" id: distance_sensor internal: False unit_of_measurement: "cm" filters: - clamp: min_value: 0.0 max_value: 800.0 ignore_out_of_range: True - throttle_average: 1000ms - platform: template name: "Distance sensor for LEDs" id: distance_sensor_leds internal: True unit_of_measurement: "cm" filters: - clamp: min_value: 0.0 max_value: 800.0 ignore_out_of_range: True - platform: template name: "Sensor readings per second" id: reading_counter unit_of_measurement: "readings/s" icon: "mdi:speedometer" update_interval: 10s lambda: |- float current_count = id(sensor_counter); id(sensor_counter) = 0; return current_count/10.0; - platform: template id: light_speed name: "Light effect changes per second" unit_of_measurement: "Hz" icon: "mdi:speedometer" update_interval: 10s lambda: |- float current_count = id(effect_counter); id(effect_counter) = 0; return current_count/10.0; #**************** TIMEOUT OF 30 SECONDS ************************ # See lights_off_when_still script for timeout value - platform: template name: "Vehicle average speed" id: vehicle_average_speed unit_of_measurement: "cm/s" accuracy_decimals: 3 update_interval: 500ms lambda: |- static float previous_distance = 0; static uint32_t last_update = 0; float rate = 0; if (last_update != 0) { float time_delta = (millis() - last_update) / 1000.0; float value_delta = id(distance_sensor_raw).state - previous_distance; rate = value_delta / time_delta; } previous_distance = id(distance_sensor_raw).state; last_update = millis(); return rate; filters: - sliding_window_moving_average: window_size: 5 # average 5 x 0.5 seconds = 2.5 seconds worth of speed send_every: 1 # send average speed every 1/2 second on_value: then: - if: condition: - lambda: 'return abs(id(vehicle_average_speed).state) < 1.0;' # 0.02 mph really slow! then: - if: condition: - and: - light.is_on: led_strip - not: script.is_running: lights_off_when_still then: script.execute: lights_off_when_still else: - if: condition: - light.is_off: led_strip then: - script.stop: lights_off_when_still - light.turn_on: id: led_strip effect: "Distance Effect" - binary_sensor.template.publish: id: parked_sensor state: false #******************** Cover ********************* cover: - platform: template name: "Garage Door" id: garage_door device_class: garage open_action: then: - switch.toggle: relay close_action: then: - switch.toggle: relay lambda: |- return id(closed_sensor).state ? COVER_OPEN : COVER_CLOSED; #********** LED Strip configuration ********** light: - platform: esp32_rmt_led_strip id: led_strip chipset: WS2812 pin: GPIO16 num_leds: ${led_count} rgb_order: GRB name: "Parking Assistant LEDs" restore_mode: RESTORE_DEFAULT_ON effects: - addressable_lambda: name: "Distance Effect" update_interval: 250ms lambda: |- float distance = id(distance_sensor_leds).state; if (distance <= 0 || std::isnan(distance)) return; // Invalid reading // Get the total number of LEDs const int total_leds = it.size(); // Calculate safe zone boundaries float min_safe = id(min_distance).state - id(buffer_distance).state; float max_safe = id(min_distance).state + id(buffer_distance).state; // Measure update rate id(effect_counter) = id(effect_counter) + 1; // Set safe_zone flag based on distance if (distance >= min_safe && distance <= max_safe) { id(safe_zone) = true; } else { id(safe_zone) = false; } // blink if less than min_safe if (distance < min_safe) { if ((millis() % 1000) < 500) { it.all() = Color(255,0,0); // RED } else { it.all() = Color::BLACK; } return; } // solid red in safe zone if (distance < max_safe) { it.all() = Color(255,0,0); return; } // Above max_safe distance, calculate color and LED count float ratio; float denominator = id(max_distance).state - max_safe; if (denominator <= 0.0) { ratio = 1.0; } else { ratio = (distance - max_safe) / denominator; ratio = std::max(0.0f, std::min(1.0f, ratio)); } // Calculate colors - linear interpolation from red to yellow to green uint8_t red, green; if (ratio <= 0.5) { // Red to Yellow red = 255; green = 255 * ratio * 2; // Green goes from 0 to 1 } else { // Yellow to Green red = 255 * 2 * (1.0 - ratio); // Red goes from 1 to 0 green = 255; } // Create the color object auto color = Color(red, green, 0); // Calculate the number of LEDs to light int leds_to_light = static_cast(ratio * total_leds + 0.5); leds_to_light = std::max(0, std::min(total_leds, leds_to_light)); // Center the lit LEDs int start_index = (total_leds - leds_to_light) / 2; int end_index = start_index + leds_to_light - 1; // Turn off all LEDs it.range(0, start_index) = Color::BLACK; it.range(start_index, end_index) = color; it.range(end_index, total_leds) = Color::BLACK; return; script: - id: lights_off_when_still mode: restart then: - delay: 30s - light.turn_off: id: led_strip transition_length: seconds: 5 - if: condition: - lambda: 'return id(safe_zone);' then: - binary_sensor.template.publish: id: parked_sensor state: true