Files
Home-Assistant/esphome/master_bedroom_remote.yaml

294 lines
8.2 KiB
YAML

substitutions:
name: mbr-ha-remote
friendly_name: "MBR HA Remote"
# Home Assistant entity IDs - UPDATE THESE TO MATCH YOUR SETUP
light_1_entity: switch.pollys_light
light_2_entity: switch.joshuas_light
fan_entity: switch.parents_ceiling_fan
esphome:
name: ${name}
friendly_name: ${friendly_name}
on_boot:
priority: -100
then:
- lambda: |-
id(last_interaction_ms) = millis();
id(backlight_is_on) = true;
- component.update: my_display
esp32:
board: esp32dev
framework:
type: arduino
logger:
api:
encryption:
key: !secret api_encryption_key
ota:
platform: esphome
password: !secret ota_password
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
ap:
ssid: "${name} Fallback"
password: !secret wifi_password
captive_portal:
# SPI for display and touchscreen (CYD uses two separate SPI buses)
spi:
- id: tft_spi
clk_pin: GPIO14
mosi_pin: GPIO13
miso_pin: GPIO12
- id: touch_spi
clk_pin: GPIO25
mosi_pin: GPIO32
miso_pin: GPIO39
# ILI9341 Display (2.8" 320x240)
display:
- platform: ili9xxx
model: ili9341
spi_id: tft_spi
cs_pin: GPIO15
dc_pin: GPIO2
dimensions:
width: 320
height: 240
# Use palette mode to lower display buffer memory usage.
color_palette: 8BIT
data_rate: 20MHz
update_interval: never
rotation: 0
invert_colors: false
id: my_display
lambda: |-
it.fill(Color(0x1A1A2E));
it.print(160, 20, id(title_font), Color(0xFFFFFF), TextAlign::TOP_CENTER, "Room Remote");
// Button 1: All Toggle (top-left)
if (id(all_state)) {
it.filled_rectangle(20, 50, 130, 80, Color(0x4CAF50));
} else {
it.filled_rectangle(20, 50, 130, 80, Color(0x424242));
}
it.print(85, 90, id(button_font), Color(0xFFFFFF), TextAlign::CENTER, "Toggle All");
// Button 2: Light 1 (top-right)
if (id(light1_state)) {
it.filled_rectangle(170, 50, 130, 80, Color(0x4CAF50));
} else {
it.filled_rectangle(170, 50, 130, 80, Color(0x424242));
}
it.print(235, 90, id(button_font), Color(0xFFFFFF), TextAlign::CENTER, "Polly's Light");
// Button 3: Light 2 (bottom-left)
if (id(light2_state)) {
it.filled_rectangle(20, 150, 130, 80, Color(0x4CAF50));
} else {
it.filled_rectangle(20, 150, 130, 80, Color(0x424242));
}
it.print(85, 190, id(button_font), Color(0xFFFFFF), TextAlign::CENTER, "Joshua's Light");
// Button 4: Fan (bottom-right)
if (id(fan_state)) {
it.filled_rectangle(170, 150, 130, 80, Color(0x4CAF50));
} else {
it.filled_rectangle(170, 150, 130, 80, Color(0x424242));
}
it.print(235, 190, id(button_font), Color(0xFFFFFF), TextAlign::CENTER, "Ceiling Fan");
# XPT2046 Touchscreen
touchscreen:
- platform: xpt2046
spi_id: touch_spi
cs_pin: GPIO33
interrupt_pin: GPIO36
calibration:
x_min: 280
x_max: 3860
y_min: 340
y_max: 3860
on_touch:
- lambda: |-
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 (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:
- platform: ledc
pin: GPIO21
id: backlight_output
light:
- platform: monochromatic
output: backlight_output
name: "Display Backlight"
id: backlight
restore_mode: ALWAYS_ON
# Fonts
font:
- file: "gfonts://Nunito"
id: title_font
size: 24
- file: "gfonts://Nunito"
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
type: bool
initial_value: "false"
- id: light1_state
type: bool
initial_value: "false"
- id: light2_state
type: bool
initial_value: "false"
- id: fan_state
type: bool
initial_value: "false"
- 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"
# Backlight timeout
interval:
- interval: 100ms
then:
- if:
condition:
lambda: 'return id(backlight_is_on) && ((uint32_t)(millis() - id(last_interaction_ms)) > 30000);'
then:
- light.turn_off: backlight
- lambda: 'id(backlight_is_on) = false;'
# Import states from Home Assistant
text_sensor:
- platform: homeassistant
entity_id: ${light_1_entity}
id: ha_light1_state
on_value:
then:
- lambda: 'id(light1_state) = (x == "on");'
- lambda: 'id(all_state) = id(light1_state) && id(light2_state) && id(fan_state);'
- component.update: my_display
- platform: homeassistant
entity_id: ${light_2_entity}
id: ha_light2_state
on_value:
then:
- lambda: 'id(light2_state) = (x == "on");'
- lambda: 'id(all_state) = id(light1_state) && id(light2_state) && id(fan_state);'
- component.update: my_display
- platform: homeassistant
entity_id: ${fan_entity}
id: ha_fan_state
on_value:
then:
- lambda: 'id(fan_state) = (x == "on");'
- lambda: 'id(all_state) = id(light1_state) && id(light2_state) && id(fan_state);'
- component.update: my_display