666 lines
21 KiB
YAML
666 lines
21 KiB
YAML
# ----------------------------------------------------------------------------------
|
|
# epaper-climate-display (ESPHome / Wemos D1 mini ESP8266 + GoodDisplay GDEY042T81)
|
|
#
|
|
# FIXED VERSION - Using LOCAL fonts to avoid compilation memory issues
|
|
# Font files located at:
|
|
# - /config/esphome/fonts/roboto/Roboto_Condensed-Regular.ttf
|
|
# - /config/esphome/fonts/roboto/Roboto_Condensed-Bold.ttf
|
|
# ----------------------------------------------------------------------------------
|
|
|
|
esphome:
|
|
name: epaper-climate-display
|
|
friendly_name: Epaper Climate Display
|
|
on_boot:
|
|
priority: 250
|
|
then:
|
|
- lambda: |-
|
|
// Boot gather (data) up to 60s
|
|
id(boot_gather_active) = true;
|
|
id(boot_gather_deadline_ms) = millis() + 60000; // 60 seconds
|
|
id(did_first_draw) = false;
|
|
|
|
// Simple boot screen shown up to 30s (or until first main draw)
|
|
id(show_boot_screen) = true;
|
|
id(boot_screen_deadline_ms) = millis() + 30000; // 30 seconds
|
|
|
|
- script.execute: boot_gather_watchdog
|
|
# Draw boot screen immediately
|
|
- component.update: epaper # Draw "Loading" screen
|
|
- delay: 200ms
|
|
- script.execute: backlight_delay # PULSE: "I'm alive and looking for WiFi/Data"
|
|
|
|
esp8266:
|
|
board: d1_mini
|
|
|
|
logger:
|
|
baud_rate: 0 # This stops the ESP8266 from "talking" over your LED pin (default option is without this line)
|
|
|
|
api:
|
|
encryption:
|
|
key: "/XNy5H5k3/hV0E8xpgFQhY2pQYMo25Y8E8Jt+9uaTgA="
|
|
reboot_timeout: 0s
|
|
|
|
ota:
|
|
- platform: esphome
|
|
password: "88fcca1cd8e29c78697cfcee032c8e1e"
|
|
|
|
wifi:
|
|
ssid: !secret wifi_ssid
|
|
password: !secret wifi_password
|
|
min_auth_mode: WPA2
|
|
fast_connect: true
|
|
power_save_mode: none
|
|
ap:
|
|
ssid: "Epaper-Climate-Display"
|
|
password: "QCaF9KQtPXUn"
|
|
|
|
captive_portal:
|
|
|
|
time:
|
|
- platform: homeassistant
|
|
id: ha_time
|
|
|
|
# --- LIGHT / PWM CONFIGURATION ---
|
|
output:
|
|
- platform: esp8266_pwm
|
|
id: backlight_pwm
|
|
pin: GPIO1 # TX Pin
|
|
frequency: 500 Hz
|
|
|
|
light:
|
|
- platform: monochromatic
|
|
id: backlight
|
|
name: "Epaper Backlight"
|
|
output: backlight_pwm
|
|
default_transition_length: 1s # All transitions take 1s by default
|
|
restore_mode: ALWAYS_OFF
|
|
|
|
# We don't use display.update_interval; we schedule refreshes via interval with quiet-hours logic.
|
|
interval:
|
|
- interval: 10min
|
|
then:
|
|
- lambda: |-
|
|
// 1. SETTINGS: Enter your pause times here
|
|
const int start_h = 23;
|
|
const int start_m = 59;
|
|
const int end_h = 4;
|
|
const int end_m = 59;
|
|
|
|
// 2. Check if time is valid
|
|
if (!id(ha_time).now().is_valid()) {
|
|
id(epaper).update();
|
|
return;
|
|
}
|
|
|
|
auto now = id(ha_time).now();
|
|
|
|
// 3. Automatic Calculations
|
|
int current_minutes = (now.hour * 60) + now.minute;
|
|
int start_pause_res = (start_h * 60) + start_m;
|
|
int end_pause_res = (end_h * 60) + end_m;
|
|
|
|
// 4. Logic check (handles midnight rollover)
|
|
bool is_paused = false;
|
|
if (start_pause_res > end_pause_res) {
|
|
// Range crosses midnight (e.g., 23:30 to 05:45)
|
|
if (current_minutes >= start_pause_res || current_minutes < end_pause_res) {
|
|
is_paused = true;
|
|
}
|
|
} else {
|
|
// Range is within one day (e.g., 13:00 to 15:00)
|
|
if (current_minutes >= start_pause_res && current_minutes < end_pause_res) {
|
|
is_paused = true;
|
|
}
|
|
}
|
|
|
|
if (is_paused) {
|
|
ESP_LOGD("display", "Scheduled refresh paused (Current: %02d:%02d)", now.hour, now.minute);
|
|
return;
|
|
}
|
|
|
|
// Trigger the E-paper update and light indication
|
|
id(epaper).update(); // 1. Refresh (Blocking)
|
|
id(refresh_indication).execute(); // 2. Pulse Light
|
|
|
|
globals:
|
|
# Boot gather controls
|
|
- id: boot_gather_active
|
|
type: bool
|
|
restore_value: no
|
|
initial_value: 'true'
|
|
|
|
- id: boot_gather_deadline_ms
|
|
type: uint32_t
|
|
restore_value: no
|
|
initial_value: '0'
|
|
|
|
- id: did_first_draw
|
|
type: bool
|
|
restore_value: no
|
|
initial_value: 'false'
|
|
|
|
# Boot screen controls (simple, no animation)
|
|
- id: show_boot_screen
|
|
type: bool
|
|
restore_value: no
|
|
initial_value: 'true'
|
|
|
|
- id: boot_screen_deadline_ms
|
|
type: uint32_t
|
|
restore_value: no
|
|
initial_value: '0'
|
|
|
|
# Cached values: room temps/humidity
|
|
- id: balcony_temp_cache
|
|
type: float
|
|
restore_value: no
|
|
initial_value: 'NAN'
|
|
- id: balcony_hum_cache
|
|
type: float
|
|
restore_value: no
|
|
initial_value: 'NAN'
|
|
|
|
- id: living_temp_cache
|
|
type: float
|
|
restore_value: no
|
|
initial_value: 'NAN'
|
|
- id: living_hum_cache
|
|
type: float
|
|
restore_value: no
|
|
initial_value: 'NAN'
|
|
|
|
- id: bathroom_temp_cache
|
|
type: float
|
|
restore_value: no
|
|
initial_value: 'NAN'
|
|
- id: bathroom_hum_cache
|
|
type: float
|
|
restore_value: no
|
|
initial_value: 'NAN'
|
|
|
|
- id: bedroom_temp_cache
|
|
type: float
|
|
restore_value: no
|
|
initial_value: 'NAN'
|
|
- id: bedroom_hum_cache
|
|
type: float
|
|
restore_value: no
|
|
initial_value: 'NAN'
|
|
|
|
- id: room_temp_cache
|
|
type: float
|
|
restore_value: no
|
|
initial_value: 'NAN'
|
|
- id: room_hum_cache
|
|
type: float
|
|
restore_value: no
|
|
initial_value: 'NAN'
|
|
|
|
- id: kitchen_temp_cache
|
|
type: float
|
|
restore_value: no
|
|
initial_value: 'NAN'
|
|
- id: kitchen_hum_cache
|
|
type: float
|
|
restore_value: no
|
|
initial_value: 'NAN'
|
|
|
|
# Cached footer sensors
|
|
- id: pressure_cache_mbar
|
|
type: float
|
|
restore_value: no
|
|
initial_value: 'NAN'
|
|
|
|
- id: battery_cache_pct
|
|
type: float
|
|
restore_value: no
|
|
initial_value: 'NAN'
|
|
|
|
- id: daily_energy_cache_kwh
|
|
type: float
|
|
restore_value: no
|
|
initial_value: 'NAN'
|
|
|
|
# Cached solar sensors
|
|
- id: daily_solar_cache_wh
|
|
type: float
|
|
restore_value: no
|
|
initial_value: 'NAN'
|
|
|
|
- id: daily_rsolar_cache_kwh
|
|
type: float
|
|
restore_value: no
|
|
initial_value: 'NAN'
|
|
|
|
script:
|
|
# Wait for values up to 60s, then do the FIRST draw once.
|
|
- id: boot_gather_watchdog
|
|
mode: restart
|
|
then:
|
|
- while:
|
|
condition:
|
|
lambda: 'return id(boot_gather_active);'
|
|
then:
|
|
- delay: 500ms
|
|
- lambda: |-
|
|
auto have = [](float v){ return !isnan(v); };
|
|
|
|
// Require all room sensors + footer sensors + new solar before "all_ok"
|
|
const bool all_ok =
|
|
have(id(balcony_temp_cache)) && have(id(balcony_hum_cache)) &&
|
|
have(id(living_temp_cache)) && have(id(living_hum_cache)) &&
|
|
have(id(bathroom_temp_cache)) && have(id(bathroom_hum_cache)) &&
|
|
have(id(bedroom_temp_cache)) && have(id(bedroom_hum_cache)) &&
|
|
have(id(room_temp_cache)) && have(id(room_hum_cache)) &&
|
|
have(id(kitchen_temp_cache)) && have(id(kitchen_hum_cache)) &&
|
|
have(id(pressure_cache_mbar)) &&
|
|
have(id(battery_cache_pct)) &&
|
|
have(id(daily_energy_cache_kwh)) &&
|
|
have(id(daily_solar_cache_wh)) &&
|
|
have(id(daily_rsolar_cache_kwh));
|
|
|
|
const bool timed_out = (millis() > id(boot_gather_deadline_ms));
|
|
|
|
if ((all_ok || timed_out) && !id(did_first_draw)) {
|
|
id(did_first_draw) = true;
|
|
id(boot_gather_active) = false;
|
|
|
|
// Stop showing boot screen once we switch to main screen.
|
|
id(show_boot_screen) = false;
|
|
|
|
// First draw now that we have data (or timed out)
|
|
id(epaper).update(); // Draw main screen
|
|
id(backlight_delay).execute(); // PULSE: "Data received, update complete"
|
|
}
|
|
|
|
# Backlight: turn ON for 10 seconds then OFF; restart extends the ON time.
|
|
- id: backlight_delay
|
|
mode: restart
|
|
then:
|
|
- light.turn_on:
|
|
id: backlight
|
|
brightness: 100%
|
|
transition_length: 500ms # Fast fade in on 500ms
|
|
- delay: 10s
|
|
- light.turn_off:
|
|
id: backlight
|
|
transition_length: 1500ms # Fades out over 2s
|
|
|
|
- id: refresh_indication
|
|
mode: restart
|
|
then:
|
|
# Simple 5-second 25% pulse
|
|
- light.turn_on:
|
|
id: backlight
|
|
brightness: 35%
|
|
transition_length: 500ms
|
|
- delay: 3s
|
|
- light.turn_off:
|
|
id: backlight
|
|
transition_length: 500ms
|
|
|
|
binary_sensor:
|
|
# Touch refresh button: force refresh anytime (including quiet hours)
|
|
- platform: gpio
|
|
id: touch_refresh
|
|
name: "Touch Refresh"
|
|
pin:
|
|
number: GPIO12 # D6
|
|
mode:
|
|
input: true
|
|
pullup: true
|
|
filters:
|
|
- delayed_on: 20ms
|
|
- delayed_off: 20ms
|
|
on_press:
|
|
then:
|
|
- lambda: |-
|
|
ESP_LOGI("touch", "Manual refresh requested");
|
|
- component.update: epaper # 1. Refresh the screen (Blocking)
|
|
- script.execute: refresh_indication # 2. Pulse the light (Starts after screen is done)
|
|
|
|
# Touch light button: start backlight timer (no toggle)
|
|
- platform: gpio
|
|
id: touch_light
|
|
name: "Touch Light"
|
|
pin:
|
|
number: GPIO3 # RX
|
|
mode:
|
|
input: true
|
|
pullup: true
|
|
filters:
|
|
- delayed_on: 20ms
|
|
- delayed_off: 20ms
|
|
on_press:
|
|
then:
|
|
- lambda: |-
|
|
ESP_LOGI("touch", "Backlight requested (preset seconds)");
|
|
- script.execute: backlight_delay
|
|
|
|
sensor:
|
|
# --- Room sensors (Home Assistant) ---
|
|
- platform: homeassistant
|
|
id: balcony_temp
|
|
entity_id: sensor.indoor_outdoor_meter_f7c8_temperatura
|
|
on_value: { then: [ lambda: 'id(balcony_temp_cache) = x;' ] }
|
|
|
|
- platform: homeassistant
|
|
id: balcony_hum
|
|
entity_id: sensor.indoor_outdoor_meter_f7c8_vlaznost
|
|
on_value: { then: [ lambda: 'id(balcony_hum_cache) = x;' ] }
|
|
|
|
- platform: homeassistant
|
|
id: living_temp
|
|
entity_id: sensor.indoor_outdoor_meter_f965_temperatura
|
|
on_value: { then: [ lambda: 'id(living_temp_cache) = x;' ] }
|
|
|
|
- platform: homeassistant
|
|
id: living_hum
|
|
entity_id: sensor.indoor_outdoor_meter_f965_vlaznost
|
|
on_value: { then: [ lambda: 'id(living_hum_cache) = x;' ] }
|
|
|
|
- platform: homeassistant
|
|
id: bathroom_temp
|
|
entity_id: sensor.meter_plus_3550_temperatura
|
|
on_value: { then: [ lambda: 'id(bathroom_temp_cache) = x;' ] }
|
|
|
|
- platform: homeassistant
|
|
id: bathroom_hum
|
|
entity_id: sensor.meter_plus_3550_vlaznost
|
|
on_value: { then: [ lambda: 'id(bathroom_hum_cache) = x;' ] }
|
|
|
|
- platform: homeassistant
|
|
id: bedroom_temp
|
|
entity_id: sensor.meter_plus_080d_temperatura
|
|
on_value: { then: [ lambda: 'id(bedroom_temp_cache) = x;' ] }
|
|
|
|
- platform: homeassistant
|
|
id: bedroom_hum
|
|
entity_id: sensor.meter_plus_080d_vlaznost
|
|
on_value: { then: [ lambda: 'id(bedroom_hum_cache) = x;' ] }
|
|
|
|
- platform: homeassistant
|
|
id: room_temp
|
|
entity_id: sensor.meter_plus_f010_temperatura
|
|
on_value: { then: [ lambda: 'id(room_temp_cache) = x;' ] }
|
|
|
|
- platform: homeassistant
|
|
id: room_hum
|
|
entity_id: sensor.meter_plus_f010_vlaznost
|
|
on_value: { then: [ lambda: 'id(room_hum_cache) = x;' ] }
|
|
|
|
- platform: homeassistant
|
|
id: kitchen_temp
|
|
entity_id: sensor.meter_plus_86aa_temperatura
|
|
on_value: { then: [ lambda: 'id(kitchen_temp_cache) = x;' ] }
|
|
|
|
- platform: homeassistant
|
|
id: kitchen_hum
|
|
entity_id: sensor.meter_plus_86aa_vlaznost
|
|
on_value: { then: [ lambda: 'id(kitchen_hum_cache) = x;' ] }
|
|
|
|
# --- Footer sensors (Home Assistant) ---
|
|
- platform: homeassistant
|
|
id: pressure_mbar
|
|
entity_id: sensor.arso_weather_brezice_zracni_tlak
|
|
on_value:
|
|
then:
|
|
- lambda: id(pressure_cache_mbar) = x;
|
|
|
|
- platform: homeassistant
|
|
id: battery_pct
|
|
entity_id: sensor.delta_2_max_0110_battery_level
|
|
on_value:
|
|
then:
|
|
- lambda: id(battery_cache_pct) = x;
|
|
|
|
- platform: homeassistant
|
|
id: daily_energy_kwh
|
|
entity_id: sensor.daily_household_power_usage
|
|
on_value:
|
|
then:
|
|
- lambda: id(daily_energy_cache_kwh) = x;
|
|
|
|
# --- Solar sensor ---
|
|
- platform: homeassistant
|
|
id: daily_solar_wh
|
|
entity_id: sensor.ecoflow_daily_solar_power
|
|
on_value:
|
|
then:
|
|
- lambda: id(daily_solar_cache_wh) = x;
|
|
|
|
# --- Solar sensor (new remote panel forecast) ---
|
|
- platform: homeassistant
|
|
id: daily_rsolar_kwh
|
|
entity_id: sensor.solcast_sc_daily_measurment_no_negative_eprices
|
|
on_value:
|
|
then:
|
|
- lambda: 'id(daily_rsolar_cache_kwh) = x;'
|
|
|
|
spi:
|
|
clk_pin: GPIO14
|
|
mosi_pin: GPIO13
|
|
|
|
# Fonts using LOCAL Roboto Condensed files
|
|
font:
|
|
- file: fonts/roboto/Roboto_Condensed-Regular.ttf
|
|
id: font_title
|
|
size: 20
|
|
glyphs:
|
|
- "! 0123456789" # space and numbers
|
|
- "ABCDEFGHIJKLMNOPQRSTUVWXYZČŠŽ" # uppercase
|
|
- "abcdefghijklmnopqrstuvwxyzčšž" # lowercase
|
|
- '.,-:;()' # punctuation
|
|
|
|
- file: fonts/roboto/Roboto_Condensed-Bold.ttf
|
|
id: font_temp
|
|
size: 42
|
|
glyphs:
|
|
- ' ' # space
|
|
- '0123456789,.-'
|
|
- "ABCDEFGHIJKLMNOPQRSTUVWXYZČŠŽ"
|
|
- "abcdefghijklmnopqrstuvwxyzčšž"
|
|
|
|
- file: fonts/roboto/Roboto_Condensed-Bold.ttf
|
|
id: font_hum
|
|
size: 30
|
|
glyphs:
|
|
- ' ' # space
|
|
- '0123456789,.'
|
|
- "ABCDEFGHIJKLMNOPQRSTUVWXYZČŠŽ"
|
|
- "abcdefghijklmnopqrstuvwxyzčšž"
|
|
- '%'
|
|
|
|
- file: fonts/roboto/Roboto_Condensed-Regular.ttf
|
|
id: font_unit
|
|
size: 22
|
|
glyphs:
|
|
- ' C%'
|
|
- "\u00B0" # degree symbol (°)
|
|
|
|
- file: fonts/roboto/Roboto_Condensed-Regular.ttf
|
|
id: font_footer
|
|
size: 18
|
|
glyphs:
|
|
- '0123456789'
|
|
- "ABCDEFGHIJKLMNOPQRSTUVWXYZČŠŽ"
|
|
- "abcdefghijklmnopqrstuvwxyzčšž"
|
|
- ' .,/:;()-%|>'
|
|
|
|
# Material Design Icons
|
|
- file: fonts/materialdesignicons-webfont.ttf
|
|
id: font_mdi
|
|
size: 18
|
|
glyphs:
|
|
- "\U000F050F" # mdi-thermometer
|
|
- "\U000F0080" # mdi-battery
|
|
- "\U000F140B" # mdi-lightning-bolt
|
|
- "\U000F1A73" # mdi-solar-panel
|
|
- "\U000F0D9B" # mdi-solar-power-variant (NEW)
|
|
|
|
- file: fonts/materialdesignicons-webfont.ttf
|
|
id: font_boot
|
|
size: 40
|
|
glyphs:
|
|
- "\U000F050F" # mdi-thermometer (temperature)
|
|
- "\U000F0080" # mdi-battery (battery)
|
|
- "\U000F140B" # mdi-lightning-bolt (energy)
|
|
- "\U000F1A73" # mdi-solar-panel (solar)
|
|
- "\U000F0D9B" # mdi-solar-power-variant (NEW)
|
|
|
|
display:
|
|
- platform: waveshare_epaper
|
|
id: epaper
|
|
model: gdey042t81
|
|
cs_pin: GPIO15
|
|
dc_pin: GPIO4
|
|
reset_pin: GPIO5
|
|
busy_pin: GPIO16
|
|
update_interval: never
|
|
full_update_every: 1
|
|
rotation: 0
|
|
|
|
lambda: |-
|
|
const int w = it.get_width(); // 400
|
|
const int h = it.get_height(); // 300
|
|
|
|
// --- BOOT SCREEN (simple, no animation) ---
|
|
if (id(show_boot_screen) && (millis() <= id(boot_screen_deadline_ms)) && !id(did_first_draw)) {
|
|
it.fill(COLOR_OFF);
|
|
it.printf(w/2, 50, id(font_boot), TextAlign::TOP_CENTER, "\U000F1A73\U000F140B\U000F0080\U000F050F");
|
|
it.printf(w/2, 110, id(font_temp), TextAlign::TOP_CENTER, "HA E-ZASLON");
|
|
it.printf(w/2, 190, id(font_hum), TextAlign::TOP_CENTER, "Pridobivam podatke");
|
|
it.printf(w/2, 230, id(font_title), TextAlign::TOP_CENTER, "To bo trajalo nekaj sekund");
|
|
return;
|
|
}
|
|
|
|
// --- MAIN GRID SCREEN ---
|
|
const int margin = 8;
|
|
const int gap = 6;
|
|
const int footer_line_h = 20;
|
|
const int footer_h = footer_line_h * 2 + 6;
|
|
const int grid_top = margin;
|
|
const int grid_bottom = h - footer_h - margin;
|
|
const int cols = 3;
|
|
const int rows = 2;
|
|
const int grid_h = grid_bottom - grid_top;
|
|
const int cell_w = (w - 2*margin - (cols-1)*gap) / cols;
|
|
const int cell_h = (grid_h - (rows-1)*gap) / rows;
|
|
|
|
auto cell_x = [&](int c) { return margin + c * (cell_w + gap); };
|
|
auto cell_y = [&](int r) { return grid_top + r * (cell_h + gap); };
|
|
|
|
auto fmt_temp = [&](float v) -> std::string {
|
|
if (isnan(v)) return std::string("--,-");
|
|
char buf[16];
|
|
snprintf(buf, sizeof(buf), "%.1f", v);
|
|
std::string out(buf);
|
|
for (auto &ch : out) if (ch == '.') ch = ',';
|
|
return out;
|
|
};
|
|
|
|
auto fmt_hum = [&](float v) -> std::string {
|
|
if (isnan(v)) return std::string("--");
|
|
char buf[16];
|
|
snprintf(buf, sizeof(buf), "%.0f", v);
|
|
return std::string(buf);
|
|
};
|
|
|
|
auto fmt_pressure = [&](float v) -> std::string {
|
|
if (isnan(v)) return std::string("--");
|
|
char buf[20];
|
|
snprintf(buf, sizeof(buf), "%.0f mbar", v);
|
|
return std::string(buf);
|
|
};
|
|
|
|
auto fmt_battery = [&](float v) -> std::string {
|
|
if (isnan(v)) return std::string("--");
|
|
char buf[20];
|
|
snprintf(buf, sizeof(buf), "%.0f%%", v);
|
|
return std::string(buf);
|
|
};
|
|
|
|
auto fmt_energy = [&](float v) -> std::string {
|
|
if (isnan(v)) return std::string("--");
|
|
char buf[24];
|
|
snprintf(buf, sizeof(buf), "%.2f kWh", v);
|
|
return std::string(buf);
|
|
};
|
|
|
|
auto fmt_solar = [&](float v) -> std::string {
|
|
if (isnan(v)) return std::string("--");
|
|
char buf[24];
|
|
snprintf(buf, sizeof(buf), "%.0f Wh", v);
|
|
return std::string(buf);
|
|
};
|
|
|
|
auto fmt_rsolar = [&](float v) -> std::string {
|
|
if (isnan(v)) return std::string("--");
|
|
char buf[24];
|
|
snprintf(buf, sizeof(buf), "%.2f kWh", v);
|
|
return std::string(buf);
|
|
};
|
|
|
|
it.fill(COLOR_OFF);
|
|
int v1 = cell_x(1) - gap/2;
|
|
int v2 = cell_x(2) - gap/2;
|
|
it.line(v1, grid_top, v1, grid_bottom, COLOR_ON);
|
|
it.line(v2, grid_top, v2, grid_bottom, COLOR_ON);
|
|
int h1 = cell_y(1) - gap/2;
|
|
it.line(margin, h1, w - margin, h1, COLOR_ON);
|
|
|
|
auto draw_cell = [&](int c, int r, const char* name, float t_cache, float h_cache) {
|
|
int x = cell_x(c);
|
|
int y = cell_y(r);
|
|
it.filled_rectangle(x, y, cell_w, cell_h, COLOR_OFF);
|
|
it.printf(x + 10, y + 3, id(font_title), TextAlign::TOP_LEFT, "%s", name);
|
|
const std::string t_str = fmt_temp(t_cache);
|
|
it.printf(x + 10, y + 22, id(font_temp), TextAlign::TOP_LEFT, "%s", t_str.c_str());
|
|
int tbx=0, tby=0, tbw=0, tbh=0;
|
|
it.get_text_bounds(0, 0, t_str.c_str(), id(font_temp), TextAlign::TOP_LEFT, &tbx, &tby, &tbw, &tbh);
|
|
it.printf(x + 10 + tbw + 3, y + 22 + 12, id(font_unit), TextAlign::TOP_LEFT, "%s", "\xC2\xB0" "C");
|
|
it.printf(x + 10, y + cell_h - 34, id(font_hum), TextAlign::TOP_LEFT, "%s%%", fmt_hum(h_cache).c_str());
|
|
};
|
|
|
|
draw_cell(0, 0, "Balkon", id(balcony_temp_cache), id(balcony_hum_cache));
|
|
draw_cell(1, 0, "Dnevna soba", id(living_temp_cache), id(living_hum_cache));
|
|
draw_cell(2, 0, "Kopalnica", id(bathroom_temp_cache), id(bathroom_hum_cache));
|
|
draw_cell(0, 1, "Spalnica", id(bedroom_temp_cache), id(bedroom_hum_cache));
|
|
draw_cell(1, 1, "Soba", id(room_temp_cache), id(room_hum_cache));
|
|
draw_cell(2, 1, "Kuhinja", id(kitchen_temp_cache), id(kitchen_hum_cache));
|
|
|
|
const int footer_y = h - footer_h;
|
|
it.filled_rectangle(0, footer_y, w, footer_h, COLOR_OFF);
|
|
const int line1_y = footer_y + 2;
|
|
const int line2_y = footer_y + footer_line_h + 4;
|
|
|
|
// Footer line 1
|
|
it.printf(margin + 0, line1_y, id(font_mdi), TextAlign::TOP_LEFT, "\U000F050F");
|
|
it.printf(margin + 26, line1_y + 1, id(font_footer), TextAlign::TOP_LEFT, "%s", fmt_pressure(id(pressure_cache_mbar)).c_str());
|
|
|
|
it.printf(margin + 127, line1_y, id(font_mdi), TextAlign::TOP_LEFT, "\U000F0080");
|
|
it.printf(margin + 153, line1_y + 1, id(font_footer), TextAlign::TOP_LEFT, "%s", fmt_battery(id(battery_cache_pct)).c_str());
|
|
|
|
it.printf(margin + 258, line1_y, id(font_mdi), TextAlign::TOP_LEFT, "\U000F140B");
|
|
it.printf(margin + 284, line1_y + 1, id(font_footer), TextAlign::TOP_LEFT, "%s", fmt_energy(id(daily_energy_cache_kwh)).c_str());
|
|
|
|
// Footer line 2
|
|
// Left: Solar Local
|
|
it.printf(margin + 0, line2_y, id(font_mdi), TextAlign::TOP_LEFT, "\U000F1A73");
|
|
it.printf(margin + 26, line2_y + 1, id(font_footer), TextAlign::TOP_LEFT, "%s", fmt_solar(id(daily_solar_cache_wh)).c_str());
|
|
|
|
// Center: Remote Solar panel forecast (New)
|
|
it.printf(margin + 127, line2_y, id(font_mdi), TextAlign::TOP_LEFT, "\U000F0D9B");
|
|
it.printf(margin + 153, line2_y + 1, id(font_footer), TextAlign::TOP_LEFT, "%s", fmt_rsolar(id(daily_rsolar_cache_kwh)).c_str());
|
|
|
|
// Right: Updated timestamp (Restored original style but with 2-digit year)
|
|
std::string updated = "Osveženo: --";
|
|
if (id(ha_time).now().is_valid()) {
|
|
auto now = id(ha_time).now();
|
|
char buf[80];
|
|
snprintf(buf, sizeof(buf), "%d. %d. %02d, %d:%02d",
|
|
now.day_of_month, now.month, (now.year % 100), now.hour, now.minute);
|
|
updated = std::string(buf);
|
|
}
|
|
it.printf(margin + 256, line2_y, id(font_footer), TextAlign::TOP_LEFT, "|");
|
|
it.printf(w - margin, line2_y + 1, id(font_footer), TextAlign::TOP_RIGHT, "%s", updated.c_str()); |