Update devices.yaml and state.json with new device configurations and states

- Refactored friendly names in devices.yaml for consistency and clarity.
- Added new devices and updated existing ones in devices.yaml.
- Populated state.json with detailed state information for various devices, including brightness, power, and link quality.
- Included update information for devices where applicable.
This commit is contained in:
2026-04-03 00:39:43 +02:00
parent 17883d85d9
commit bdbeec27aa
28 changed files with 6459 additions and 89839 deletions

375
esphome/display_hal.yaml Normal file
View File

@@ -0,0 +1,375 @@
substitutions:
device_name: "esp32-75epaper-hal"
friendly_name: "Epaper display hal"
comment: "7.5inch epaper display hal"
location: "hal"
api_password: !secret eink_display_api
ota_password: !secret ota_password
wifi_ssid: !secret wifi_ssid
wifi_password: !secret wifi_password
# Pins for Waveshare ePaper ESP32-S3
pin_spi_clk: GPIO7
pin_spi_mosi: GPIO9
pin_ep_cs: GPIO2
pin_ep_dc: GPIO4
pin_ep_busy: GPIO3
pin_ep_reset: GPIO1
packages:
board: !include boards/esp32-S3.yaml
device_base: !include common/common.yaml
connection: !include common/wifi_nosens.yaml
logger: !include templates/logger.yaml
esphome:
includes:
- include/epaper75.h
- include/text_utils.h
external_components:
# Use local patched waveshare component with red/black support
- source:
type: local
path: components_local
components: [ waveshare_epaper ]
sun:
latitude: !secret home_latitude
longitude: !secret home_longitude
globals:
- id: data_updated
type: bool
restore_value: no
initial_value: 'false'
- id: initial_data_received
type: bool
restore_value: no
initial_value: 'false'
- id: recorded_display_refresh
type: int
restore_value: yes
initial_value: '0'
time:
- platform: homeassistant
id: esptime
on_time_sync:
- then:
- logger.log: "Time synced with Home Assistant"
on_time:
- minutes: 5
then:
- if:
condition:
lambda: 'return id(data_updated) == true;'
then:
- lambda: 'id(initial_data_received) = true;'
- logger.log: "Sensor data updated: Refreshing display..."
- component.update: eink_display
- lambda: 'id(data_updated) = false;'
- lambda: 'id(recorded_display_refresh) += 1;'
- lambda: 'id(display_last_update).publish_state(id(esptime).now().timestamp);'
else:
- logger.log: "No sensors updated - skipping display refresh."
button:
- platform: template
name: "Hallway Display Refresh"
id: refresh
icon: "mdi:refresh"
on_press:
- logger.log: "Hallway display manual refresh"
- component.update: eink_display
# Fonts for display
font:
- file: 'fonts/GothamRnd-Bold.ttf'
id: font_date
size: 42
glyphs: &default-glyphs
['&', '@', '!', ',', '.', '"', '%', '(', ')', '+', '-', '_', ':', '°', '0',
'1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E',
'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S',
'T', 'U', 'V', 'W', 'X', 'Y', 'Z', ' ', 'a', 'b', 'c', 'd', 'e', 'f',
'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
'u', 'v', 'w', 'x', 'y', 'z','|', '/']
- file: 'fonts/GothamRnd-Bold.ttf'
id: font_temperature
size: 48
glyphs: *default-glyphs
- file: 'fonts/GothamRnd-Book.ttf'
id: font_weather_condition
size: 20
glyphs: *default-glyphs
- file: 'fonts/GothamRnd-Bold.ttf'
id: font_section_title
size: 20
glyphs: *default-glyphs
- file: 'fonts/GothamRnd-Bold.ttf'
id: font_section_header
size: 28
glyphs: *default-glyphs
- file: 'fonts/GothamRnd-Book.ttf'
id: font_status_text
size: 16
glyphs: *default-glyphs
- file: 'fonts/GothamRnd-Book.ttf'
id: font_small
size: 12
glyphs: *default-glyphs
# Material Design Icons
- file: 'fonts/materialdesignicons-webfont.ttf'
id: font_mdi_large
size: 64
glyphs:
- "\U000F0590" # mdi-weather-cloudy
- "\U000F0595" # mdi-weather-partly-cloudy
- "\U000F0599" # mdi-weather-sunny
- "\U000F0594" # mdi-weather-night
- "\U000F0597" # mdi-weather-rainy
- "\U000f010b" # mdi-car
- "\U000F0575" # mdi-lock
- "\U000F0576" # mdi-lock-open
- "\U000F0E84" # mdi-home
- "\U000F0048" # mdi-lightning-bolt
sensor:
- platform: template
name: "${device_name} Last Update"
device_class: timestamp
id: display_last_update
- platform: wifi_signal
id: sensor_wifi_signal
name: "${device_name} WiFi Signal"
update_interval: 60s
on_value:
- component.update: sensor_wifi_signal_percentage
- platform: template
id: sensor_wifi_signal_percentage
name: "${device_name} WiFi Signal Percentage"
icon: "mdi:wifi"
unit_of_measurement: "%"
update_interval: never
lambda: |-
if (id(sensor_wifi_signal).state) {
if (id(sensor_wifi_signal).state <= -100 ) {
return 0;
} else if (id(sensor_wifi_signal).state >= -50) {
return 100;
} else {
return 2 * (id(sensor_wifi_signal).state + 100);
}
} else {
return NAN;
}
- platform: template
name: "${device_name} Display Refresh Count"
lambda: 'return id(recorded_display_refresh);'
unit_of_measurement: "refreshes"
# Placeholder sensor - will be overwritten by HA include
- platform: template
name: "Weather Temperature"
id: weather_temp
unit_of_measurement: "°C"
lambda: 'return 22.0;' # Mock data
- !include { file: sensors/homeassistant.yaml, vars: { id: weather_temperature_now, entity_id: sensor.hallway_display_data }}
- !include { file: sensors/homeassistant.yaml, vars: { id: car_charge_level, entity_id: sensor.hallway_display_data }}
text_sensor:
# Pull data from Home Assistant hallway template sensor using reusable include
- !include { file: sensors/homeassistant.yaml, vars: { id: datum, entity_id: sensor.hallway_display_data }}
- !include { file: sensors/homeassistant.yaml, vars: { id: weather_condition_now, entity_id: sensor.hallway_display_data }}
- !include { file: sensors/homeassistant.yaml, vars: { id: door_status, entity_id: sensor.hallway_display_data }}
- !include { file: sensors/homeassistant.yaml, vars: { id: presence_status, entity_id: sensor.hallway_display_data }}
- !include { file: sensors/homeassistant.yaml, vars: { id: car_status, entity_id: sensor.hallway_display_data }}
- !include { file: sensors/homeassistant.yaml, vars: { id: car_range, entity_id: sensor.hallway_display_data }}
- !include { file: sensors/homeassistant.yaml, vars: { id: last_change, entity_id: sensor.hallway_display_data }}
# Primary data source - pulls from Home Assistant hallway template sensor
- platform: homeassistant
id: display_data_source
entity_id: sensor.hallway_display_data
binary_sensor:
- platform: template
name: "Door Locked"
id: door_locked
lambda: 'return true;' # Mock data
- platform: template
name: "People Home"
id: people_home
lambda: 'return true;' # Mock data
- platform: template
name: "Car Charging"
id: car_charging
lambda: 'return false;' # Mock data
# Define colors for e-paper BV2 (3-color: white background, black and red ink)
color:
- id: color_black
red: 0%
green: 0%
blue: 0%
white: 50%
- id: color_white
red: 0%
green: 0%
blue: 0%
white: 0%
- id: color_red
red: 100%
green: 0%
blue: 0%
white: 0%
# SPI configuration
spi:
clk_pin: ${pin_spi_clk}
mosi_pin: ${pin_spi_mosi}
# Display configuration
display:
- platform: waveshare_epaper
cs_pin: ${pin_ep_cs}
dc_pin: ${pin_ep_dc}
busy_pin:
number: ${pin_ep_busy}
inverted: true
reset_pin: ${pin_ep_reset}
reset_duration: 2ms
model: 7.50in-bv2-rb
update_interval: 30min
id: eink_display
rotation: 90°
lambda: |-
// Display: Waveshare 7.5" native 800x480, with rotation: 90° gives 480x800 (portrait)
int w = it.get_width(); // 480 in portrait
int h = it.get_height(); // 800 in portrait
// Fill background white
it.fill(COLOR_OFF);
// ===== LAYOUT VARIABLES =====
int margin = 8;
int inner_margin = 12;
int section_gap = 6;
int section_width = w - 2 * margin;
// Sections sized to fit 800px height
int header_y = margin;
int header_height = 140;
int door_y = header_y + header_height + section_gap;
int door_height = 110;
int car_y = door_y + door_height + section_gap;
int car_height = 110;
int presence_y = car_y + car_height + section_gap;
int presence_height = 110;
int energy_y = presence_y + presence_height + section_gap;
int footer_height = 45;
int footer_y = h - footer_height;
int energy_height = footer_y - energy_y - section_gap;
// ===== HEADER: Date & Weather =====
// Date - with fallback
if (id(datum).has_state()) {
it.printf(w / 2, header_y, id(font_date), TextAlign::TOP_CENTER, "%s", id(datum).state.c_str());
} else {
it.printf(w / 2, header_y, id(font_date), TextAlign::TOP_CENTER, "Loading...");
}
int weather_y = header_y + 55;
int icon_x = margin + 35;
it.circle(icon_x, weather_y, 25);
int temp_col_x = icon_x + 45;
if (id(weather_temperature_now).has_state()) {
it.printf(temp_col_x, weather_y - 15, id(font_temperature), TextAlign::TOP_LEFT, "%2.0f°C", id(weather_temperature_now).state);
} else {
it.printf(temp_col_x, weather_y - 15, id(font_temperature), TextAlign::TOP_LEFT, "--°C");
}
if (id(weather_condition_now).has_state()) {
it.printf(temp_col_x, weather_y + 25, id(font_weather_condition), TextAlign::TOP_LEFT, "%s", id(weather_condition_now).state.c_str());
} else {
it.printf(temp_col_x, weather_y + 25, id(font_weather_condition), TextAlign::TOP_LEFT, "---");
}
// Divider line
int divider_y = header_y + header_height - 10;
it.line(margin, divider_y, w - margin, divider_y);
// ===== SECTION 1: Door Lock Status =====
it.rectangle(margin, door_y, section_width, door_height);
it.printf(margin + inner_margin, door_y + 8, id(font_section_header), TextAlign::TOP_LEFT, "[Door]");
if (id(door_status).has_state()) {
it.printf(margin + inner_margin, door_y + 38, id(font_status_text), TextAlign::TOP_LEFT, "%s", id(door_status).state.c_str());
} else {
it.printf(margin + inner_margin, door_y + 38, id(font_status_text), TextAlign::TOP_LEFT, "Loading...");
}
// ===== SECTION 2: Presence Status =====
it.rectangle(margin, presence_y, section_width, presence_height);
it.printf(margin + inner_margin, presence_y + 8, id(font_section_header), TextAlign::TOP_LEFT, "[Presence]");
if (id(presence_status).has_state()) {
it.printf(margin + inner_margin, presence_y + 38, id(font_status_text), TextAlign::TOP_LEFT, "%s", id(presence_status).state.c_str());
} else {
it.printf(margin + inner_margin, presence_y + 38, id(font_status_text), TextAlign::TOP_LEFT, "Loading...");
}
// ===== SECTION 3: Car Battery Status =====
it.rectangle(margin, car_y, section_width, car_height);
it.printf(margin + inner_margin, car_y + 8, id(font_section_header), TextAlign::TOP_LEFT, "[Car]");
if (id(car_charge_level).has_state()) {
it.printf(margin + inner_margin, car_y + 38, id(font_status_text), TextAlign::TOP_LEFT, "Charge: %2.0f%%", id(car_charge_level).state);
} else {
it.printf(margin + inner_margin, car_y + 38, id(font_status_text), TextAlign::TOP_LEFT, "Charge: --%");
}
// ===== SECTION 4: Energy Usage =====
it.rectangle(margin, energy_y, section_width, energy_height);
it.printf(margin + inner_margin, energy_y + 10, id(font_section_header), TextAlign::TOP_LEFT, "[Energy]");
int graph_left = margin + inner_margin;
int graph_top = energy_y + 40;
int graph_width = section_width - 2 * inner_margin;
int graph_height = energy_height - 50;
int num_bars = 24;
float bar_heights[] = {
15, 28, 42, 38, 35, 48, 52, 45, 40, 55, 60, 50,
45, 58, 62, 55, 48, 65, 70, 60, 52, 68, 75, 55
};
int bar_w = (graph_width - 6) / num_bars;
for (int i = 0; i < num_bars; i++) {
int bh = (bar_heights[i] * graph_height) / 80;
int bx = graph_left + (i * bar_w) + 2;
int by = graph_top + graph_height - bh;
it.filled_rectangle(bx, by, bar_w - 1, bh);
}
// ===== FOOTER =====
it.rectangle(margin, footer_y, section_width, footer_height);
it.printf(w / 2, footer_y + 6, id(font_section_header), TextAlign::TOP_CENTER, "All Systems OK");
it.printf(w - margin - inner_margin, footer_y + 20, id(font_small), TextAlign::TOP_RIGHT, "Updated");