# ============================================================================= # 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' # ----------------------------------------------------------------------------- # 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 # 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: |- auto cam = id(litter_cam); camera::Camera::FrameBuffer *fb = cam->get_image(); if (fb) { size_t out_len = 4 * ((fb->length + 2) / 3); char *base64_buffer = (char *)malloc(out_len + 1); if (base64_buffer) { size_t encoded_len = 0; int ret = mbedtls_base64_encode( (unsigned char *)base64_buffer, out_len + 1, &encoded_len, fb->buf, fb->length); if (ret == 0) { base64_buffer[encoded_len] = '\0'; id(mqtt_client).publish("litter_box/camera/image", base64_buffer); ESP_LOGI("capture", "Published image"); id(status_message).publish_state("Image sent for analysis"); } free(base64_buffer); } cam->return_image(fb); } - light.turn_off: status_led - lambda: 'id(motion_cooldown_active) = true;' - delay: 30s - lambda: 'id(motion_cooldown_active) = false;'