initial commit
This commit is contained in:
@@ -0,0 +1,340 @@
|
||||
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_iot_ssid
|
||||
password: !secret wifi_password
|
||||
ap:
|
||||
ssid: "${name} Fallback"
|
||||
password: !secret wifi_password
|
||||
|
||||
# Prevents aggressive reconnection attempts
|
||||
power_save_mode: none # Use 'light' for battery devices
|
||||
|
||||
# Slower but more reliable connection
|
||||
fast_connect: true
|
||||
|
||||
|
||||
# Handle connection failures gracefully
|
||||
on_connect:
|
||||
- logger.log: "Wi-Fi connected!"
|
||||
on_disconnect:
|
||||
- logger.log: "Wi-Fi disconnected!"
|
||||
|
||||
# Reduce mDNS traffic
|
||||
mdns:
|
||||
disabled: false # Set to true if you use static IPs and don't need discovery
|
||||
|
||||
#captive_portal:
|
||||
|
||||
web_server:
|
||||
port: 80
|
||||
|
||||
# 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(0x000000), TextAlign::CENTER, "Ceiling Fan");
|
||||
|
||||
// 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(0x000000), 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(0x000000), 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(0x000000), TextAlign::CENTER, "Toggle All");
|
||||
|
||||
# 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)) > 5000);'
|
||||
then:
|
||||
- light.turn_off: backlight
|
||||
- lambda: 'id(backlight_is_on) = false;'
|
||||
|
||||
# Watchdog to auto-reboot if things go wrong
|
||||
- interval: 5min
|
||||
then:
|
||||
- if:
|
||||
condition:
|
||||
and:
|
||||
- lambda: 'return millis() > 300000;' # >5 min uptime
|
||||
- not:
|
||||
wifi.connected:
|
||||
then:
|
||||
- logger.log: "Wi-Fi not connected for 5 min, rebooting..."
|
||||
- delay: 10s
|
||||
- lambda: 'App.safe_reboot();'
|
||||
|
||||
# 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
|
||||
|
||||
# ESPHome version
|
||||
- platform: version
|
||||
name: "${friendly_name} ESPHome Version"
|
||||
|
||||
# WiFi info
|
||||
- platform: wifi_info
|
||||
ip_address:
|
||||
name: "${device_name} IP Address"
|
||||
ssid:
|
||||
name: "${device_name} Connected SSID"
|
||||
bssid:
|
||||
name: "${device_name} BSSID"
|
||||
Reference in New Issue
Block a user