Compare commits

..

64 Commits

Author SHA1 Message Date
Joshua King
c1934f3806 Update display initialization comments for clarity and accuracy 2026-03-07 08:40:16 -05:00
Joshua King
38b3ab8f56 Fix display orientation settings for correct portrait mode 2026-03-07 08:34:06 -05:00
Joshua King
a0e2687bf5 Fix display orientation settings for correct portrait mode 2026-03-07 08:17:59 -05:00
Joshua King
c95ad4cd2c Change display rotation from 180 to 0 degrees for correct orientation 2026-03-07 08:02:00 -05:00
Joshua King
d32f26da94 Update ILI9488 display rotation to 180 degrees for correct orientation 2026-03-06 21:38:41 -05:00
Joshua King
a4700b673e Refactor SPI handling in cat-medication-tracker to use ESP-IDF API for improved performance and reliability 2026-03-06 21:34:11 -05:00
Joshua King
3bd797bcc5 Refactor ESPHome configuration to include SPI.h via spi_helper.h 2026-03-06 21:28:10 -05:00
Joshua King
1a486240a0 Add SPI.h include to platformio build flags for ESPHome configuration 2026-03-06 21:22:23 -05:00
Joshua King
ef0841de3f Fix display orientation and update MADCTL settings for correct portrait mode 2026-03-06 21:17:53 -05:00
Joshua King
6434885ee9 Update ILI9488 display rotation to 180 degrees for correct orientation 2026-03-06 21:07:14 -05:00
Joshua King
72cc7c4524 Update ILI9488 display rotation to correct orientation 2026-03-06 21:04:49 -05:00
Joshua King
ed17ec0a4d Update ILI9488 display initialization to correct portrait orientation 2026-03-06 20:56:47 -05:00
Joshua King
a8537e087c Fix horizontal mirroring for ILI9488 display by updating MADCTL command handling 2026-03-06 20:45:08 -05:00
Joshua King
82ba5d420c Fix horizontal mirroring for ILI9488 display by updating MADCTL settings and resetting rotation to 0 2026-03-06 20:32:08 -05:00
Joshua King
a2eb2b6106 Update display rotation for ILI9488 to 180 degrees in Cat Medication Tracker 2026-03-06 20:21:57 -05:00
Joshua King
34c92deb26 Update display rotation for ILI9488 in Cat Medication Tracker 2026-03-06 20:18:07 -05:00
Joshua King
560449b1c3 Remove horizontal mirroring for ILI9488 display in Cat Medication Tracker 2026-03-06 16:22:09 -05:00
Joshua King
9e2b0b108c Enable horizontal mirroring for ILI9488 display in Cat Medication Tracker 2026-03-06 16:05:42 -05:00
Joshua King
653d708b78 Remove horizontal mirroring fix for ILI9488 display from update script 2026-03-06 15:54:26 -05:00
Joshua King
56a378f783 Fix horizontal mirroring for ILI9488 display by overriding MADCTL command 2026-03-06 15:48:09 -05:00
Joshua King
812c3fd820 Fix horizontal mirroring for ILI9488 display and update display configuration 2026-03-06 15:28:43 -05:00
Joshua King
cced711c68 Update display configuration to use ili9xxx platform and adjust rotation and mirroring settings 2026-03-06 15:22:09 -05:00
Joshua King
0569620c6c Update display rotation to 180 degrees and disable horizontal mirroring in Cat Medication Tracker 2026-03-06 15:12:30 -05:00
Joshua King
d75297fde4 Enable horizontal mirroring for display in Cat Medication Tracker 2026-03-06 14:56:49 -05:00
Joshua King
5d761acf69 Remove reset duration from display configuration and update backlight GPIO pin in Cat Medication Tracker 2026-03-06 14:42:13 -05:00
Joshua King
fd0d061f96 Update display configuration for improved stability and adjust backlight settings in Cat Medication Tracker 2026-03-06 14:26:43 -05:00
Joshua King
c684ecba49 Refactor display initialization and update configuration for improved stability in Cat Medication Tracker 2026-03-06 14:14:14 -05:00
Joshua King
1b8ec2f489 Add touchscreen support and medication control buttons in Cat Medication Tracker 2026-03-06 14:08:12 -05:00
Joshua King
495224d3eb Refactor display configuration and reduce data rate to enhance stability in Cat Medication Tracker 2026-03-06 14:05:07 -05:00
Joshua King
eea8f239fa Set display data rate to 20MHz for optimal performance in Cat Medication Tracker 2026-03-06 13:52:42 -05:00
Joshua King
6dcab86122 Refactor backlight control and update display configuration in Cat Medication Tracker 2026-03-06 13:50:48 -05:00
Joshua King
7d332586ea Adjust display data rate to 20MHz and invert backlight control in Cat Medication Tracker configuration 2026-03-06 13:37:29 -05:00
Joshua King
2220cec970 Change ESP32 framework type from esp-idf to arduino in Cat Medication Tracker configuration 2026-03-06 13:25:41 -05:00
Joshua King
ee85f43a92 Update color depth for display in Cat Medication Tracker to enhance visual quality 2026-03-06 13:11:56 -05:00
Joshua King
3a0d7da7f6 Add notes on PSRAM usage and display buffer strategy for ESP32-32E 2026-03-06 12:45:44 -05:00
Joshua King
31aaab69b7 Update display configuration in Cat Medication Tracker: change display platform to mipi_spi and adjust color depth and buffer size 2026-03-06 12:43:47 -05:00
Joshua King
a41523a7ac Enable PSRAM support for enhanced TFT framebuffer usage in Cat Medication Tracker 2026-03-06 11:58:41 -05:00
Joshua King
71f35fe822 Add image publishing functionality to Litter Box Camera: implement base64 encoding and MQTT publishing on image capture 2026-03-06 08:55:37 -05:00
Joshua King
6dd6e39c04 Update display configuration for Cat Medication Tracker: change model to ILI9488 and adjust dimensions and button placements 2026-03-06 08:49:02 -05:00
Joshua King
01e02033f6 Fix MQTT client ID configuration in Litter Box Camera setup 2026-03-05 16:39:00 -05:00
Joshua King
be876dc24d Add MQTT client ID configuration to Litter Box Camera setup 2026-03-05 16:36:36 -05:00
Joshua King
9b86dca27e Add Litter Box Camera configuration for ESP32-S3 with camera and sensor integration 2026-03-05 16:32:49 -05:00
Joshua King
b1bb0deb0e Refactor boot sequence: adjust on_boot priority and delay for backlight; change display update interval to 2s; set logger level to DEBUG 2026-03-04 20:30:51 -05:00
Joshua King
f32acd86e4 Refactor boot sequence: adjust on_boot actions for backlight and display update; change display update interval to never; update backlight control to use GPIO 2026-03-04 20:20:30 -05:00
Joshua King
45c9186f25 Refactor boot sequence: remove boot_test_mode global variable; simplify backlight control and update display logic 2026-03-04 20:02:55 -05:00
Joshua King
ccb50f1c37 Enhance boot sequence: add boot_test_mode global variable; adjust backlight delay and update display logic for test mode 2026-03-04 19:58:19 -05:00
Joshua King
0c3f065f64 Refactor boot sequence: update display component on boot and adjust touchscreen settings; set display update interval to 2s 2026-03-04 19:51:11 -05:00
Joshua King
eadb67b2fc Enhance boot sequence: turn on backlight and execute display update script; adjust touchscreen update interval to 100ms 2026-03-04 19:44:45 -05:00
Joshua King
a9b9559c23 Update touchscreen configuration: change interrupt_pin to update_interval 2026-03-04 19:18:17 -05:00
Joshua King
487f3f0207 Add color_palette setting to display configuration 2026-03-04 18:27:29 -05:00
Joshua King
7ad2a7b498 Remove draw_from_origin setting from display configuration 2026-03-04 18:19:58 -05:00
Joshua King
2681623838 Update ESP32 framework type to esp-idf and enable drawing from origin in display configuration 2026-03-04 18:17:48 -05:00
Joshua King
57f5745afe Remove unnecessary buffer_type setting from display configuration 2026-03-04 18:08:57 -05:00
Joshua King
039c3a901c Fix WiFi configuration: Update SSID to use correct secret and remove unnecessary settings; Enhance display configuration: Adjust color order, data rate, and buffer type 2026-03-04 18:05:23 -05:00
Joshua King
dd0b85137f Fix WiFi configuration: Update SSID to use correct secret and adjust fallback password settings 2026-03-04 17:37:54 -05:00
Joshua King
f0c41a6fdc Fix WiFi configuration: Update SSID to use correct secret and streamline fallback password 2026-03-04 17:29:30 -05:00
Joshua King
58e4ca6c03 Enhance touchscreen functionality: Add buttons for medication control and simplify touch event handling 2026-03-04 17:23:41 -05:00
Joshua King
4aecb61673 Enhance touchscreen handling: Add threshold for touch sensitivity and filter out phantom touches to improve accuracy 2026-03-04 17:16:44 -05:00
Joshua King
72f03f9045 Fix WiFi fallback password and update display configuration: Change fallback password to secret and disable color inversion 2026-03-04 17:06:50 -05:00
Joshua King
c43a3767dd Add Cat Medication Tracker configuration: Implement display, touchscreen, and medication state management 2026-03-04 17:04:06 -05:00
Joshua King
240fe06229 Enhance touchscreen handling: Implement backlight control on first touch and debounce touch events to improve responsiveness 2026-03-02 21:39:09 -05:00
Joshua King
0acf0ab8bf Refactor touchscreen button handling: Simplify button logic and implement service calls for light and fan control 2026-03-02 20:18:14 -05:00
d56fa9d393 updates 2026-03-02 20:14:22 -05:00
9cdc4ecba0 Refactor fan control configuration: Fix GPIO pin inversion, add fan IDs, and implement a switch for controlling all fans 2026-03-02 14:36:42 -05:00
5 changed files with 742 additions and 74 deletions

