Public Access
Driver (components/el512/el512_panel.*, ESP-IDF only): - DMA ring of 8 x 4 lines (17 KB) refilled from the I2S EOF interrupt (IRAM) instead of a 142 KB full-frame stream; ~107 KB heap freed. - 256-entry lookup table encodes 8 pixels per step. - Back buffer + displayed buffer, swapped at the start of a frame. - PlatformIO builds it via lib_extra_dirs (EL512Core); lib/EL512 keeps the Adafruit GFX front end. ESPHome external component (platform: el512): - DisplayBuffer with optional ENABLE / _LOWPWR pins, set_enabled(), set_low_power(), helper functions for page lambdas. - still_alive_page: plays the Portal end-credits show, then returns to the previous page. Still Alive engine moved to components/el512/still_alive.* with a built-in 5x7 font (from Adafruit GFX, BSD); the PlatformIO demo uses the same code. The importer writes components/el512/still_alive_data.h and stops at 200 s (--until-ms), matching the hand-trimmed data. esphome/el512-panel.yaml: 8 pages (clock, forecast, agenda, dashboard, now playing, Plex, solar, Still Alive), page select, rotation, display and low-power switches, message text. home_assistant/packages/el512_panel.yaml: helper template sensors for forecast, agenda, now playing and Plex streams. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
109 lines
3.4 KiB
C++
109 lines
3.4 KiB
C++
// Small helpers for page lambdas (available as el512::... in YAML lambdas).
|
|
#pragma once
|
|
|
|
#include <cstdio>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include "esphome/components/display/display.h"
|
|
|
|
namespace esphome::el512 {
|
|
|
|
// Split the "a|b|c;d|e|f" lists made by the Home Assistant package.
|
|
inline std::vector<std::string> split(const std::string &s, char sep) {
|
|
std::vector<std::string> out;
|
|
if (s.empty())
|
|
return out;
|
|
size_t start = 0;
|
|
while (true) {
|
|
const size_t end = s.find(sep, start);
|
|
out.push_back(s.substr(start, end == std::string::npos ? std::string::npos : end - start));
|
|
if (end == std::string::npos)
|
|
return out;
|
|
start = end + 1;
|
|
}
|
|
}
|
|
|
|
// Field i of a "|"-separated entry, or "" if missing.
|
|
inline std::string field(const std::string &entry, size_t i) {
|
|
const auto parts = split(entry, '|');
|
|
return i < parts.size() ? parts[i] : std::string();
|
|
}
|
|
|
|
// Material Design Icons glyph (UTF-8) for a Home Assistant weather condition.
|
|
inline const char *weather_icon(const std::string &c) {
|
|
if (c == "clear-night") return "\U000F0594";
|
|
if (c == "cloudy") return "\U000F0590";
|
|
if (c == "fog") return "\U000F0591";
|
|
if (c == "hail") return "\U000F0592";
|
|
if (c == "lightning") return "\U000F0593";
|
|
if (c == "lightning-rainy") return "\U000F067E";
|
|
if (c == "partlycloudy") return "\U000F0595";
|
|
if (c == "pouring") return "\U000F0596";
|
|
if (c == "rainy") return "\U000F0597";
|
|
if (c == "snowy") return "\U000F0598";
|
|
if (c == "snowy-rainy") return "\U000F067F";
|
|
if (c == "sunny") return "\U000F0599";
|
|
if (c == "windy") return "\U000F059D";
|
|
if (c == "windy-variant") return "\U000F059E";
|
|
return "\U000F05D6"; // exceptional / unknown
|
|
}
|
|
|
|
// Readable weather condition ("partlycloudy" -> "Partly cloudy").
|
|
inline std::string weather_text(const std::string &c) {
|
|
if (c == "clear-night") return "Clear";
|
|
if (c == "partlycloudy") return "Partly cloudy";
|
|
if (c == "lightning-rainy") return "Thunderstorm";
|
|
if (c == "snowy-rainy") return "Sleet";
|
|
if (c == "windy-variant") return "Windy";
|
|
if (c == "exceptional") return "Exceptional";
|
|
if (c.empty()) return "";
|
|
std::string s = c;
|
|
s[0] = (char) toupper(s[0]);
|
|
return s;
|
|
}
|
|
|
|
// Cut text with "..." so it fits in max_width pixels in the given font.
|
|
inline std::string fit(display::BaseFont *font, const std::string &text, int max_width) {
|
|
int w, x_off, baseline, h;
|
|
font->measure(text.c_str(), &w, &x_off, &baseline, &h);
|
|
if (w <= max_width)
|
|
return text;
|
|
std::string s = text;
|
|
while (!s.empty()) {
|
|
// Drop a whole UTF-8 character.
|
|
do {
|
|
s.pop_back();
|
|
} while (!s.empty() && (s.back() & 0xC0) == 0x80);
|
|
const std::string t = s + "...";
|
|
font->measure(t.c_str(), &w, &x_off, &baseline, &h);
|
|
if (w <= max_width)
|
|
return t;
|
|
}
|
|
return "...";
|
|
}
|
|
|
|
// 245 -> "4:05", 3725 -> "1:02:05".
|
|
inline std::string mmss(int seconds) {
|
|
if (seconds < 0)
|
|
seconds = 0;
|
|
char buf[16];
|
|
if (seconds >= 3600)
|
|
snprintf(buf, sizeof(buf), "%d:%02d:%02d", seconds / 3600, seconds / 60 % 60, seconds % 60);
|
|
else
|
|
snprintf(buf, sizeof(buf), "%d:%02d", seconds / 60, seconds % 60);
|
|
return buf;
|
|
}
|
|
|
|
// 1250 -> "1250 W", 4200 -> "4.20 kW".
|
|
inline std::string watts(float w) {
|
|
char buf[16];
|
|
if (w >= 1000 || w <= -1000)
|
|
snprintf(buf, sizeof(buf), "%.2f kW", w / 1000.0f);
|
|
else
|
|
snprintf(buf, sizeof(buf), "%.0f W", w);
|
|
return buf;
|
|
}
|
|
|
|
} // namespace esphome::el512
|