esphome: name: fjernkontroll-markise1 friendly_name: Fjernkontroll_markise1 esp32: board: esp32-c3-devkitm-1 framework: type: arduino # Enable logging logger: # Enable Home Assistant API api: encryption: key: "XXXXXXXXXXXXXXXXX" services: - service: open_blinds then: - script.execute: open_blinds - service: close_blinds then: - script.execute: close_blinds - service: stop_blinds then: - script.execute: stop_blinds - service: reset_blinds_position then: - script.execute: reset_blinds_position - service: calibrate_blinds then: - script.execute: calibrate_blinds - service: set_blinds_position variables: position: float then: - lambda: |- float current_position = id(blinds_position_internal); if (position == current_position) return; // Do nothing if already at target float travel_time = 37.5 * (abs(position - current_position) / 100.0); if (position > current_position) { id(blinds_direction) = 1; id(blinds_moving) = true; id(blinds_last_start_time) = millis(); id(blinds_status).publish_state("Moving to " + std::to_string((int)position) + "%"); id(blinds_out).turn_on(); delay(travel_time * 1000); id(blinds_out).turn_off(); } else if (position < current_position) { id(blinds_direction) = -1; id(blinds_moving) = true; id(blinds_last_start_time) = millis(); id(blinds_status).publish_state("Moving to " + std::to_string((int)position) + "%"); id(blinds_in).turn_on(); delay(travel_time * 1000); id(blinds_in).turn_off(); } id(blinds_position_internal) = position; id(blinds_position).publish_state(position); id(blinds_status).publish_state("Stopped"); id(blinds_moving) = false; id(blinds_direction) = 0; ota: - platform: esphome password: "XXXXXXXXXXXXXXXXXXX" wifi: ssid: !secret wifi_ssid password: !secret wifi_password # Enable fallback hotspot (captive portal) in case wifi connection fails ap: ssid: "Fjernkontroll-Markise1" password: "XXXXXXXXXXXXXXXXX" captive_portal: # Binary sensors - Physical buttons + power source detection binary_sensor: - platform: gpio pin: number: GPIO6 mode: INPUT_PULLUP inverted: true name: "Physical Out Button" filters: - delayed_on: 50ms on_press: then: - script.execute: open_blinds - platform: gpio pin: number: GPIO21 mode: INPUT_PULLUP inverted: true name: "Physical In Button" filters: - delayed_on: 50ms on_press: then: - script.execute: close_blinds - platform: gpio pin: number: GPIO7 mode: INPUT_PULLUP inverted: true name: "Physical Stop Button" filters: - delayed_on: 50ms on_press: then: - script.execute: stop_blinds - platform: gpio pin: number: GPIO4 mode: INPUT name: "Power Source" id: power_source_status device_class: power filters: - delayed_on: 500ms - delayed_off: 500ms on_state: then: - lambda: |- float voltage = id(battery_voltage).state; bool gpio_state = id(power_source_status).state; if (voltage < 3.5) { id(power_status).publish_state("Connected, but battery not connected"); } else if (voltage >= 3.5 && voltage < 4.18 && gpio_state) { id(power_status).publish_state("Charging"); } else if (voltage >= 4.18 && gpio_state) { id(power_status).publish_state("Connected"); } else if (voltage >= 3.5 && !gpio_state) { id(power_status).publish_state("Battery"); } else { id(power_status).publish_state("__????___"); } # Motor relays switch: - platform: gpio pin: GPIO5 id: blinds_out - platform: gpio pin: GPIO10 id: blinds_in - platform: gpio pin: GPIO20 id: blinds_stop # Sensors - Blinds + battery sensor: - platform: template name: "Blinds Position" id: blinds_position unit_of_measurement: "%" accuracy_decimals: 0 lambda: |- return id(blinds_position_internal); - platform: adc pin: GPIO2 name: "Battery Voltage" id: battery_voltage attenuation: 12db update_interval: 5s filters: - median: window_size: 16 send_every: 1 send_first_at: 1 - multiply: 2.0 on_value: then: - lambda: |- float voltage = id(battery_voltage).state; bool gpio_state = id(power_source_status).state; if (voltage < 3.5) { id(power_status).publish_state("Connected, but battery not connected"); } else if (voltage >= 3.5 && voltage < 4.18 && gpio_state) { id(power_status).publish_state("Charging"); } else if (voltage >= 4.18 && gpio_state) { id(power_status).publish_state("Connected"); } else if (voltage >= 3.5 && !gpio_state) { id(power_status).publish_state("Battery"); } else { id(power_status).publish_state("__????___"); } - platform: template name: "Battery Percentage" id: battery_percentage unit_of_measurement: "%" lambda: |- float voltage = id(battery_voltage).state; if (voltage >= 4.2) return 100.0; if (voltage >= 4.15) return 95.0; if (voltage >= 4.11) return 90.0; if (voltage >= 4.08) return 85.0; if (voltage >= 4.02) return 80.0; if (voltage >= 3.98) return 75.0; if (voltage >= 3.95) return 70.0; if (voltage >= 3.91) return 65.0; if (voltage >= 3.87) return 60.0; if (voltage >= 3.85) return 55.0; if (voltage >= 3.84) return 50.0; if (voltage >= 3.82) return 45.0; if (voltage >= 3.80) return 40.0; if (voltage >= 3.79) return 35.0; if (voltage >= 3.77) return 30.0; if (voltage >= 3.75) return 25.0; if (voltage >= 3.73) return 20.0; if (voltage >= 3.71) return 0.0; return 0.0; update_interval: 5s entity_category: diagnostic # Text sensors text_sensor: - platform: template name: "Blinds Status" id: blinds_status - platform: template name: "Power Status" id: power_status update_interval: 1s entity_category: diagnostic # Globals globals: - id: blinds_position_internal type: float restore_value: yes initial_value: '0.0' - id: blinds_moving type: bool restore_value: no initial_value: 'false' - id: blinds_direction type: int restore_value: no initial_value: '0' # 1 for opening, -1 for closing, 0 for stopped - id: blinds_last_start_time type: unsigned long restore_value: no initial_value: '0' - id: post_stop_update_time type: int restore_value: no initial_value: '0' # Scripts script: - id: open_blinds mode: restart then: - lambda: |- if (id(blinds_direction) != 1) { if (id(blinds_direction) == -1) { id(close_blinds).stop(); } id(blinds_direction) = 1; id(blinds_moving) = true; id(blinds_last_start_time) = millis(); id(blinds_status).publish_state("Opening"); id(blinds_out).turn_on(); } - delay: 37.5s - script.execute: stop_blinds - id: close_blinds mode: restart then: - lambda: |- if (id(blinds_direction) != -1) { if (id(blinds_direction) == 1) { id(open_blinds).stop(); } id(blinds_direction) = -1; id(blinds_moving) = true; id(blinds_last_start_time) = millis(); id(blinds_status).publish_state("Closing"); id(blinds_in).turn_on(); } - delay: 37.5s - script.execute: stop_blinds - id: stop_blinds mode: restart then: - switch.turn_off: blinds_out - switch.turn_off: blinds_in - switch.turn_on: blinds_stop - delay: 100ms - switch.turn_off: blinds_stop - lambda: |- if (id(blinds_moving)) { unsigned long elapsed = millis() - id(blinds_last_start_time); float delta = (elapsed / 37500.0) * 100.0; if (id(blinds_direction) == 1) { id(blinds_position_internal) += delta; } else if (id(blinds_direction) == -1) { id(blinds_position_internal) -= delta; } id(blinds_position_internal) = fmax(0.0, fmin(100.0, id(blinds_position_internal))); id(blinds_position).publish_state(id(blinds_position_internal)); // Ensure immediate update id(blinds_status).publish_state("Stopped"); id(blinds_moving) = false; id(blinds_direction) = 0; } - script.stop: open_blinds - script.stop: close_blinds - id: reset_blinds_position then: - lambda: |- id(blinds_position_internal) = 0.0; id(blinds_status).publish_state("Reset to 0%"); - id: calibrate_blinds then: - script.execute: close_blinds - delay: 40s # Ensure blinds are fully closed - script.execute: stop_blinds - lambda: |- id(blinds_position_internal) = 0.0; id(blinds_status).publish_state("Calibrated at 0%"); # Position updater interval: - interval: 500ms then: - lambda: |- if (id(blinds_moving)) { // Blinds are moving; update position float delta = (0.5 / 37.5) * 100.0; // Adjusted for 500ms interval if (id(blinds_direction) == 1) { id(blinds_position_internal) += delta; } else if (id(blinds_direction) == -1) { id(blinds_position_internal) -= delta; } id(blinds_position_internal) = fmax(0.0, fmin(100.0, id(blinds_position_internal))); // Reset post-stop update timer id(post_stop_update_time) = 10; // 10 * 500ms = 5 seconds } else if (id(post_stop_update_time) > 0) { // Blinds have stopped; continue updating for the remaining time float delta = (0.5 / 37.5) * 100.0; if (id(blinds_direction) == 1) { id(blinds_position_internal) += delta; } else if (id(blinds_direction) == -1) { id(blinds_position_internal) -= delta; } id(blinds_position_internal) = fmax(0.0, fmin(100.0, id(blinds_position_internal))); // Decrement the post-stop update timer id(post_stop_update_time)--; }