281 lines
10 KiB
C++
281 lines
10 KiB
C++
// 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 20–50ms 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 0–255 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); // 0–252, Annäherung an 0–255
|
||
|
||
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;
|
||
}
|