Files
Home-Assistant/esphome/litter-box-cam.yaml

335 lines
9.5 KiB
YAML

# =============================================================================
# LITTER BOX MONITOR - DFRobot ESP32-S3 AI Camera
# =============================================================================
substitutions:
device_name: litter-box-cam
friendly_name: "Litter Box Camera"
# -----------------------------------------------------------------------------
# Core Device Configuration
# -----------------------------------------------------------------------------
esphome:
name: ${device_name}
friendly_name: ${friendly_name}
platformio_options:
build_flags: "-DBOARD_HAS_PSRAM"
board_build.arduino.memory_type: qio_opi
esp32:
board: esp32-s3-devkitc-1
framework:
type: arduino
version: latest
flash_size: 16MB
# -----------------------------------------------------------------------------
# PSRAM Configuration (Critical for camera)
# -----------------------------------------------------------------------------
psram:
mode: octal
speed: 80MHz
# -----------------------------------------------------------------------------
# Logging & API
# -----------------------------------------------------------------------------
logger:
level: INFO
api:
encryption:
key: !secret api_encryption_key
services:
- service: capture_and_analyze
then:
- script.execute: capture_image_script
- service: update_status
variables:
needs_scooping: bool
litter_level_low: bool
cat_present: bool
cleanliness_score: int
then:
- lambda: |-
id(needs_scooping_sensor).publish_state(needs_scooping);
id(litter_level_low_sensor).publish_state(litter_level_low);
id(cat_present_sensor).publish_state(cat_present);
id(cleanliness_score_sensor).publish_state(cleanliness_score);
id(last_analysis_time).publish_state(id(homeassistant_time).now().timestamp);
if (cat_present) {
id(daily_cat_visits) += 1;
id(cat_visits_sensor).publish_state(id(daily_cat_visits));
}
ota:
- platform: esphome
password: !secret ota_password
# -----------------------------------------------------------------------------
# WiFi Configuration
# -----------------------------------------------------------------------------
wifi:
ssid: !secret wifi_iot_ssid
password: !secret wifi_password
ap:
ssid: "${device_name}-AP"
password: !secret wifi_password
captive_portal:
# -----------------------------------------------------------------------------
# MQTT Configuration
# -----------------------------------------------------------------------------
mqtt:
id: mqtt_client
broker: !secret mqtt_broker
username: !secret mqtt_username
password: !secret mqtt_password
topic_prefix: litter_box
# -----------------------------------------------------------------------------
# Time Synchronization
# -----------------------------------------------------------------------------
time:
- platform: homeassistant
id: homeassistant_time
on_time:
- seconds: 0
minutes: 0
hours: 0
then:
- lambda: |-
id(daily_cat_visits) = 0;
id(cat_visits_sensor).publish_state(0);
# -----------------------------------------------------------------------------
# Global Variables
# -----------------------------------------------------------------------------
globals:
- id: daily_cat_visits
type: int
restore_value: yes
initial_value: '0'
- id: motion_cooldown_active
type: bool
initial_value: 'false'
- id: publish_next_image
type: bool
initial_value: 'false'
# -----------------------------------------------------------------------------
# Camera Configuration (OV3660 - DFR1154 Specific)
# -----------------------------------------------------------------------------
esp32_camera:
id: litter_cam
name: "Litter Box Camera"
# Hardware Interface - DFR1154 Pinout
external_clock:
pin: GPIO5
frequency: 20MHz
i2c_pins:
sda: GPIO8
scl: GPIO9
data_pins: [GPIO16, GPIO18, GPIO21, GPIO17, GPIO14, GPIO7, GPIO6, GPIO4]
vsync_pin: GPIO1
href_pin: GPIO2
pixel_clock_pin: GPIO15
# Image Parameters
max_framerate: 10 fps
idle_framerate: 0.1 fps
resolution: 800x600
jpeg_quality: 10
vertical_flip: true
horizontal_mirror: false
aec_mode: AUTO
agc_mode: AUTO
wb_mode: AUTO
brightness: 0
contrast: 1
saturation: 0
on_image:
then:
- lambda: |-
if (!id(publish_next_image)) return;
id(publish_next_image) = false;
const uint8_t *data = x->get_data();
size_t len = x->get_data_length();
size_t out_len = 4 * ((len + 2) / 3);
char *buf = (char *)malloc(out_len + 1);
if (!buf) return;
static const char b64[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
size_t j = 0;
for (size_t i = 0; i < len; i += 3) {
uint32_t b = ((uint32_t)data[i] << 16);
if (i + 1 < len) b |= ((uint32_t)data[i + 1] << 8);
if (i + 2 < len) b |= data[i + 2];
buf[j++] = b64[(b >> 18) & 0x3F];
buf[j++] = b64[(b >> 12) & 0x3F];
buf[j++] = (i + 1 < len) ? b64[(b >> 6) & 0x3F] : '=';
buf[j++] = (i + 2 < len) ? b64[b & 0x3F] : '=';
}
buf[j] = '\0';
id(mqtt_client).publish("litter_box/camera/image", buf);
ESP_LOGI("capture", "Published image (%d bytes raw, %d base64)", len, j);
id(status_message).publish_state("Image sent for analysis");
free(buf);
# Web server for manual viewing
esp32_camera_web_server:
- port: 8080
mode: stream
- port: 8081
mode: snapshot
# -----------------------------------------------------------------------------
# GPIO Outputs
# -----------------------------------------------------------------------------
output:
- id: led_status
platform: gpio
pin: GPIO3
- id: ir_led
platform: gpio
pin: GPIO47
- id: speaker_gain
platform: gpio
pin: GPIO41
- id: speaker_mode
platform: gpio
pin: GPIO40
# -----------------------------------------------------------------------------
# Lights
# -----------------------------------------------------------------------------
light:
- platform: binary
name: "Status LED"
id: status_led
output: led_status
- platform: binary
name: "IR Night Vision"
id: ir_night_vision
output: ir_led
restore_mode: RESTORE_DEFAULT_OFF
# -----------------------------------------------------------------------------
# Binary Sensors
# -----------------------------------------------------------------------------
binary_sensor:
- platform: template
name: "Motion Detected"
id: motion_detected
device_class: motion
- platform: template
name: "Needs Scooping"
id: needs_scooping_sensor
device_class: problem
icon: "mdi:shovel"
- platform: template
name: "Litter Level Low"
id: litter_level_low_sensor
device_class: problem
icon: "mdi:cup-outline"
- platform: template
name: "Cat Present"
id: cat_present_sensor
device_class: occupancy
icon: "mdi:cat"
# -----------------------------------------------------------------------------
# Sensors
# -----------------------------------------------------------------------------
sensor:
- platform: wifi_signal
name: "WiFi Signal"
update_interval: 60s
- platform: template
name: "Cleanliness Score"
id: cleanliness_score_sensor
unit_of_measurement: "%"
icon: "mdi:star"
accuracy_decimals: 0
- platform: template
name: "Daily Cat Visits"
id: cat_visits_sensor
icon: "mdi:paw"
accuracy_decimals: 0
- platform: template
name: "Last Analysis"
id: last_analysis_time
device_class: timestamp
icon: "mdi:clock-outline"
# -----------------------------------------------------------------------------
# Text Sensors
# -----------------------------------------------------------------------------
text_sensor:
- platform: version
name: "ESPHome Version"
- platform: template
name: "Status Message"
id: status_message
icon: "mdi:message-text"
# -----------------------------------------------------------------------------
# Switches
# -----------------------------------------------------------------------------
switch:
- platform: restart
name: "Restart Camera"
- platform: template
name: "Auto Capture Enabled"
id: auto_capture_enabled
optimistic: true
restore_mode: RESTORE_DEFAULT_ON
# -----------------------------------------------------------------------------
# Buttons
# -----------------------------------------------------------------------------
button:
- platform: template
name: "Capture Now"
icon: "mdi:camera"
on_press:
- script.execute: capture_image_script
# -----------------------------------------------------------------------------
# Scripts
# -----------------------------------------------------------------------------
script:
- id: capture_image_script
mode: single
then:
- lambda: 'ESP_LOGI("capture", "Capturing image...");'
- light.turn_on: status_led
- delay: 200ms
- lambda: |-
id(publish_next_image) = true;
id(litter_cam).request_image(esphome::esp32_camera::SINGLE_SHOT);
- light.turn_off: status_led
- lambda: 'id(motion_cooldown_active) = true;'
- delay: 30s
- lambda: 'id(motion_cooldown_active) = false;'