Skip to content

Instantly share code, notes, and snippets.

@ohadbenita
Created February 12, 2026 14:13
Show Gist options
  • Select an option

  • Save ohadbenita/3b9ce015f5ac4b8137f677bc760c75de to your computer and use it in GitHub Desktop.

Select an option

Save ohadbenita/3b9ce015f5ac4b8137f677bc760c75de to your computer and use it in GitHub Desktop.
ESP Home Timer camera configuration
# 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