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:
375
esphome/display_hal.yaml
Normal file
375
esphome/display_hal.yaml
Normal 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");
|
||||
Reference in New Issue
Block a user