Here’s my config that reports the wifi signal, home assistant API connection and the temperature and humidity index from my Bambu Lab AMS.
Feel free to change the font files for your own liking.
My working config:
substitutions:
friendly_name: "ESP32-C6-LCD-1.47"
device_name: esp32-c6-lcd-147
id: esp32_c6_lcd_147
area: Home Assistant
device_description: ""
bt_proxy: "false"
logger: "DEBUG"
# logger: "VERY_VERBOSE"
vendor: "Waveshare"
model: "ESP32-C6-LCD-1-47in"
device_version: "v"
microcontroller: "ESP32-C6FH4 (QFN32) (revision v0.1)"
frequency: "160MHz"
ram: "512KB"
rom: "320KB"
hp_sram: "512KB"
lp_sram: "16KB"
flash: "4MB"
features: "WiFi 6, BT 5, Thread (802.15.4)"
crystal: "40MHz"
mac: "9c:9e:6e:ff:fe:76:51:a8"
base_mac: "9c:9e:6e:76:51:a8"
mac_ext: "ff:fe"
# i2c
i2c_id: bus_a
sda: GPIO1
scl: GPIO2
scan: "false"
# SPI bus for LCD and SD Card
miso_pin: GPIO5
mosi_pin: GPIO6
clk_pin: GPIO7
# LCD Pinout
lcd_cs_pin: GPIO14
lcd_dc_pin: GPIO15
lcd_reset_pin: GPIO21
lcd_backlight_pin: GPIO22
# SD Card
sd_cs_pin: GPIO4
# LED
rgb_led: GPIO8
# One Wire
one_wire: GPIO10
i2c:
- id: $i2c_id
sda: $sda
scl: $scl
scan: False
spi:
clk_pin: $clk_pin
miso_pin: $miso_pin
mosi_pin: $mosi_pin
one_wire:
- pin: $one_wire
platform: gpio
esp32:
board: esp32-c6-devkitc-1
variant: esp32c6
flash_size: 4MB
framework:
type: esp-idf
version: 5.3.1
platform_version: 6.9.0
sdkconfig_options:
CONFIG_ESPTOOLPY_FLASHSIZE_DETECT: y
CONFIG_ESPTOOLPY_FLASHSIZE_4MB: y
CONFIG_OPENTHREAD_ENABLED: n
CONFIG_USE_MINIMAL_MDNS: y
# Memory optimization
COMPILER_OPTIMIZATION_SIZE: y
COMPILER_OPTIMIZATION_LEVEL_RELEASE: y
CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_DISABLE: y
CONFIG_HEAP_POISONING_DISABLED: y
esphome:
friendly_name: $friendly_name
name: $id
name_add_mac_suffix: false
comment: "${device_description}"
area: $area
project:
name: "${vendor}.${model}"
version: "${device_version}"
platformio_options:
board_build.flash_mode: dio
build_flags:
- "-DI2C_NUM_1=I2C_NUM_0"
- "-DBOARD_HAS_PSRAM"
- "-Wl,-Map,output.map"
board_build.arduino.memory_type: qio_opi
platform_packages:
- platformio/toolchain-xtensa-esp-elf @ 14.2.0+20241119
logger:
level: $logger
api:
id: api_id
reboot_timeout: 300s
on_client_connected:
if:
condition:
api.connected:
then:
- logger.log:
format: "API Connection: %s [%s]"
args: ["client_info.c_str()", "client_address.c_str()"]
on_client_disconnected:
- logger.log:
format: "API Disonnection: %s [%s]"
args: ["client_info.c_str()", "client_address.c_str()"]
ota:
- platform: esphome
id: ota_esphome
safe_mode:
on_safe_mode:
- logger.log: "Safe Mode Activated"
wifi:
id: wifi_id
ssid: !secret wifi_ssid
password: !secret wifi_password
fast_connect: off
reboot_timeout: 5min
power_save_mode: HIGH
# ap:
# ssid: "${friendly_name} Fallback"
# password: !secret wifi_ap_password
# manual_ip:
# static_ip: $static_ip
# gateway: !secret gateway
# subnet: !secret subnet
# dns1: !secret dns1
# dns2: !secret dns2
# ap_timeout: 5min
on_connect:
then:
- logger.log: "WiFi connected"
on_disconnect:
then:
- logger.log: "WiFi disconnected"
# ESP-IDF ONLY https://esphome.io/components/wifi.html
# Enable 802.11v BSS Transition Management support.
enable_btm: true
# Enable 802.11k Radio Resource Management support.
enable_rrm: true
network:
enable_ipv6: False
# https://esphome.io/components/esp32_ble_tracker
# Enable Bluetooth Proxying
esp32_ble_tracker:
id: ble_tracker
scan_parameters:
interval: 1100ms
window: 1100ms
active: ${bt_proxy}
continuous: false
on_scan_end:
- then:
- lambda: |-
ESP_LOGD("ble_auto", "The scan has ended!");
# https://esphome.io/components/bluetooth_proxy.html
bluetooth_proxy:
id: ble_proxy
# Enables proxying active connections. Defaults to false.
active: ${bt_proxy}
cache_services: true
# https://esphome.io/components/esp32_improv.html
esp32_improv:
authorizer: none
on_start:
then:
- logger.log: "Improv awaiting authorization/authorized"
on_provisioning:
then:
- logger.log: "Improv provisioning"
on_provisioned:
then:
- logger.log: "Improv provisioned"
on_stop:
then:
- logger.log: "Improv stopped"
on_state:
then:
- if:
condition:
lambda: return state == improv::STATE_AUTHORIZED;
then:
- logger.log: "Improv state is STATE_AUTHORIZED"
# https://esphome.io/components/improv_serial
improv_serial:
# https://esphome.io/components/captive_portal
# captive_portal:
# deep_sleep:
# run_duration: 2min # Time the device stays awake
# sleep_duration: 28min # Time the device sleeps
text_sensor:
# ESPHome Version
- platform: version
name: "ESPHome Version"
id: "${id}_esphome_version"
icon: si:esphomes
hide_timestamp: 'true'
entity_category: diagnostic
# WiFi Info: IP, MAC, SSID, BSSID, Scan
- platform: wifi_info
ip_address:
name: "IP Address"
icon: mdi:ip
entity_category: diagnostic
update_interval: never
disabled_by_default: True
mac_address:
name: "WiFi MAC Address"
icon: mdi:fingerprint
entity_category: diagnostic
disabled_by_default: True
ssid:
name: "Connected SSID"
icon: mdi:wifi-settings
entity_category: diagnostic
update_interval: never
disabled_by_default: True
bssid:
name: "Connected BSSID"
icon: mdi:wifi
entity_category: diagnostic
update_interval: never
disabled_by_default: True
scan_results:
name: "Latest Scan Results"
icon: mdi:wifi-sync
entity_category: diagnostic
disabled_by_default: True
binary_sensor:
# https://esphome.io/components/binary_sensor/status.html
# Reports if this device is Connected or not
- platform: status
name: "Status"
id: "${id}_status"
icon: mdi:check-network
entity_category: diagnostic
button:
# Remotely reboot your node into Safe Mode.
# https://esphome.io/components/switch/safe_mode.html
- platform: safe_mode
name: "(Safe Mode)"
icon: mdi:restart-alert
id: "${id}_safe_mode"
entity_category: config
disabled_by_default: True
# Restart
- platform: restart
name: "Restart"
icon: mdi:restart
id: "${id}_restart"
# entity_category: diagnostic
disabled_by_default: True
# Shutdown
- platform: shutdown
name: "Shutdown"
id: "${id}_shutdown"
# entity_category: diagnostic
disabled_by_default: True
light:
# RGB LED
- platform: esp32_rmt_led_strip
pin:
number: $rgb_led
ignore_strapping_warning: true
name: "RGB LED"
id: rgb_led
chipset: ws2812
rmt_channel: 0
num_leds: 1
rgb_order: RGB
# gamma_correct: 2.8
default_transition_length: 2s
restore_mode: RESTORE_DEFAULT_OFF
effects:
- random:
- random:
name: "My Slow Random Effect"
transition_length: 30s
update_interval: 30s
- random:
name: "My Fast Random Effect"
transition_length: 4s
update_interval: 5s
- pulse:
- pulse:
name: "Fast Pulse"
transition_length: 0.5s
update_interval: 0.5s
min_brightness: 0%
max_brightness: 100%
- pulse:
name: "Slow Pulse"
transition_length: 500ms
update_interval: 2s
- pulse:
name: "Asymmetrical Pulse on1s off500ms"
transition_length:
on_length: 1s
off_length: 500ms
update_interval: 1.5s
- strobe:
- strobe:
name: "Strobe Effect br100 r100g90b0 250/500"
colors:
- state: true
brightness: 100%
red: 100%
green: 90%
blue: 0%
duration: 500ms
- state: false
duration: 250ms
- state: true
brightness: 100%
red: 0%
green: 100%
blue: 0%
duration: 500ms
- flicker:
- flicker:
name: "Flicker a95 i1.5"
alpha: 95%
intensity: 1.5%
- addressable_rainbow:
- addressable_rainbow:
name: "Rainbow w12"
width: 12
- addressable_rainbow:
name: "Rainbow sp10 w50"
speed: 10
width: 50
- addressable_color_wipe:
- addressable_color_wipe:
name: "Color Wipe r100g100b100 100ms"
colors:
- red: 100%
green: 100%
blue: 100%
num_leds: 1
gradient: true
- red: 0%
green: 0%
blue: 0%
num_leds: 1
add_led_interval: 100ms
reverse: false
- addressable_scan:
- addressable_scan:
name: "Scan Effect i100ms w1"
move_interval: 100ms
scan_width: 1
- addressable_twinkle:
- addressable_twinkle:
name: "Twinkle p5 i4ms"
twinkle_probability: 5%
progress_interval: 4ms #default 4ms
- addressable_twinkle:
name: "Twinkle p50 i4ms"
twinkle_probability: 50%
- addressable_random_twinkle:
- addressable_random_twinkle:
name: "Random Twinkle p50 i32ms"
twinkle_probability: 5%
progress_interval: 32ms
- addressable_fireworks:
- addressable_fireworks:
name: "Fireworks sp10% fade120 i32ms"
spark_probability: 10%
use_random_color: false
fade_out_rate: 120
update_interval: 32ms
- addressable_flicker:
- addressable_flicker:
name: Flicker Effect With Custom Values
update_interval: 16ms
intensity: 5%
- addressable_lambda:
name: "Error"
update_interval: 10ms
lambda: |-
static uint8_t brightness_step = 0;
static bool brightness_decreasing = true;
static uint8_t brightness_step_number = 10;
if (initial_run) {
brightness_step = 0;
brightness_decreasing = true;
}
Color error_color(255, 0, 0);
for (int i = 0; i < 12; i++) {
it[i] = error_color * uint8_t(255/brightness_step_number*(brightness_step_number-brightness_step));
}
if (brightness_decreasing) {
brightness_step++;
} else {
brightness_step--;
}
if (brightness_step == 0 || brightness_step == brightness_step_number) {
brightness_decreasing = !brightness_decreasing;
}
# Sync time with Home Assistant.
time:
- platform: homeassistant
id: homeassistant_time
sensor:
- platform: uptime
type: timestamp
name: Uptime
entity_category: diagnostic
# WiFi Signal sensor
- platform: wifi_signal
name: WiFi Signal
id: wifi_signal_db
icon: mdi:wifi-strength-outline
entity_category: diagnostic
update_interval: 30s
disabled_by_default: True
- platform: copy
source_id: wifi_signal_db
name: "WiFi Signal Percent"
id: wifi_signal_percent
icon: mdi:wifi-strength-outline
unit_of_measurement: "%"
filters:
- lambda: return min(max(2 * (x + 100.0), 0.0), 100.0);
entity_category: "diagnostic"
device_class: ""
disabled_by_default: True
- platform: uptime
type: seconds
name: Uptime Sensor
entity_category: diagnostic
- platform: internal_temperature
entity_category: diagnostic
name: "Internal Temperature"
- platform: homeassistant
name: AMS_Temperature
entity_id: sensor.ams_temperature
id: ams_temperature
internal: true
unit_of_measurement: "°F"
- platform: homeassistant
name: Humidity Index
entity_id: sensor.ams_humidity_index
id: ams_humidity_index
internal: true
font:
- file: "common/fonts/8-bit-nintendo.ttf"
id: nes_1x
size: 7 # Pixel Perfect
- file: "common/fonts/8-bit-nintendo.ttf"
id: nes_2x
size: 14
# - file: "common/fonts/8-bit-nintendo.ttf"
# id: nes_3x
# size: 21
# - file: "common/fonts/8-bit-nintendo.ttf"
# id: nes_4x
# size: 28
# - file: "common/fonts/gotham-Light-webfont.ttf"
# id: gotham_lt
# size: 25
- file: "common/fonts/gotham-Medium-webfont.ttf"
id: gotham_md
size: 60
# - file: "common/fonts/gotham-Black-webfont.ttf"
# id: gotham_b
# size: 30
image:
# Home Assistant Logo
- file: mdi:home-assistant
id: home_assistant_logo
# resize: 35x35
- file: mdi:home-assistant
id: home_assistant_logo_l
resize: 150x150
# WiFi
- file: mdi:signal-cellular-3
id: wifi_3
# resize: 35x35
- file: mdi:signal-cellular-2
id: wifi_2
# resize: 35x35
- file: mdi:signal-cellular-1
id: wifi_1
# resize: 35x35
- file: mdi:signal-cellular-outline
id: wifi_0
# resize: 35x35
- file: mdi:signal-off
id: wifi_off
resize: 150x150
# Temperature
- file: mdi:thermometer-low
id: temp_low
resize: 200x200
- file: mdi:thermometer
id: temp_med
resize: 200x200
- file: mdi:thermometer-high
id: temp_high
resize: 200x200
# Humidity
- file: mdi:water
id: water_drop
resize: 100x100
# - file: mdi:water-percent
# id: hum_icon
# resize: 30x30
# - file: mdi:water-percent-alert
# id: hum_icon_alert
# resize: 30x30
output:
- platform: ledc
pin: $lcd_backlight_pin
id: lcd_backlight
frequency: 1000 Hz
number:
- platform: template
name: "Display Brightness"
id: display_brightness
optimistic: true
min_value: 0
max_value: 100
step: 1
initial_value: 100
restore_value: true
mode: slider
on_value:
then:
- output.set_level:
id: lcd_backlight
level: !lambda "return x / 100.0;"
# https://esphome.io/components/display/
# TFT Display (ST7789)
display:
- platform: st7789v
cs_pin: $lcd_cs_pin
dc_pin:
number: $lcd_dc_pin
ignore_strapping_warning: true
reset_pin: $lcd_reset_pin
model: Waveshare 1.47in 172X320
id: tft_ha
rotation: 0
lambda: |-
ESP_LOGD("display", "Display update running");
auto black = Color(0, 0, 0);
auto gray = Color(128, 128, 128);
auto red = Color(255, 0, 0);
auto orange = Color(255, 165, 0);
auto yellow = Color(255, 255, 0);
auto green = Color(0, 255, 0);
auto blue = Color(0, 0, 255);
auto hablue = Color(3, 169, 244);
auto white = Color(255, 255, 255);
// Check if the HA time text sensor is available
// if (id(readable_ha_time).has_state() && id(readable_ha_time).state.length() > 0) {
// Print time from HA text sensor
// it.printf(it.get_width()/2, 10, id(nes_1x), TextAlign::TOP_CENTER, "%s", id(readable_ha_time).state.c_str());
// } else {
// Fallback to time from ESPHome's `homeassistant_time`
auto cur_time = id(homeassistant_time).now();
if (cur_time.is_valid()) {
auto hour = cur_time.hour;
auto ampm = "am";
// Convert military time to standard AM/PM format
if (hour > 12) hour = hour - 12;
if (hour >= 12) ampm = "pm";
if (hour == 0) hour = 12; // Midnight adjustment
// Print formatted time
// it.printf(it.get_width()/2, -10, id(nes_2x), TextAlign::TOP_CENTER, "%2d:%02d %s", hour, cur_time.minute, ampm);
it.printf(it.get_width()/2, -8, id(nes_2x), TextAlign::TOP_CENTER, "%2d:%02d", hour, cur_time.minute);
} else {}
// }
// Determine the appropriate WiFi icon
if (!id(wifi_signal_percent).has_state()) {
// Display WiFi off icon when disconnected
it.image(it.get_width()/2, it.get_height()/2, id(wifi_off), ImageAlign::CENTER, gray);
} else {
// Home Assistant API Connection Status
if (!id(api_id).is_connected()) {
// When disconnected, draw the icon with reduced opacity or you could skip drawing it
it.image(it.get_width()/2, it.get_height()/2, id(home_assistant_logo_l), ImageAlign::CENTER, gray); // Gray color for disconnected state
} else {
it.image(it.get_width()-2, 0, id(home_assistant_logo), ImageAlign::TOP_RIGHT, hablue);
}
// WiFi Signal Strength Status
int wifi_strength = id(wifi_signal_percent).state;
if (wifi_strength >= 75) {
it.image(5, 0, id(wifi_3), ImageAlign::TOP_LEFT, green);
} else if (wifi_strength >= 50) {
it.image(5, 0, id(wifi_2), ImageAlign::TOP_LEFT, orange);
} else if (wifi_strength >= 25) {
it.image(5, 0, id(wifi_1), ImageAlign::TOP_LEFT, yellow);
} else {
it.image(5, 0, id(wifi_0), ImageAlign::TOP_LEFT, red);
}
}
// Humidity Index
if (id(ams_humidity_index).has_state()) {
if (!isnan(id(ams_humidity_index).state)) {
//Humidity Index Status
int hum_idx = id(ams_humidity_index).state;
if (hum_idx == 1) {
it.image(it.get_width()/2 +10, it.get_height()/2, id(temp_low), ImageAlign::CENTER, blue);
it.image(it.get_width()/2 -38, it.get_height()/2 +2, id(water_drop), ImageAlign::CENTER, black);
it.image(it.get_width()/2 -40, it.get_height()/2, id(water_drop), ImageAlign::CENTER, blue);
} else if (hum_idx == 2) {
it.image(it.get_width()/2 +10, it.get_height()/2, id(temp_med), ImageAlign::CENTER, green);
it.image(it.get_width()/2 -38, it.get_height()/2 +2, id(water_drop), ImageAlign::CENTER, black);
it.image(it.get_width()/2 -40, it.get_height()/2, id(water_drop), ImageAlign::CENTER, green);
} else if (hum_idx == 3) {
it.image(it.get_width()/2 +10, it.get_height()/2, id(temp_med), ImageAlign::CENTER, yellow);
it.image(it.get_width()/2 -38, it.get_height()/2 +2, id(water_drop), ImageAlign::CENTER, black);
it.image(it.get_width()/2 -40, it.get_height()/2, id(water_drop), ImageAlign::CENTER, yellow);
} else if (hum_idx == 4) {
it.image(it.get_width()/2 +10, it.get_height()/2, id(temp_high), ImageAlign::CENTER, orange);
it.image(it.get_width()/2 -38, it.get_height()/2 +2, id(water_drop), ImageAlign::CENTER, black);
it.image(it.get_width()/2 -40, it.get_height()/2, id(water_drop), ImageAlign::CENTER, orange);
} else if (hum_idx == 5) {
it.image(it.get_width()/2 +10, it.get_height()/2, id(temp_high), ImageAlign::CENTER, red);
it.image(it.get_width()/2 -38, it.get_height()/2 +2, id(water_drop), ImageAlign::CENTER, black);
it.image(it.get_width()/2 -40, it.get_height()/2, id(water_drop), ImageAlign::CENTER, red);
} else {}
it.printf(it.get_width()/2 +10, it.get_height()/2 +40, id(gotham_md), white, TextAlign::CENTER, "%.0f", id(ams_humidity_index).state);
} else {}
} else {}
// AMS Temperature
if (id(ams_temperature).has_state()) {
if (!isnan(id(ams_temperature).state)) {
it.printf(it.get_width()/2, it.get_height()+10, id(nes_2x), white, TextAlign::BOTTOM_CENTER, "%.0f °F", id(ams_temperature).state);
} else {}
} else {}