Created
February 12, 2026 14:13
-
-
Save ohadbenita/3b9ce015f5ac4b8137f677bc760c75de to your computer and use it in GitHub Desktop.
ESP Home Timer camera configuration
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # lpg-timercamera.yaml | |
| substitutions: | |
| name: lpg-timercamera | |
| friendly_name: LPG TimerCamera | |
| # How long to stay awake so HA can run rest_command analysis | |
| run_seconds: "120" | |
| esphome: | |
| name: ${name} | |
| friendly_name: ${friendly_name} | |
| on_boot: | |
| priority: 600 | |
| then: | |
| - switch.turn_on: bat_hold_pin | |
| - delay: 10s | |
| - script.execute: wake_marker_then_sleep | |
| esp32: | |
| board: esp32dev | |
| framework: | |
| type: arduino | |
| logger: | |
| level: INFO | |
| wifi: | |
| ssid: !secret wifi_ssid | |
| password: !secret wifi_password | |
| fast_connect: true | |
| power_save_mode: light | |
| reboot_timeout: 0s | |
| ap: | |
| ssid: "${friendly_name} Fallback" | |
| password: !secret fallback_ap_password | |
| api: | |
| reboot_timeout: 0s | |
| encryption: | |
| key: !secret esphome_api_key | |
| ota: | |
| captive_portal: | |
| psram: | |
| mode: quad | |
| speed: 80MHz | |
| # ===== I2C / Camera ===== | |
| i2c: | |
| - id: bsp_i2c | |
| sda: GPIO12 | |
| scl: GPIO14 | |
| - id: cam_i2c | |
| sda: GPIO25 | |
| scl: GPIO23 | |
| esp32_camera: | |
| id: cam | |
| name: "OV3660 Camera" | |
| i2c_id: cam_i2c | |
| external_clock: | |
| pin: GPIO27 | |
| frequency: 20MHz | |
| data_pins: [GPIO32, GPIO35, GPIO34, GPIO5, GPIO39, GPIO18, GPIO36, GPIO19] | |
| vsync_pin: GPIO22 | |
| href_pin: GPIO26 | |
| pixel_clock_pin: GPIO21 | |
| reset_pin: GPIO15 | |
| resolution: 640x480 | |
| jpeg_quality: 10 | |
| # ===== RTC + HA time sync ===== | |
| time: | |
| - platform: bm8563 | |
| i2c_id: bsp_i2c | |
| update_interval: never | |
| - platform: homeassistant | |
| id: ha_time | |
| timezone: "Asia/Jerusalem" | |
| on_time_sync: | |
| then: | |
| - bm8563.write_time: | |
| # ===== Text sensors ===== | |
| text_sensor: | |
| - platform: template | |
| name: "${friendly_name} State" | |
| id: snapshot_marker | |
| update_interval: never | |
| # Epoch timestamp (seconds) for the next wake time (next local noon). | |
| - platform: template | |
| name: "${friendly_name} Next Wake Epoch" | |
| id: next_wake_epoch | |
| update_interval: never | |
| # ===== Battery sensors ===== | |
| sensor: | |
| - platform: adc | |
| pin: GPIO38 | |
| attenuation: 12dB | |
| name: "Battery Voltage" | |
| id: battery_voltage | |
| update_interval: 10s | |
| filters: | |
| - multiply: 1.51 | |
| - platform: template | |
| id: battery_percent | |
| name: "Battery Percentage" | |
| unit_of_measurement: "%" | |
| accuracy_decimals: 0 | |
| lambda: |- | |
| float voltage = id(battery_voltage).state; | |
| const float min_v = 3.350f; | |
| const float max_v = 4.150f; | |
| if (isnan(voltage)) return NAN; | |
| if (voltage <= min_v) return 0.0f; | |
| if (voltage >= max_v) return 100.0f; | |
| return ((voltage - min_v) / (max_v - min_v)) * 100.0f; | |
| # ===== LED output ===== | |
| output: | |
| - platform: ledc | |
| id: blue_led | |
| pin: GPIO2 | |
| light: | |
| - platform: monochromatic | |
| output: blue_led | |
| name: "Blue LED" | |
| restore_mode: RESTORE_DEFAULT_ON | |
| # ===== Battery hold ===== | |
| switch: | |
| - platform: gpio | |
| id: bat_hold_pin | |
| name: "Battery Hold Pin" | |
| pin: GPIO33 | |
| restore_mode: RESTORE_DEFAULT_ON | |
| # ===== Script: publish state, compute next wake, log it, then sleep ===== | |
| script: | |
| - id: wake_marker_then_sleep | |
| mode: single | |
| then: | |
| # Ready for analysis/snapshot | |
| - lambda: |- | |
| ESP_LOGI("lpg", "Camera is awake and ready for snapshot."); | |
| id(snapshot_marker).publish_state("awake_ready_for_snapshot"); | |
| # Keep awake long enough for HA to run rest_command analysis | |
| - delay: !lambda "return atoi(\"${run_seconds}\") * 1000;" | |
| # Mark sleep transition | |
| - lambda: |- | |
| ESP_LOGI("lpg", "Camera entering deep sleep."); | |
| id(snapshot_marker).publish_state("asleep"); | |
| - delay: 500ms | |
| # Compute next wake time (next local 12:00) and publish/log it | |
| - lambda: |- | |
| const int day_s = 86400; | |
| if (!id(ha_time).now().is_valid()) { | |
| ESP_LOGI("lpg", "HA time not valid; cannot compute next wake precisely."); | |
| id(next_wake_epoch).publish_state("unknown"); | |
| return; | |
| } | |
| auto now = id(ha_time).now(); | |
| const int now_s = now.hour * 3600 + now.minute * 60 + now.second; | |
| const int noon_s = 12 * 3600; | |
| int delta_s = 0; | |
| if (now_s < noon_s) { | |
| delta_s = noon_s - now_s; | |
| } else { | |
| delta_s = (day_s - now_s) + noon_s; | |
| } | |
| if (delta_s < 60) delta_s = 60; | |
| const int64_t next_epoch = (int64_t) now.timestamp + (int64_t) delta_s; | |
| id(next_wake_epoch).publish_state(to_string(next_epoch)); | |
| ESP_LOGI("lpg", "Next wake scheduled in %d sec (epoch=%lld).", | |
| delta_s, (long long) next_epoch); | |
| # Enter deep sleep until next local noon (fallback: 24h - run_seconds) | |
| - deep_sleep.enter: | |
| id: deep_sleep_ctrl | |
| sleep_duration: !lambda |- | |
| const int day_s = 86400; | |
| const int run_s = atoi("${run_seconds}"); | |
| // Fallback: drift-free schedule if HA time isn't available | |
| const int fallback_sleep_s = | |
| (run_s >= day_s) ? 60 : (day_s - run_s); | |
| if (!id(ha_time).now().is_valid()) | |
| return (uint64_t)fallback_sleep_s * 1000ULL; | |
| auto now = id(ha_time).now(); | |
| const int now_s = now.hour * 3600 + now.minute * 60 + now.second; | |
| const int noon_s = 12 * 3600; | |
| int delta_s = 0; | |
| if (now_s < noon_s) { | |
| delta_s = noon_s - now_s; | |
| } else { | |
| delta_s = (day_s - now_s) + noon_s; | |
| } | |
| if (delta_s < 60) delta_s = 60; | |
| return (uint64_t)delta_s * 1000ULL; | |
| deep_sleep: | |
| id: deep_sleep_ctrl |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment