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: |- id(last_interaction_ms) = millis(); if (!id(backlight_is_on)) { auto call = id(backlight).turn_on(); call.perform(); id(backlight_is_on) = true; return; } 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: 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