Add ESPHome display component; ring-buffer driver shared with PlatformIO

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>
This commit is contained in:
mangavalkandClaude Opus 5.5 committed 2026-10-18 20:20:13 +02:00
1 parent 410885de24
commit 963590cdfe
27 files changed
+2110 -828

No files matched your search

+110
View File
@@ -0,0 +1,110 @@
#include "el512_display.h"
#include <esp_heap_caps.h>
#include "esphome/core/hal.h"
#include "esphome/core/log.h"
namespace esphome::el512 {
static const char *const TAG = "el512";
void EL512Display::setup() {
this->panel_ = new EL512Panel(this->pins_); // NOLINT(cppcoreguidelines-owning-memory)
if (!this->panel_->begin()) {
ESP_LOGE(TAG, "Panel init failed (out of DMA memory or no free interrupt)");
this->mark_failed();
return;
}
// Apply states that switches may have restored before setup ran.
this->panel_->setLowPower(this->low_power_);
this->panel_->setEnabled(this->enabled_);
}
void EL512Display::loop() {
if (this->still_alive_page_ == nullptr || this->panel_ == nullptr || !this->panel_->running())
return;
const bool active = this->is_still_alive_playing();
const uint32_t now = millis();
if (active && !this->still_alive_was_active_) {
// Remember where we came from, then start the show from the top.
this->still_alive_return_ = this->previous_page_ != this->still_alive_page_ ? this->previous_page_ : nullptr;
this->still_alive_.start(now);
ESP_LOGI(TAG, "Still Alive started; start the song in %u ms", (unsigned) this->still_alive_song_start_ms());
}
this->still_alive_was_active_ = active;
if (!active)
return;
if (this->still_alive_.finished(now) && this->still_alive_return_ != nullptr) {
this->show_page(this->still_alive_return_);
this->update();
return;
}
if (this->still_alive_.update(now))
this->update();
}
void EL512Display::play_still_alive() {
if (this->still_alive_page_ == nullptr)
return;
if (this->is_still_alive_playing()) {
this->still_alive_.start(millis()); // already showing: just restart
} else {
this->show_page(this->still_alive_page_); // loop() starts it
}
}
void EL512Display::draw_still_alive() {
if (this->panel_ != nullptr)
this->still_alive_.draw(*this->panel_);
}
void EL512Display::update() {
if (this->panel_ == nullptr || !this->panel_->running())
return;
this->do_update_();
this->panel_->show();
}
void EL512Display::fill(Color color) {
// The panel clear is a memset; with clipping active, fall back to per-pixel.
if (this->panel_ == nullptr || this->get_clipping().is_set()) {
Display::fill(color);
return;
}
this->panel_->clear(color.is_on());
}
void EL512Display::draw_absolute_pixel_internal(int x, int y, Color color) {
if (this->panel_ != nullptr)
this->panel_->setPixel(x, y, color.is_on());
}
void EL512Display::set_enabled(bool on) {
this->enabled_ = on;
if (this->panel_ != nullptr)
this->panel_->setEnabled(on);
}
void EL512Display::set_low_power(bool on) {
this->low_power_ = on;
if (this->panel_ != nullptr)
this->panel_->setLowPower(on);
}
void EL512Display::dump_config() {
LOG_DISPLAY("", "EL512.256-H", this);
ESP_LOGCONFIG(TAG,
" Pins: VCLK %d, VID %d, TVID %d, HS %d, VS %d, ENABLE %d, _LOWPWR %d\n"
" Refresh: %.1f Hz, DMA ring %u bytes\n"
" Free internal heap: %u bytes",
this->pins_.vclk, this->pins_.vid, this->pins_.tvid, this->pins_.hs, this->pins_.vs,
this->pins_.enable, this->pins_.lowpwr_n, EL512Panel::refreshHz(),
(unsigned) EL512Panel::RING_BYTES,
(unsigned) heap_caps_get_free_size(MALLOC_CAP_INTERNAL));
LOG_UPDATE_INTERVAL(this);
if (this->is_failed())
ESP_LOGE(TAG, " Init failed");
}
} // namespace esphome::el512