View File

@@ -0,0 +1,293 @@
substitutions:
name: cat-medication-tracker
friendly_name: "Cat Medication Tracker"
esphome:
name: ${name}
friendly_name: ${friendly_name}
includes:
- spi_helper.h
on_boot:
- priority: 100
then:
- light.turn_on: backlight
- priority: -100
then:
- lambda: |-
// MADCTL 0x48: MY=0, MX=1, BGR=1 — correct portrait, no mirror for ESP32-2432S035R
// ESP-IDF SPI master API used to bypass ESPHome's buffered display layer
spi_device_handle_t disp_fix;
spi_device_interface_config_t cfg = {};
cfg.clock_speed_hz = 1000000;
cfg.mode = 0;
cfg.spics_io_num = -1; // manual CS
cfg.queue_size = 1;
if (spi_bus_add_device(SPI2_HOST, &cfg, &disp_fix) == ESP_OK) {
gpio_set_level((gpio_num_t)15, 0); // CS low
gpio_set_level((gpio_num_t)2, 0); // DC = command
spi_transaction_t t = {};
t.length = 8;
t.flags = SPI_TRANS_USE_TXDATA;
t.tx_data[0] = 0x36; // MADCTL register
spi_device_polling_transmit(disp_fix, &t);
gpio_set_level((gpio_num_t)2, 1); // DC = data
t.tx_data[0] = 0x48; // MY=0, MX=1, BGR=1
spi_device_polling_transmit(disp_fix, &t);
gpio_set_level((gpio_num_t)15, 1); // CS high
spi_bus_remove_device(disp_fix);
}
- component.update: my_display
esp32:
board: esp32dev
framework:
type: arduino
logger:
level: DEBUG
logs:
xpt2046: WARN
api:
encryption:
key: !secret api_encryption_key
ota:
platform: esphome
password: !secret ota_password
wifi:
ssid: !secret wifi_iot_ssid
password: !secret wifi_password
ap:
ssid: "${name}-fallback"
password: !secret fallback_password
captive_portal:
time:
- platform: homeassistant
id: homeassistant_time
on_time:
- seconds: 0
minutes: 0
hours: 0
then:
- switch.turn_off: penelope_medicated
- switch.turn_off: tess_medicated
- script.execute: update_display
spi:
- id: tft_spi
clk_pin: GPIO14
mosi_pin: GPIO13
miso_pin: GPIO12
display:
- platform: mipi_spi
model: ILI9488
spi_id: tft_spi
cs_pin: GPIO15
dc_pin: GPIO2
reset_pin: GPIO4
rotation: 0
invert_colors: false
color_order: bgr
data_rate: 10MHz
dimensions:
width: 320
height: 480
id: my_display
auto_clear_enabled: false
update_interval: 2s
color_depth: 16
buffer_size: 25%
lambda: |-
// Colors
auto red = Color(255, 0, 0);
auto green = Color(0, 200, 0);
auto light_grey = Color(200, 200, 200);
auto white = Color(255, 255, 255);
auto black = Color(0, 0, 0);
auto dark_grey = Color(80, 80, 80);
// Fill background
it.fill(light_grey);
// Border: green if all done, red otherwise
bool all_done = id(penelope_medicated).state && id(tess_medicated).state;
auto border_color = all_done ? green : red;
int border = 10;
it.filled_rectangle(0, 0, 320, border, border_color);
it.filled_rectangle(0, 480 - border, 320, border, border_color);
it.filled_rectangle(0, 0, border, 480, border_color);
it.filled_rectangle(320 - border, 0, border, 480, border_color);
// Title
it.printf(160, 30, id(title_font), black, TextAlign::TOP_CENTER, "Cat Meds");
// Penelope button
int btn_x = 40;
int btn_y = 90;
int btn_w = 240;
int btn_h = 120;
auto penelope_color = id(penelope_medicated).state ? green : red;
it.filled_rectangle(btn_x, btn_y, btn_w, btn_h, penelope_color);
it.rectangle(btn_x, btn_y, btn_w, btn_h, dark_grey);
it.printf(btn_x + btn_w/2, btn_y + btn_h/2, id(button_font), white, TextAlign::CENTER, "Penelope");
if (id(penelope_medicated).state) {
it.printf(btn_x + btn_w/2, btn_y + btn_h - 20, id(status_font), white, TextAlign::CENTER, "DONE");
}
// Tess button
btn_y = 230;
auto tess_color = id(tess_medicated).state ? green : red;
it.filled_rectangle(btn_x, btn_y, btn_w, btn_h, tess_color);
it.rectangle(btn_x, btn_y, btn_w, btn_h, dark_grey);
it.printf(btn_x + btn_w/2, btn_y + btn_h/2, id(button_font), white, TextAlign::CENTER, "Tess");
if (id(tess_medicated).state) {
it.printf(btn_x + btn_w/2, btn_y + btn_h - 20, id(status_font), white, TextAlign::CENTER, "DONE");
}
// Reset button
int reset_x = 110;
int reset_y = 395;
int reset_w = 100;
int reset_h = 55;
it.filled_rectangle(reset_x, reset_y, reset_w, reset_h, dark_grey);
it.rectangle(reset_x, reset_y, reset_w, reset_h, black);
it.printf(reset_x + reset_w/2, reset_y + reset_h/2, id(status_font), white, TextAlign::CENTER, "RESET");
# XPT2046 Touchscreen
touchscreen:
- platform: xpt2046
id: my_touchscreen
spi_id: tft_spi
cs_pin: GPIO33
update_interval: 250ms
threshold: 1200
calibration:
x_min: 280
x_max: 3850
y_min: 340
y_max: 3860
# Touch buttons as binary sensors
binary_sensor:
- platform: touchscreen
touchscreen_id: my_touchscreen
name: "Penelope Button"
id: penelope_button
x_min: 40
x_max: 280
y_min: 90
y_max: 210
on_press:
then:
- switch.toggle: penelope_medicated
- platform: touchscreen
touchscreen_id: my_touchscreen
name: "Tess Button"
id: tess_button
x_min: 40
x_max: 280
y_min: 230
y_max: 350
on_press:
then:
- switch.toggle: tess_medicated
- platform: touchscreen
touchscreen_id: my_touchscreen
name: "Reset Button"
id: reset_button
x_min: 110
x_max: 210
y_min: 395
y_max: 450
on_press:
then:
- switch.turn_off: penelope_medicated
- switch.turn_off: tess_medicated
- platform: template
name: "Penelope Medication Status"
lambda: 'return id(penelope_medicated).state;'
device_class: running
- platform: template
name: "Tess Medication Status"
lambda: 'return id(tess_medicated).state;'
device_class: running
- platform: template
name: "All Cats Medicated"
lambda: 'return id(penelope_medicated).state && id(tess_medicated).state;'
device_class: running
# Backlight control
output:
- platform: gpio
pin: GPIO27
id: backlight_pwm
inverted: false
light:
- platform: binary
output: backlight_pwm
name: "${friendly_name} Backlight"
id: backlight
restore_mode: ALWAYS_ON
# Medication state switches (exposed to Home Assistant)
switch:
- platform: template
name: "Penelope Medicated"
id: penelope_medicated
optimistic: true
restore_mode: RESTORE_DEFAULT_OFF
on_turn_on:
- script.execute: update_display
on_turn_off:
- script.execute: update_display
- platform: template
name: "Tess Medicated"
id: tess_medicated
optimistic: true
restore_mode: RESTORE_DEFAULT_OFF
on_turn_on:
- script.execute: update_display
on_turn_off:
- script.execute: update_display
- platform: template
name: "Reset All Medications"
id: reset_all
optimistic: false
turn_on_action:
- switch.turn_off: penelope_medicated
- switch.turn_off: tess_medicated
# Script to update display
script:
- id: update_display
then:
- component.update: my_display
# Fonts
font:
- file: "gfonts://Roboto"
id: title_font
size: 28
- file: "gfonts://Roboto"
id: button_font
size: 24
- file: "gfonts://Roboto"
id: status_font
size: 14

