Initial commit

This commit is contained in:
2026-03-29 14:47:13 +02:00
commit b49984b9c0
32 changed files with 2394 additions and 0 deletions
+280
View File
@@ -0,0 +1,280 @@
// CButton.cpp
// Implementierung der CButton-Klasse.
//
// Animations-Arithmetik:
// Alle Berechnungen in Integer (kein float) Cortex-M0+ hat keine FPU.
// Helligkeits-Skalierung: (Kanalwert * scale) / 255
// PULSE nutzt ein lineares Dreieck (0→255→0) statt Sinus einfacher, trotzdem
// weich genug für den visuellen Eindruck bei 2050ms Loop-Rate.
//
// COLOR_CYCLE Hue-Sweep:
// Teilt den Farbraum in 6 Segmente (R→Y→G→C→B→M→R), je 43 Hue-Einheiten.
// Innerhalb jedes Segments steigt/fällt ein Kanal linear kein HSV-Float nötig.
//
// COLOR_FADE Crossfade:
// Lineare RGB-Interpolation zwischen m_anim_from und m_anim_to.
// t = elapsed * 255 / period (0→255); Ergebnis: from + (to-from)*t/255.
#include <Arduino.h>
#include "CButton.h"
#include "hal/ws2812.h"
// ── Hue → RGB (vollständig gesättigte Farbe, S=255, V=255) ───────────────────
//
// Hue 0255 wird auf 6 Segmente à 43 Einheiten aufgeteilt:
// Seg 0: R=255, G steigt, B=0 (Rot → Gelb)
// Seg 1: R fällt, G=255, B=0 (Gelb → Grün)
// Seg 2: R=0, G=255, B steigt (Grün → Cyan)
// Seg 3: R=0, G fällt, B=255 (Cyan → Blau)
// Seg 4: R steigt, G=0, B=255 (Blau → Magenta)
// Seg 5: R=255, G=0, B fällt (Magenta → Rot)
static RGB hue_to_rgb(uint8_t h)
{
uint8_t seg = h / 43;
uint8_t frac = (uint8_t)((h % 43) * 6); // 0252, Annäherung an 0255
switch (seg) {
case 0: return RGB(255, frac, 0);
case 1: return RGB(255 - frac, 255, 0);
case 2: return RGB(0, 255, frac);
case 3: return RGB(0, 255 - frac, 255);
case 4: return RGB(frac, 0, 255);
default: return RGB(255, 0, 255 - frac);
}
}
// ── Konstruktor ───────────────────────────────────────────────────────────────
CButton::CButton()
: m_key_id(0)
, m_led_index(-1)
, m_action{ActionType::NONE, 0}
, m_base()
, m_override()
, m_override_active(false)
, m_dirty(false)
, m_anim(LEDAnim::STATIC)
, m_anim_period_ms(500)
, m_anim_start_ms(0)
, m_anim_from()
, m_anim_to()
{}
// ── Initialisierung ───────────────────────────────────────────────────────────
void CButton::init(uint8_t key_id, int8_t led_index, SAction action, RGB base)
{
m_key_id = key_id;
m_led_index = led_index;
m_action = action;
m_base = base;
m_override_active = false;
m_anim = LEDAnim::STATIC;
m_dirty = true; // Initialen Zustand beim ersten render_led() schreiben
}
// ── Tastendruck-Hooks ─────────────────────────────────────────────────────────
void CButton::on_press()
{
// Reserviert für zukünftige Button-Logik (Hold, Toggle, Doppelklick …)
}
void CButton::on_release()
{
// Reserviert für zukünftige Button-Logik
}
// ── LED Layer 1: base ─────────────────────────────────────────────────────────
void CButton::set_base(RGB color)
{
m_base = color;
m_dirty = true;
}
// ── LED Layer 2: override ─────────────────────────────────────────────────────
void CButton::set_override(RGB color)
{
m_override = color;
m_override_active = true;
m_dirty = true;
}
void CButton::clear_override()
{
m_override_active = false;
m_dirty = true; // Base-Farbe muss neu in WS2812-Buffer geschrieben werden
}
// ── LED-Animation ─────────────────────────────────────────────────────────────
void CButton::set_anim(LEDAnim anim, uint16_t period_ms, uint16_t phase_offset_ms)
{
m_anim = anim;
m_anim_period_ms = (period_ms > 0) ? period_ms : 1; // Division durch 0 vermeiden
// phase_offset_ms in die Vergangenheit zurücksetzen → verschobener Startpunkt
m_anim_start_ms = millis() - phase_offset_ms;
m_dirty = true;
}
void CButton::set_color_fade(RGB to, uint16_t period_ms)
{
// Startfarbe = Snapshot der aktuell aktiven Farbe (override > base)
m_anim_from = m_override_active ? m_override : m_base;
m_anim_to = to;
m_anim = LEDAnim::COLOR_FADE;
m_anim_period_ms = (period_ms > 0) ? period_ms : 1;
m_anim_start_ms = millis();
m_dirty = true;
}
void CButton::clear_anim()
{
m_anim = LEDAnim::STATIC;
m_dirty = true; // Sofort wieder volle Helligkeit rendern
}
// ── Animations-Hilfsmethoden ──────────────────────────────────────────────────
// Gibt true zurück solange die Animation noch läuft.
// BLINK, PULSE, COLOR_CYCLE laufen endlos; FADE_*/COLOR_FADE enden nach period_ms.
bool CButton::is_animating() const
{
if (m_anim == LEDAnim::STATIC) return false;
if (m_anim == LEDAnim::BLINK ||
m_anim == LEDAnim::PULSE ||
m_anim == LEDAnim::COLOR_CYCLE) return true;
// FADE_IN / FADE_OUT / COLOR_FADE: läuft bis elapsed >= period
return (millis() - m_anim_start_ms) < (uint32_t)m_anim_period_ms;
}
// Berechnet den aktuellen Helligkeits-Skalierungsfaktor (0 = aus, 255 = voll).
// Darf m_anim und m_dirty verändern (Abschluss von FADE_IN/FADE_OUT).
uint8_t CButton::compute_scale()
{
if (m_anim == LEDAnim::STATIC) return 255;
uint32_t elapsed = millis() - m_anim_start_ms;
uint32_t period = (uint32_t)m_anim_period_ms;
switch (m_anim) {
case LEDAnim::BLINK: {
// An für erste Halbperiode, aus für zweite
uint32_t t = elapsed % period;
return (t < period / 2) ? 255 : 0;
}
case LEDAnim::PULSE: {
// Lineares Dreieck: 0 → 255 → 0 in einer Periode
uint32_t t = elapsed % period;
uint32_t half = period / 2;
if (t < half)
return (uint8_t)((t * 255) / half);
else
return (uint8_t)(((period - t) * 255) / half);
}
case LEDAnim::FADE_IN: {
// Einmalig: schwarz → volle Helligkeit
if (elapsed >= period) {
// Animation abgeschlossen ab jetzt STATIC bei voller Helligkeit
m_anim = LEDAnim::STATIC;
m_dirty = true;
return 255;
}
return (uint8_t)((elapsed * 255) / period);
}
case LEDAnim::FADE_OUT: {
// Einmalig: volle Helligkeit → schwarz
if (elapsed >= period) {
// Animation abgeschlossen LED bleibt aus.
// set_base() oder set_override() bringt sie zurück.
m_anim = LEDAnim::STATIC;
m_base = RGB(0, 0, 0); // Base auf schwarz setzen damit LED dunkel bleibt
m_dirty = true;
return 0;
}
return (uint8_t)(((period - elapsed) * 255) / period);
}
default:
return 255;
}
}
// Berechnet die finale RGB-Farbe für alle Animationstypen.
// Helligkeits-Animationen (STATIC/BLINK/PULSE/FADE_*):
// Wendet compute_scale() auf die aktive Farbe (override > base) an.
// Farb-Animationen (COLOR_CYCLE/COLOR_FADE):
// Berechnet die Farbe direkt; base/override werden nicht verändert.
RGB CButton::compute_rgb()
{
switch (m_anim) {
case LEDAnim::COLOR_CYCLE: {
// Hue 0→255 über eine Periode, dann Wiederholung; 40% Helligkeit (102/255)
uint32_t elapsed = millis() - m_anim_start_ms;
uint32_t period = (uint32_t)m_anim_period_ms;
uint8_t hue = (uint8_t)((elapsed % period) * 255 / period);
RGB c = hue_to_rgb(hue);
return RGB(
(uint8_t)(((uint16_t)c.r * 102) / 255),
(uint8_t)(((uint16_t)c.g * 102) / 255),
(uint8_t)(((uint16_t)c.b * 102) / 255)
);
}
case LEDAnim::COLOR_FADE: {
// Linearer Crossfade von m_anim_from nach m_anim_to
uint32_t elapsed = millis() - m_anim_start_ms;
uint32_t period = (uint32_t)m_anim_period_ms;
if (elapsed >= period) {
// Abgeschlossen base auf Zielfarbe setzen, ab jetzt STATIC
m_anim = LEDAnim::STATIC;
m_base = m_anim_to;
m_dirty = true;
return m_anim_to;
}
uint8_t t = (uint8_t)((elapsed * 255) / period); // 0→255
int16_t dr = (int16_t)m_anim_to.r - (int16_t)m_anim_from.r;
int16_t dg = (int16_t)m_anim_to.g - (int16_t)m_anim_from.g;
int16_t db = (int16_t)m_anim_to.b - (int16_t)m_anim_from.b;
return RGB(
(uint8_t)(m_anim_from.r + (dr * t) / 255),
(uint8_t)(m_anim_from.g + (dg * t) / 255),
(uint8_t)(m_anim_from.b + (db * t) / 255)
);
}
default: {
// Helligkeits-Animation: compute_scale() × aktive Farbe
const RGB& color = m_override_active ? m_override : m_base;
uint8_t scale = compute_scale();
return RGB(
(uint8_t)(((uint16_t)color.r * scale) / 255),
(uint8_t)(((uint16_t)color.g * scale) / 255),
(uint8_t)(((uint16_t)color.b * scale) / 255)
);
}
}
}
// ── Rendering ─────────────────────────────────────────────────────────────────
bool CButton::render_led()
{
if (!has_led()) return false;
bool animating = is_animating();
if (!m_dirty && !animating) return false; // Nichts zu tun
RGB c = compute_rgb();
ws2812_set(static_cast<uint8_t>(m_led_index), c.r, c.g, c.b);
m_dirty = false;
return true;
}