272 lines
7.7 KiB
YAML
272 lines
7.7 KiB
YAML
esphome:
|
|
name: esdisplay-kitchen
|
|
comment: "e-Paper display in the kitchen connected to Home Assistant"
|
|
platformio_options:
|
|
lib_deps:
|
|
- bblanchon/ArduinoJson@7.4.2
|
|
includes:
|
|
- prometheus_client.h
|
|
|
|
globals:
|
|
- id: prometheus_connected
|
|
type: bool
|
|
restore_value: no
|
|
initial_value: 'true'
|
|
|
|
esp32:
|
|
board: nodemcu-32s
|
|
framework:
|
|
type: arduino
|
|
|
|
# Enable logging
|
|
logger:
|
|
|
|
# Enable Home Assistant API (auto-discovery)
|
|
api:
|
|
password: ""
|
|
|
|
# Over-The-Air updates
|
|
ota:
|
|
platform: esphome
|
|
|
|
wifi:
|
|
ssid: !env_var WIFI_SSID
|
|
password: !env_var WIFI_PASSWORD
|
|
|
|
# Enable fallback hotspot in case wifi fails
|
|
ap:
|
|
ssid: "Kitchen Display Fallback"
|
|
password: "fallbackpassword"
|
|
|
|
captive_portal:
|
|
|
|
# Time synchronization to get current time on display
|
|
time:
|
|
- platform: sntp
|
|
id: sntp_time
|
|
timezone: "Europe/Berlin"
|
|
|
|
# Define local and imported sensors
|
|
sensor:
|
|
- platform: homeassistant
|
|
id: ha_outdoor_temp
|
|
entity_id: sensor.regensburg_regensburg_temperatur
|
|
internal: true
|
|
- platform: homeassistant
|
|
id: ha_kitchen_temp
|
|
entity_id: sensor.home_kuche_wandthermostat_temperature
|
|
internal: true
|
|
|
|
# Local template sensors scraped from Prometheus
|
|
- platform: template
|
|
id: local_cellar_temp
|
|
name: "Kitchen Display Cellar Temperature Indoor"
|
|
unit_of_measurement: "°C"
|
|
accuracy_decimals: 1
|
|
- platform: template
|
|
id: local_cellar_humi
|
|
name: "Kitchen Display Cellar Humidity Indoor"
|
|
unit_of_measurement: "%"
|
|
accuracy_decimals: 0
|
|
- platform: template
|
|
id: local_cellar_dp
|
|
name: "Kitchen Display Cellar Dewpoint Indoor"
|
|
unit_of_measurement: "°C"
|
|
accuracy_decimals: 1
|
|
- platform: template
|
|
id: local_cellar_out_temp
|
|
name: "Kitchen Display Cellar Temperature Outdoor"
|
|
unit_of_measurement: "°C"
|
|
accuracy_decimals: 1
|
|
- platform: template
|
|
id: local_cellar_out_humi
|
|
name: "Kitchen Display Cellar Humidity Outdoor"
|
|
unit_of_measurement: "%"
|
|
accuracy_decimals: 0
|
|
- platform: template
|
|
id: local_cellar_out_dp
|
|
name: "Kitchen Display Cellar Dewpoint Outdoor"
|
|
unit_of_measurement: "°C"
|
|
accuracy_decimals: 1
|
|
- platform: template
|
|
id: local_fan_status
|
|
name: "Kitchen Display Cellar Fan Status"
|
|
accuracy_decimals: 0
|
|
- platform: template
|
|
id: local_mold_critical
|
|
name: "Kitchen Display Cellar Mold Critical"
|
|
accuracy_decimals: 0
|
|
- platform: template
|
|
id: local_fan_would_activate
|
|
name: "Kitchen Display Cellar Fan Activation Criteria"
|
|
accuracy_decimals: 0
|
|
- platform: template
|
|
id: local_mold_threshold
|
|
name: "Kitchen Display Cellar Mold Threshold"
|
|
unit_of_measurement: "%"
|
|
accuracy_decimals: 0
|
|
- platform: template
|
|
id: local_shelly_online
|
|
name: "Kitchen Display Cellar Shelly Online"
|
|
accuracy_decimals: 0
|
|
|
|
# Interval to scrape metrics from Prometheus every 30 seconds
|
|
interval:
|
|
- interval: 30s
|
|
then:
|
|
- lambda: |-
|
|
fetch_prometheus();
|
|
|
|
text_sensor:
|
|
- platform: homeassistant
|
|
id: ha_message
|
|
entity_id: input_text.epaper_message
|
|
internal: true
|
|
- platform: wifi_info
|
|
ip_address:
|
|
name: "IP Address"
|
|
id: wifi_ip
|
|
|
|
# Font configuration (uses Google Fonts Roboto)
|
|
font:
|
|
- file: "gfonts://Roboto"
|
|
id: font_title
|
|
size: 16
|
|
- file: "gfonts://Roboto"
|
|
id: font_body
|
|
size: 12
|
|
|
|
# SPI bus configuration for e-Paper display
|
|
spi:
|
|
clk_pin: GPIO18
|
|
mosi_pin: GPIO23
|
|
|
|
display:
|
|
- platform: waveshare_epaper
|
|
id: epaper_display
|
|
model: 2.13inv3
|
|
cs_pin: GPIO5
|
|
dc_pin: GPIO17
|
|
reset_pin: GPIO16
|
|
busy_pin: GPIO4
|
|
rotation: 90
|
|
update_interval: 60s
|
|
lambda: |-
|
|
// Fill background with white
|
|
it.fill(COLOR_OFF);
|
|
|
|
// Check if Prometheus is reachable
|
|
if (!id(prometheus_connected)) {
|
|
it.print(125, 20, id(font_title), TextAlign::CENTER, "PROMETHEUS ERROR");
|
|
it.print(125, 55, id(font_body), TextAlign::CENTER, "Cannot reach server");
|
|
it.print(125, 75, id(font_body), TextAlign::CENTER, "Check network or auth");
|
|
|
|
// Show time at the bottom if synced
|
|
it.line(5, 96, 245, 96);
|
|
char time_str[30];
|
|
auto time_now = id(sntp_time).now();
|
|
if (time_now.is_valid()) {
|
|
it.strftime(5, 100, id(font_body), "Time: %a %H:%M", time_now);
|
|
} else {
|
|
it.print(5, 100, id(font_body), "Time: --:--");
|
|
}
|
|
return;
|
|
}
|
|
|
|
// --- SECTION 1: HEADER ---
|
|
it.print(5, 2, id(font_title), "Cellar Fan (TAUPI)");
|
|
|
|
// Time and weekday on the right
|
|
auto time_now = id(sntp_time).now();
|
|
if (time_now.is_valid()) {
|
|
it.strftime(245, 2, id(font_body), TextAlign::TOP_RIGHT, "%a %H:%M", time_now);
|
|
} else {
|
|
it.print(245, 2, id(font_body), TextAlign::TOP_RIGHT, "Time: --:--");
|
|
}
|
|
|
|
// Draw header divider
|
|
it.line(5, 18, 245, 18);
|
|
|
|
// --- SECTION 2: CLIMATE CELLS ---
|
|
// Vertical separator
|
|
it.line(122, 22, 122, 82);
|
|
|
|
// CELLAR INDOOR (Left)
|
|
it.print(5, 22, id(font_body), "CELLAR INDOOR");
|
|
|
|
if (!isnan(id(local_cellar_temp).state)) {
|
|
it.printf(5, 36, id(font_body), "Temp: %.1f C", id(local_cellar_temp).state);
|
|
} else {
|
|
it.print(5, 36, id(font_body), "Temp: -- C");
|
|
}
|
|
|
|
if (!isnan(id(local_cellar_humi).state)) {
|
|
it.printf(5, 49, id(font_body), "Humi: %.0f%%", id(local_cellar_humi).state);
|
|
} else {
|
|
it.print(5, 49, id(font_body), "Humi: --%%");
|
|
}
|
|
|
|
if (!isnan(id(local_cellar_dp).state)) {
|
|
it.printf(5, 62, id(font_body), "Dewp: %.1f C", id(local_cellar_dp).state);
|
|
} else {
|
|
it.print(5, 62, id(font_body), "Dewp: -- C");
|
|
}
|
|
|
|
if (!isnan(id(local_mold_threshold).state)) {
|
|
it.printf(5, 75, id(font_body), "Limit: %.0f%%", id(local_mold_threshold).state);
|
|
} else {
|
|
it.print(5, 75, id(font_body), "Limit: --%%");
|
|
}
|
|
|
|
// CELLAR OUTDOOR (Right)
|
|
it.print(128, 22, id(font_body), "CELLAR OUTDOOR");
|
|
|
|
if (!isnan(id(local_cellar_out_temp).state)) {
|
|
it.printf(128, 36, id(font_body), "Temp: %.1f C", id(local_cellar_out_temp).state);
|
|
} else {
|
|
it.print(128, 36, id(font_body), "Temp: -- C");
|
|
}
|
|
|
|
if (!isnan(id(local_cellar_out_humi).state)) {
|
|
it.printf(128, 49, id(font_body), "Humi: %.0f%%", id(local_cellar_out_humi).state);
|
|
} else {
|
|
it.print(128, 49, id(font_body), "Humi: --%%");
|
|
}
|
|
|
|
if (!isnan(id(local_cellar_out_dp).state)) {
|
|
it.printf(128, 62, id(font_body), "Dewp: %.1f C", id(local_cellar_out_dp).state);
|
|
} else {
|
|
it.print(128, 62, id(font_body), "Dewp: -- C");
|
|
}
|
|
|
|
// --- SECTION 3: STATUS BAR (Bottom) ---
|
|
it.line(5, 86, 245, 86);
|
|
|
|
// Fan status
|
|
const char* fan_state = "OFF";
|
|
if (!isnan(id(local_fan_status).state) && id(local_fan_status).state > 0.5) {
|
|
fan_state = "ON";
|
|
}
|
|
it.printf(5, 90, id(font_body), "Fan: %s", fan_state);
|
|
|
|
// Mold status
|
|
const char* mold_state = "SAFE";
|
|
if (!isnan(id(local_mold_critical).state) && id(local_mold_critical).state > 0.5) {
|
|
mold_state = "CRITICAL";
|
|
}
|
|
it.printf(128, 90, id(font_body), "Mold: %s", mold_state);
|
|
|
|
// Criteria status
|
|
const char* crit_state = "NO";
|
|
if (!isnan(id(local_fan_would_activate).state) && id(local_fan_would_activate).state > 0.5) {
|
|
crit_state = "YES";
|
|
}
|
|
it.printf(5, 104, id(font_body), "Criteria: %s", crit_state);
|
|
|
|
// Shelly Status
|
|
const char* shelly_state = "OFFLINE";
|
|
if (!isnan(id(local_shelly_online).state) && id(local_shelly_online).state > 0.5) {
|
|
shelly_state = "ONLINE";
|
|
}
|
|
it.printf(128, 104, id(font_body), "Shelly: %s", shelly_state);
|