View File

@@ -35,22 +35,45 @@ captive_portal:
web_server:
port: 80
# GPIO Outputs for fan control
# IMPORTANT: inverted: true because NPN transistor driver inverts the logic
# GPIO HIGH -> NPN ON -> Gate LOW -> Fan OFF
# GPIO LOW -> NPN OFF -> Gate HIGH (5V via pull-up) -> Fan ON
output:
- platform: gpio
pin:
number: GPIO3
inverted: true # Fix for driver inversion
pin:
number: GPIO3 # D1 on XIAO
inverted: true
id: fan_80mm_output
- platform: gpio
pin:
number: GPIO4
pin:
number: GPIO4 # D2 on XIAO
inverted: true
id: fan_120mm_output
# Fan entities for Home Assistant
fan:
- platform: binary
name: "80mm Exhaust Fan"
id: fan_80mm
output: fan_80mm_output
- platform: binary
name: "120mm Exhaust Fan"
output: fan_120mm_output
id: fan_120mm
output: fan_120mm_output
# Optional: Add a switch to control both fans together
switch:
- platform: template
name: "All Fans"
id: all_fans
turn_on_action:
- fan.turn_on: fan_80mm
- fan.turn_on: fan_120mm
turn_off_action:
- fan.turn_off: fan_80mm
- fan.turn_off: fan_120mm
lambda: |-
return id(fan_80mm).state && id(fan_120mm).state;

