# ==============================================================================
# CYD 2.8" HAMon - Home Assistant Monitor
# A customisable clock & sensor dashboard for the ESP32 Cheap Yellow Display
#
# Generated by CYD HAMon Config Generator
# https://github.com/element-software/CYD-ESPHome-HA-Monitor
#
# License: MIT
# ==============================================================================
#
# SUBSTITUTIONS - Customise your dashboard by editing the values below.
# Passwords and keys should be stored in your secrets.yaml file.
#
# ==============================================================================
substitutions:
# --- Device ---
device_name: "hamon"
friendly_name: "HAMon"
# --- Icon Glyphs ---
# List each unique icon ONCE here. If you use the same icon multiple times below,
# only include it once in this list to avoid "duplicate glyph" errors.
icon_glyphs: "\uea0b\ueffc\ue1ff\ue8ac\ue7fd\ue0f0"
# --- Color theme for Screen 1 ---
s1_bg_color: "0x0F1419"
s1_font_color: "0xFFFFFF"
# ==============================================================================
# Substitutions for Screen 1
# ==============================================================================
# --- Screen 1, R1C1 ---
s1_r1c1_entity: "sensor.whole_home_energy_usage"
s1_r1c1_type: "sensor"
s1_r1c1_label: "Energy"
s1_r1c1_icon: "\uea0b"
s1_r1c1_icon_color: "0xFFA500"
s1_r1c1_format: '%.0fW'
s1_r1c1_thresh_0: "5000"
s1_r1c1_thresh_0_color: "0xFF0000"
s1_r1c1_thresh_1: "3000"
s1_r1c1_thresh_1_color: "0xFFA500"
s1_r1c1_thresh_2: "1000"
s1_r1c1_thresh_2_color: "0x32CD32"
# --- Screen 1, R1C2 ---
s1_r1c2_entity: "binary_sensor.front_door_sensor_contact"
s1_r1c2_type: "binary"
s1_r1c2_label: "Front Door"
s1_r1c2_icon: "\ueffc"
s1_r1c2_icon_color: "0x32CD32"
s1_r1c2_state_on: "Open"
s1_r1c2_state_off: "Closed"
s1_r1c2_icon_on: "\ueffc"
s1_r1c2_icon_off: "\ueffc"
s1_r1c2_color_on: "0xFF0000"
s1_r1c2_color_off: "0x32CD32"
s1_r1c2_color_on_text: "0x000000"
# --- Screen 1, R2C1 ---
s1_r2c1_entity: "sensor.office_presence_one_temperature"
s1_r2c1_type: "sensor"
s1_r2c1_label: "Office"
s1_r2c1_icon: "\ue1ff"
s1_r2c1_icon_color: "0x00BFFF"
s1_r2c1_format: '%.1f°C'
s1_r2c1_thresh_0: "30"
s1_r2c1_thresh_0_color: "0xFF0000"
s1_r2c1_thresh_1: "25"
s1_r2c1_thresh_1_color: "0xFFA500"
s1_r2c1_thresh_2: "15"
s1_r2c1_thresh_2_color: "0x32CD32"
# --- Screen 1, R2C2 ---
s1_r2c2_entity: "switch.zigbee2mqtt_bridge_permit_join"
s1_r2c2_type: "switch"
s1_r2c2_label: "Z2M Join"
s1_r2c2_icon: "\ue8ac"
s1_r2c2_icon_color: "0x888888"
s1_r2c2_state_on: "On"
s1_r2c2_state_off: "Off"
s1_r2c2_icon_on: "\ue8ac"
s1_r2c2_icon_off: "\ue8ac"
s1_r2c2_color_on: "0x4CAF50"
s1_r2c2_color_off: "0x888888"
s1_r2c2_color_on_text: "0x000000"
# --- Screen 1, R3C1 ---
s1_r3c1_entity: "binary_sensor.garden_pano_person_occupancy"
s1_r3c1_type: "binary"
s1_r3c1_label: "Garden"
s1_r3c1_icon: "\ue7fd"
s1_r3c1_icon_color: "0x32CD32"
s1_r3c1_state_on: "Detected"
s1_r3c1_state_off: "Clear"
s1_r3c1_icon_on: "\ue7fd"
s1_r3c1_icon_off: "\ue7fd"
s1_r3c1_color_on: "0xFF0000"
s1_r3c1_color_off: "0x32CD32"
s1_r3c1_color_on_text: "0x000000"
# --- Screen 1, R3C2 ---
s1_r3c2_entity: "light.kitchen_downlights"
s1_r3c2_type: "light"
s1_r3c2_label: "Kitchen"
s1_r3c2_icon: "\ue0f0"
s1_r3c2_icon_color: "0x888888"
s1_r3c2_state_on: "On"
s1_r3c2_state_off: "Off"
s1_r3c2_icon_on: "\ue0f0"
s1_r3c2_icon_off: "\ue0f0"
s1_r3c2_color_on: "0xFFE082"
s1_r3c2_color_off: "0x888888"
s1_r3c2_color_on_text: "0x000000"
# ------------------------------------------------------------------------------
# Display: platform mipi_spi (model ILI9341). The mipi_spi driver shipped in
# ESPHome 2025.5.0 — https://esphome.io/changelog/2025.5.0/
# Legacy ili9xxx/st7735 are deprecated in favor of mipi_spi; see
# https://github.com/esphome/esphome/pull/15416
#
# Minimum ESPHome: 2025.5.0. Deprecation warnings for ili9xxx/st7735 from 2026.4.0+.
# ------------------------------------------------------------------------------
# ==============================================================================
esphome:
name: ${device_name}
friendly_name: ${friendly_name}
esp32:
board: esp32dev
framework:
type: esp-idf
logger:
# Replace with the API encryption key provided by your ESPHome instance
api:
encryption:
key: !secret hamon_api_key
# Replace with the OTA password provided by your ESPHome instance
ota:
- platform: esphome
password: !secret hamon_ota_password
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
ap:
ssid: "${friendly_name} Fallback"
password: !secret hamon_ap_password
captive_portal:
web_server:
port: 80
version: 3
output:
- platform: ledc
pin: GPIO21
id: backlight_pwm
spi:
- id: tft
clk_pin: GPIO14
mosi_pin: GPIO13
miso_pin: GPIO12
- id: touch
clk_pin: GPIO25
mosi_pin: GPIO32
miso_pin: GPIO39
display:
- platform: mipi_spi
id: my_display
model: ILI9341
spi_id: tft
cs_pin: GPIO15
dc_pin: GPIO2
auto_clear_enabled: false
invert_colors: false
color_order: RGB
dimensions:
width: 240
height: 320
offset_width: 0
offset_height: 0
pad_width: 0
pad_height: 0
transform:
swap_xy: true
mirror_y: true
mirror_x: true
touchscreen:
platform: xpt2046
id: my_touchscreen
spi_id: touch
cs_pin: GPIO33
calibration:
x_min: 220
x_max: 3756
y_min: 394
y_max: 3749
transform:
swap_xy: false
mirror_x: true
mirror_y: false
on_touch:
- lambda: |-
ESP_LOGI("touch", "Touch at LVGL (%d, %d)", touch.x, touch.y);
# --- FONTS ---
font:
- file: "gfonts://Roboto"
id: clock_font
size: 48
glyphs: '0123456789: '
- file: "gfonts://Roboto"
id: date_font
size: 20
glyphs: "0123456789/- abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
- file: "gfonts://Material Icons"
id: icon_font
size: 28
glyphs: "${icon_glyphs}"
- file: "gfonts://Roboto"
id: state_font
size: 18
glyphs: '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz .°%-/³µμÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿ'
- file: "gfonts://Roboto"
id: label_font
size: 11
glyphs: "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz .°%-/³µμÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿ"
# --- TIME ---
time:
- platform: homeassistant
id: esptime
on_time:
- seconds: 0
then:
- lvgl.label.update:
id: label_clock
text: !lambda 'return id(esptime).now().strftime("%H:%M");'
- lvgl.label.update:
id: label_date
text: !lambda |-
static const char *const dias[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
auto now = id(esptime).now();
return str_sprintf("%s %02d/%02d", dias[now.day_of_week - 1], now.day_of_month, now.month);
# --- DISPLAY PAGE CONFIG ---
lvgl:
displays:
- my_display
touchscreens:
- my_touchscreen
pages:
- id: page_s1
bg_color: ${s1_bg_color}
widgets:
- label:
id: label_clock
text: "--:--"
text_font: clock_font
text_color: ${s1_font_color}
align: TOP_MID
y: 15
- label:
id: label_date
text: "--- --/--"
text_font: date_font
text_color: ${s1_font_color}
align: TOP_MID
y: 65
# ====== ROW 1 ======
- button:
x: 2
y: 100
id: button_s1_r1c1
width: 117
height: 68
bg_opa: TRANSP
border_width: 0
shadow_width: 0
radius: 0
press_lock: false
scrollbar_mode: "OFF"
widgets:
- label:
id: icon_s1_r1c1
text: "${s1_r1c1_icon}"
text_font: icon_font
text_color: ${s1_r1c1_icon_color}
align: LEFT_MID
x: 0
clickable: false
- label:
id: lbl_s1_r1c1
text: "${s1_r1c1_label}"
text_font: label_font
text_color: ${s1_font_color}
align: LEFT_MID
x: 32
y: -10
clickable: false
- label:
id: val_s1_r1c1
text: "--"
text_font: state_font
text_color: ${s1_font_color}
align: LEFT_MID
x: 32
y: 10
clickable: false
- button:
x: 121
y: 100
id: button_s1_r1c2
width: 117
height: 68
bg_opa: TRANSP
border_width: 0
shadow_width: 0
radius: 0
press_lock: false
scrollbar_mode: "OFF"
widgets:
- label:
id: icon_s1_r1c2
text: "${s1_r1c2_icon}"
text_font: icon_font
text_color: ${s1_r1c2_icon_color}
align: LEFT_MID
x: 0
clickable: false
- label:
id: lbl_s1_r1c2
text: "${s1_r1c2_label}"
text_font: label_font
text_color: ${s1_font_color}
align: LEFT_MID
x: 32
y: -10
clickable: false
- label:
id: val_s1_r1c2
text: "--"
text_font: state_font
text_color: ${s1_font_color}
align: LEFT_MID
x: 32
y: 10
clickable: false
# ====== ROW 2 ======
- button:
x: 2
y: 170
id: button_s1_r2c1
width: 117
height: 68
bg_opa: TRANSP
border_width: 0
shadow_width: 0
radius: 0
press_lock: false
scrollbar_mode: "OFF"
widgets:
- label:
id: icon_s1_r2c1
text: "${s1_r2c1_icon}"
text_font: icon_font
text_color: ${s1_r2c1_icon_color}
align: LEFT_MID
x: 0
clickable: false
- label:
id: lbl_s1_r2c1
text: "${s1_r2c1_label}"
text_font: label_font
text_color: ${s1_font_color}
align: LEFT_MID
x: 32
y: -10
clickable: false
- label:
id: val_s1_r2c1
text: "--"
text_font: state_font
text_color: ${s1_font_color}
align: LEFT_MID
x: 32
y: 10
clickable: false
- button:
x: 121
y: 170
id: button_s1_r2c2
width: 117
height: 68
bg_opa: TRANSP
border_width: 0
shadow_width: 0
radius: 0
press_lock: false
scrollbar_mode: "OFF"
on_click:
- homeassistant.action:
action: switch.toggle
data:
entity_id: "switch.zigbee2mqtt_bridge_permit_join"
widgets:
- label:
id: icon_s1_r2c2
text: "${s1_r2c2_icon}"
text_font: icon_font
text_color: ${s1_r2c2_icon_color}
align: LEFT_MID
x: 0
clickable: false
- label:
id: lbl_s1_r2c2
text: "${s1_r2c2_label}"
text_font: label_font
text_color: ${s1_font_color}
align: LEFT_MID
x: 32
y: -10
clickable: false
- label:
id: val_s1_r2c2
text: "--"
text_font: state_font
text_color: ${s1_font_color}
align: LEFT_MID
x: 32
y: 10
clickable: false
# ====== ROW 3 ======
- button:
x: 2
y: 240
id: button_s1_r3c1
width: 117
height: 68
bg_opa: TRANSP
border_width: 0
shadow_width: 0
radius: 0
press_lock: false
scrollbar_mode: "OFF"
widgets:
- label:
id: icon_s1_r3c1
text: "${s1_r3c1_icon}"
text_font: icon_font
text_color: ${s1_r3c1_icon_color}
align: LEFT_MID
x: 0
clickable: false
- label:
id: lbl_s1_r3c1
text: "${s1_r3c1_label}"
text_font: label_font
text_color: ${s1_font_color}
align: LEFT_MID
x: 32
y: -10
clickable: false
- label:
id: val_s1_r3c1
text: "--"
text_font: state_font
text_color: ${s1_font_color}
align: LEFT_MID
x: 32
y: 10
clickable: false
- button:
x: 121
y: 240
id: button_s1_r3c2
width: 117
height: 68
bg_opa: TRANSP
border_width: 0
shadow_width: 0
radius: 0
press_lock: false
scrollbar_mode: "OFF"
on_click:
- homeassistant.action:
action: light.toggle
data:
entity_id: "light.kitchen_downlights"
widgets:
- label:
id: icon_s1_r3c2
text: "${s1_r3c2_icon}"
text_font: icon_font
text_color: ${s1_r3c2_icon_color}
align: LEFT_MID
x: 0
clickable: false
- label:
id: lbl_s1_r3c2
text: "${s1_r3c2_label}"
text_font: label_font
text_color: ${s1_font_color}
align: LEFT_MID
x: 32
y: -10
clickable: false
- label:
id: val_s1_r3c2
text: "--"
text_font: state_font
text_color: ${s1_font_color}
align: LEFT_MID
x: 32
y: 10
clickable: false
# --- BINARY SENSOR, LIGHT & INPUT BOOLEAN ENTITY STATE ---
# Binary sensors, HA light entities, and input_boolean helpers (on/off state); icon color follows state (color_on / color_off).
binary_sensor:
- platform: homeassistant
id: ha_s1_r1c2
entity_id: ${s1_r1c2_entity}
trigger_on_initial_state: true
on_state:
then:
- lvgl.label.update:
id: icon_s1_r1c2
text: !lambda |-
if (id(ha_s1_r1c2).state) return "${s1_r1c2_icon_on}";
return "${s1_r1c2_icon_off}";
- lvgl.widget.update:
id: icon_s1_r1c2
text_color: !lambda |-
if (id(ha_s1_r1c2).state) return lv_color_hex((uint32_t)${s1_r1c2_color_on});
return lv_color_hex((uint32_t)${s1_r1c2_color_off});
- lvgl.label.update:
id: val_s1_r1c2
text: !lambda |-
if (id(ha_s1_r1c2).state) return "${s1_r1c2_state_on}";
return "${s1_r1c2_state_off}";
- lvgl.widget.update:
id: val_s1_r1c2
text_color: !lambda |-
if (id(ha_s1_r1c2).state) return lv_color_hex((uint32_t)${s1_r1c2_color_on});
return lv_color_hex((uint32_t)${s1_r1c2_color_off});
- platform: homeassistant
id: ha_s1_r3c1
entity_id: ${s1_r3c1_entity}
trigger_on_initial_state: true
on_state:
then:
- lvgl.label.update:
id: icon_s1_r3c1
text: !lambda |-
if (id(ha_s1_r3c1).state) return "${s1_r3c1_icon_on}";
return "${s1_r3c1_icon_off}";
- lvgl.widget.update:
id: icon_s1_r3c1
text_color: !lambda |-
if (id(ha_s1_r3c1).state) return lv_color_hex((uint32_t)${s1_r3c1_color_on});
return lv_color_hex((uint32_t)${s1_r3c1_color_off});
- lvgl.label.update:
id: val_s1_r3c1
text: !lambda |-
if (id(ha_s1_r3c1).state) return "${s1_r3c1_state_on}";
return "${s1_r3c1_state_off}";
- lvgl.widget.update:
id: val_s1_r3c1
text_color: !lambda |-
if (id(ha_s1_r3c1).state) return lv_color_hex((uint32_t)${s1_r3c1_color_on});
return lv_color_hex((uint32_t)${s1_r3c1_color_off});
- platform: homeassistant
id: ha_s1_r3c2
entity_id: ${s1_r3c2_entity}
trigger_on_initial_state: true
on_state:
then:
- lvgl.label.update:
id: icon_s1_r3c2
text: !lambda |-
if (id(ha_s1_r3c2).state) return "${s1_r3c2_icon_on}";
return "${s1_r3c2_icon_off}";
- lvgl.widget.update:
id: icon_s1_r3c2
text_color: !lambda |-
if (id(ha_s1_r3c2).state) return lv_color_hex((uint32_t)${s1_r3c2_color_on_text});
return lv_color_hex((uint32_t)${s1_r3c2_color_off});
- lvgl.label.update:
id: val_s1_r3c2
text: !lambda |-
if (id(ha_s1_r3c2).state) return "${s1_r3c2_state_on}";
return "${s1_r3c2_state_off}";
- lvgl.widget.update:
id: val_s1_r3c2
text_color: !lambda |-
if (id(ha_s1_r3c2).state) return lv_color_hex((uint32_t)${s1_r3c2_color_on_text});
return lv_color_hex((uint32_t)${s1_r3c2_color_off});
- if:
condition:
lambda: return id(ha_s1_r3c2).state;
then:
- lvgl.widget.update:
id: button_s1_r3c2
bg_color: ${s1_r3c2_color_on}
bg_opa: COVER
else:
- lvgl.widget.update:
id: button_s1_r3c2
bg_opa: TRANSP
# --- SWITCH ENTITY STATE ---
# HA switch entities (on/off state with tap-to-toggle).
switch:
- platform: homeassistant
id: ha_s1_r2c2
entity_id: ${s1_r2c2_entity}
on_state:
then:
- lvgl.label.update:
id: icon_s1_r2c2
text: !lambda |-
if (id(ha_s1_r2c2).state) return "${s1_r2c2_icon_on}";
return "${s1_r2c2_icon_off}";
- lvgl.widget.update:
id: icon_s1_r2c2
text_color: !lambda |-
if (id(ha_s1_r2c2).state) return lv_color_hex((uint32_t)${s1_r2c2_color_on_text});
return lv_color_hex((uint32_t)${s1_r2c2_color_off});
- lvgl.label.update:
id: val_s1_r2c2
text: !lambda |-
if (id(ha_s1_r2c2).state) return "${s1_r2c2_state_on}";
return "${s1_r2c2_state_off}";
- lvgl.widget.update:
id: val_s1_r2c2
text_color: !lambda |-
if (id(ha_s1_r2c2).state) return lv_color_hex((uint32_t)${s1_r2c2_color_on_text});
return lv_color_hex((uint32_t)${s1_r2c2_color_off});
- if:
condition:
lambda: return id(ha_s1_r2c2).state;
then:
- lvgl.widget.update:
id: button_s1_r2c2
bg_color: ${s1_r2c2_color_on}
bg_opa: COVER
else:
- lvgl.widget.update:
id: button_s1_r2c2
bg_opa: TRANSP
# --- LIGHT CONFIG (display backlight) ---
light:
- platform: monochromatic
output: backlight_pwm
name: Display Backlight
id: backlight
restore_mode: ALWAYS_ON
default_transition_length: 0.5s
# --- NUMERIC SENSOR CONFIG ---
sensor:
- platform: homeassistant
id: ha_s1_r1c1
entity_id: ${s1_r1c1_entity}
on_value:
then:
- lvgl.label.update:
id: val_s1_r1c1
text: !lambda |-
return str_sprintf("${s1_r1c1_format}", x);
- lvgl.widget.update:
id: icon_s1_r1c1
text_color: !lambda |-
if (x > ${s1_r1c1_thresh_0}) return lv_color_hex(${s1_r1c1_thresh_0_color});
if (x > ${s1_r1c1_thresh_1}) return lv_color_hex(${s1_r1c1_thresh_1_color});
return lv_color_hex(${s1_r1c1_thresh_2_color});
- platform: homeassistant
id: ha_s1_r2c1
entity_id: ${s1_r2c1_entity}
on_value:
then:
- lvgl.label.update:
id: val_s1_r2c1
text: !lambda |-
return str_sprintf("${s1_r2c1_format}", x);
- lvgl.widget.update:
id: icon_s1_r2c1
text_color: !lambda |-
if (x > ${s1_r2c1_thresh_0}) return lv_color_hex(${s1_r2c1_thresh_0_color});
if (x > ${s1_r2c1_thresh_1}) return lv_color_hex(${s1_r2c1_thresh_1_color});
return lv_color_hex(${s1_r2c1_thresh_2_color});