335
esphome/litter-box-cam.yaml Normal file
View File

@@ -0,0 +1,335 @@
# =============================================================================
# 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;'

View File

@@ -119,81 +119,57 @@ touchscreen:
y_min: 340
y_max: 3860
on_touch:
# Wake backlight if off
- lambda: |-
id(last_interaction_ms) = millis();
uint32_t now = millis();
id(last_interaction_ms) = now;
// Wake backlight on first touch only, don't also trigger a button
if (!id(backlight_is_on)) {
auto call = id(backlight).turn_on();
call.perform();
id(backlight_is_on) = true;
id(last_button_press_ms) = now;
return;
}
// Debounce: ignore repeated touch events within 600ms
if ((uint32_t)(now - id(last_button_press_ms)) < 600) return;
id(last_button_press_ms) = now;
ESP_LOGD("touch", "Touch at x=%d, y=%d", touch.x, touch.y);
# Button 1: Toggle All (top-left)
- if:
condition:
lambda: 'return touch.x >= 20 && touch.x <= 150 && touch.y >= 50 && touch.y <= 130;'
then:
- lambda: |-
ESP_LOGD("touch", "Toggle All pressed");
id(light1_state) = !id(light1_state);
id(light2_state) = !id(light2_state);
id(fan_state) = !id(fan_state);
id(all_state) = id(light1_state) && id(light2_state) && id(fan_state);
- homeassistant.service:
service: switch.toggle
data:
entity_id: ${light_1_entity}
- homeassistant.service:
service: switch.toggle
data:
entity_id: ${light_2_entity}
- homeassistant.service:
service: switch.toggle
data:
entity_id: ${fan_entity}
- component.update: my_display
# Button 2: Polly's Light (top-right)
- if:
condition:
lambda: 'return touch.x >= 170 && touch.x <= 300 && touch.y >= 50 && touch.y <= 130;'
then:
- lambda: |-
ESP_LOGD("touch", "Polly's Light pressed");
id(light1_state) = !id(light1_state);
id(all_state) = id(light1_state) && id(light2_state) && id(fan_state);
- homeassistant.service:
service: switch.toggle
data:
entity_id: ${light_1_entity}
- component.update: my_display
# Button 3: Joshua's Light (bottom-left)
- if:
condition:
lambda: 'return touch.x >= 20 && touch.x <= 150 && touch.y >= 150 && touch.y <= 230;'
then:
- lambda: |-
ESP_LOGD("touch", "Joshua's Light pressed");
id(light2_state) = !id(light2_state);
id(all_state) = id(light1_state) && id(light2_state) && id(fan_state);
- homeassistant.service:
service: switch.toggle
data:
entity_id: ${light_2_entity}
- component.update: my_display
# Button 4: Ceiling Fan (bottom-right)
- if:
condition:
lambda: 'return touch.x >= 170 && touch.x <= 300 && touch.y >= 150 && touch.y <= 230;'
then:
- lambda: |-
ESP_LOGD("touch", "Ceiling Fan pressed");
id(fan_state) = !id(fan_state);
id(all_state) = id(light1_state) && id(light2_state) && id(fan_state);
- homeassistant.service:
service: switch.toggle
data:
entity_id: ${fan_entity}
- component.update: my_display
// Button 1: Toggle All (top-left)
if (touch.x >= 20 && touch.x <= 150 && touch.y >= 50 && touch.y <= 130) {
ESP_LOGD("touch", "Toggle All pressed");
id(light1_state) = !id(light1_state);
id(light2_state) = !id(light2_state);
id(fan_state) = !id(fan_state);
id(all_state) = id(light1_state) && id(light2_state) && id(fan_state);
id(svc_toggle_all).execute();
// Button 2: Polly's Light (top-right)
} else if (touch.x >= 170 && touch.x <= 300 && touch.y >= 50 && touch.y <= 130) {
ESP_LOGD("touch", "Polly's Light pressed");
id(light1_state) = !id(light1_state);
id(all_state) = id(light1_state) && id(light2_state) && id(fan_state);
id(svc_toggle_polly).execute();
// Button 3: Joshua's Light (bottom-left)
} else if (touch.x >= 20 && touch.x <= 150 && touch.y >= 150 && touch.y <= 230) {
ESP_LOGD("touch", "Joshua's Light pressed");
id(light2_state) = !id(light2_state);
id(all_state) = id(light1_state) && id(light2_state) && id(fan_state);
id(svc_toggle_joshua).execute();
// Button 4: Ceiling Fan (bottom-right)
} else if (touch.x >= 170 && touch.x <= 300 && touch.y >= 150 && touch.y <= 230) {
ESP_LOGD("touch", "Ceiling Fan pressed");
id(fan_state) = !id(fan_state);
id(all_state) = id(light1_state) && id(light2_state) && id(fan_state);
id(svc_toggle_fan).execute();
}
id(my_display).update();
# Backlight control
output:
@@ -217,6 +193,41 @@ font:
id: button_font
size: 20
# Scripts to call HA services (lambdas can't call homeassistant.service directly)
script:
- id: svc_toggle_all
then:
- homeassistant.service:
service: switch.toggle
data:
entity_id: ${light_1_entity}
- homeassistant.service:
service: switch.toggle
data:
entity_id: ${light_2_entity}
- homeassistant.service:
service: switch.toggle
data:
entity_id: ${fan_entity}
- id: svc_toggle_polly
then:
- homeassistant.service:
service: switch.toggle
data:
entity_id: ${light_1_entity}
- id: svc_toggle_joshua
then:
- homeassistant.service:
service: switch.toggle
data:
entity_id: ${light_2_entity}
- id: svc_toggle_fan
then:
- homeassistant.service:
service: switch.toggle
data:
entity_id: ${fan_entity}
# Global state tracking
globals:
- id: all_state
@@ -234,6 +245,9 @@ globals:
- id: last_interaction_ms
type: uint32_t
initial_value: "0"
- id: last_button_press_ms
type: uint32_t
initial_value: "0"
- id: backlight_is_on
type: bool
initial_value: "true"

3
esphome/spi_helper.h Normal file
View File

@@ -0,0 +1,3 @@
#pragma once
#include "driver/spi_master.h"
#include "driver/gpio.h"