Browse Source

LED-Test-Tool + Intent-Modell mit 129 Intents

LED-Test-Tool (tools/led_test/):
- Standalone-Script fuer WS2812/NeoPixel LED-Test ohne Trixy
- Unterstuetzt: single (1x), jewel (7x), ring12, ring16, ring24
- 9 Animationen: blink, breathe, circulate, circular-fade,
  chase, rainbow, wave, sparkle, fill-sequential
- Trixy-Zustandssimulation (Boot→Standby→Wakeword→Listening→...)
- Getestet auf Pi Zero WH mit Jewel 7x

Intent-Modell aktualisiert:
- 129 Intents (inkl. TV-Programm + Streaming)
- 29963 Trainings-Samples
- Plugin slot_lists.json Auto-Discovery (TV + Streaming)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
patrick 2 months ago
parent
commit
80899bc042

+ 1 - 1
config/client_config.json

@@ -15,7 +15,7 @@
     "input_device": null,
     "output_device": null,
     "buffer_size": 4096,
-    "output_volume": 65,
+    "output_volume": 60,
     "output_muted": false,
     "input_volume": 80,
     "input_muted": false,

+ 10 - 8
models/intent/bio_labels.json

@@ -29,12 +29,14 @@
   "I-query": 27,
   "B-room": 28,
   "I-room": 29,
-  "B-size": 30,
-  "I-size": 31,
-  "B-time": 32,
-  "I-time": 33,
-  "B-topping": 34,
-  "I-topping": 35,
-  "B-value": 36,
-  "I-value": 37
+  "B-service": 30,
+  "I-service": 31,
+  "B-size": 32,
+  "I-size": 33,
+  "B-time": 34,
+  "I-time": 35,
+  "B-topping": 36,
+  "I-topping": 37,
+  "B-value": 38,
+  "I-value": 39
 }

File diff suppressed because it is too large
+ 1 - 1
models/intent/intent_labels.json


File diff suppressed because it is too large
+ 602 - 503
models/intent/metafile.json


+ 479 - 0
tools/led_test/led_test.py

@@ -0,0 +1,479 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+"""
+LED-Test-Script fuer WS2812/NeoPixel LEDs am Raspberry Pi.
+
+Unabhaengig von Trixy — braucht nur rpi-ws281x.
+Testet alle Animationen fuer die drei LED-Typen:
+  - Breakout (1 LED)
+  - Jewel 7 (7 LEDs, Mitte + 6 Ring)
+  - Ring 12 (12 LEDs im Kreis)
+
+Verkabelung:
+  Pi GPIO 18 (Pin 12)  ──[330Ω]──  DIN (Daten)
+  Pi 5V (Pin 2 oder 4)  ─────────  VCC (Strom)
+  Pi GND (Pin 6)         ─────────  GND
+
+Ausfuehrung:
+  sudo pip install rpi-ws281x
+  sudo python3 led_test.py --leds 7 --type jewel
+
+WICHTIG: Muss als root/sudo laufen (GPIO-Zugriff)!
+"""
+
+import argparse
+import math
+import random
+import signal
+import sys
+import time
+
+try:
+    from rpi_ws281x import PixelStrip, Color
+except ImportError:
+    print("FEHLER: rpi-ws281x nicht installiert!")
+    print("  sudo pip install rpi-ws281x")
+    sys.exit(1)
+
+
+# === Hardware-Konfiguration ===
+LED_FREQ_HZ = 800000
+LED_DMA = 10
+LED_INVERT = False
+LED_CHANNEL = 0
+
+
+# === Farb-Helfer ===
+
+def rgb(r, g, b):
+    return Color(r, g, b)
+
+def hsv_to_rgb(h, s, v):
+    """HSV (0-1) zu RGB (0-255)."""
+    if s == 0:
+        r = g = b = int(v * 255)
+        return r, g, b
+    i = int(h * 6)
+    f = (h * 6) - i
+    p = int(v * (1 - s) * 255)
+    q = int(v * (1 - s * f) * 255)
+    t = int(v * (1 - s * (1 - f)) * 255)
+    v = int(v * 255)
+    i %= 6
+    if i == 0: return v, t, p
+    if i == 1: return q, v, p
+    if i == 2: return p, v, t
+    if i == 3: return p, q, v
+    if i == 4: return t, p, v
+    return v, p, q
+
+
+# === Layout-Definitionen ===
+
+class Layout:
+    def __init__(self, name, count, center=None, ring=None):
+        self.name = name
+        self.count = count
+        self.center = center  # Index der Mitte (None bei Ring/Single)
+        self.ring = ring or list(range(count))  # Ring-Indizes
+
+LAYOUTS = {
+    "single":  Layout("Breakout 1x", 1, center=None, ring=[0]),
+    "jewel":   Layout("Jewel 7x", 7, center=0, ring=[1, 2, 3, 4, 5, 6]),
+    "ring12":  Layout("Ring 12x", 12, center=None, ring=list(range(12))),
+    "ring16":  Layout("Ring 16x", 16, center=None, ring=list(range(16))),
+    "ring24":  Layout("Ring 24x", 24, center=None, ring=list(range(24))),
+}
+
+
+# === Animationen ===
+
+def clear(strip):
+    """Alle LEDs aus."""
+    for i in range(strip.numPixels()):
+        strip.setPixelColor(i, Color(0, 0, 0))
+    strip.show()
+
+
+def test_single_color(strip, layout, color, name, duration=2.0):
+    """Alle LEDs eine Farbe."""
+    print(f"  {name}...", end="", flush=True)
+    for i in range(strip.numPixels()):
+        strip.setPixelColor(i, color)
+    strip.show()
+    time.sleep(duration)
+    clear(strip)
+    print(" OK")
+
+
+def test_blink(strip, layout, color, times=3, speed=0.2):
+    """Blinken."""
+    print(f"  Blink ({times}x)...", end="", flush=True)
+    for _ in range(times):
+        for i in range(strip.numPixels()):
+            strip.setPixelColor(i, color)
+        strip.show()
+        time.sleep(speed)
+        clear(strip)
+        time.sleep(speed)
+    print(" OK")
+
+
+def test_breathe(strip, layout, color, cycles=3, steps=60):
+    """Sanftes Atmen (Sinus-Kurve)."""
+    print(f"  Breathe ({cycles} Zyklen)...", end="", flush=True)
+    r = (color >> 16) & 0xFF
+    g = (color >> 8) & 0xFF
+    b = color & 0xFF
+
+    for cycle in range(cycles):
+        for step in range(steps):
+            brightness = (math.sin(step / steps * 2 * math.pi - math.pi / 2) + 1) / 2
+            cr = int(r * brightness)
+            cg = int(g * brightness)
+            cb = int(b * brightness)
+            for i in range(strip.numPixels()):
+                strip.setPixelColor(i, Color(cr, cg, cb))
+            strip.show()
+            time.sleep(0.03)
+    clear(strip)
+    print(" OK")
+
+
+def test_circulate(strip, layout, color, rounds=3, speed=0.08):
+    """Eine LED wandert im Kreis."""
+    if len(layout.ring) <= 1:
+        print("  Circulate (uebersprungen — nur 1 LED)")
+        return
+
+    print(f"  Circulate ({rounds} Runden)...", end="", flush=True)
+    center_color = Color(0, 30, 0)  # Mitte gedimmt gruen
+
+    for _ in range(rounds):
+        for pos in range(len(layout.ring)):
+            clear(strip)
+            # Mitte (Jewel)
+            if layout.center is not None:
+                strip.setPixelColor(layout.center, center_color)
+            # Aktive LED
+            strip.setPixelColor(layout.ring[pos], color)
+            strip.show()
+            time.sleep(speed)
+    clear(strip)
+    print(" OK")
+
+
+def test_circular_fade(strip, layout, color, rounds=3, tail=3, speed=0.06):
+    """Kreis mit Nachleuchten."""
+    ring = layout.ring
+    if len(ring) <= 1:
+        print("  Circular-Fade (uebersprungen — nur 1 LED)")
+        return
+
+    print(f"  Circular-Fade (tail={tail})...", end="", flush=True)
+    r = (color >> 16) & 0xFF
+    g = (color >> 8) & 0xFF
+    b = color & 0xFF
+
+    center_color = Color(0, 20, 0)
+
+    for _ in range(rounds):
+        for pos in range(len(ring)):
+            clear(strip)
+            if layout.center is not None:
+                strip.setPixelColor(layout.center, center_color)
+
+            # Haupt-LED
+            strip.setPixelColor(ring[pos], color)
+
+            # Nachleuchtende LEDs
+            for t in range(1, tail + 1):
+                fade = 1.0 - (t / (tail + 1))
+                fade_idx = (pos - t) % len(ring)
+                strip.setPixelColor(
+                    ring[fade_idx],
+                    Color(int(r * fade), int(g * fade), int(b * fade)),
+                )
+            strip.show()
+            time.sleep(speed)
+    clear(strip)
+    print(" OK")
+
+
+def test_rainbow(strip, layout, rounds=2, speed=0.02):
+    """Regenbogen-Rotation."""
+    print(f"  Rainbow ({rounds} Runden)...", end="", flush=True)
+    ring = layout.ring
+
+    for offset in range(rounds * len(ring) * 5):
+        for i, led_idx in enumerate(ring):
+            hue = ((i / len(ring)) + (offset / (len(ring) * 5))) % 1.0
+            r, g, b = hsv_to_rgb(hue, 1.0, 1.0)
+            strip.setPixelColor(led_idx, Color(r, g, b))
+        if layout.center is not None:
+            strip.setPixelColor(layout.center, Color(50, 50, 50))
+        strip.show()
+        time.sleep(speed)
+    clear(strip)
+    print(" OK")
+
+
+def test_chase(strip, layout, color, rounds=4, group=3, speed=0.06):
+    """Lauflicht (Gruppe von LEDs)."""
+    ring = layout.ring
+    if len(ring) <= 1:
+        print("  Chase (uebersprungen — nur 1 LED)")
+        return
+
+    print(f"  Chase ({group} LEDs Gruppe)...", end="", flush=True)
+    r = (color >> 16) & 0xFF
+    g = (color >> 8) & 0xFF
+    b = color & 0xFF
+
+    for _ in range(rounds):
+        for pos in range(len(ring)):
+            clear(strip)
+            if layout.center is not None:
+                strip.setPixelColor(layout.center, Color(0, 20, 0))
+            for j in range(group):
+                idx = (pos + j) % len(ring)
+                fade = 1.0 - (j / group) * 0.5
+                strip.setPixelColor(
+                    ring[idx],
+                    Color(int(r * fade), int(g * fade), int(b * fade)),
+                )
+            strip.show()
+            time.sleep(speed)
+    clear(strip)
+    print(" OK")
+
+
+def test_sparkle(strip, layout, color, duration=3.0, speed=0.05):
+    """Zufaelliges Funkeln."""
+    print(f"  Sparkle ({duration}s)...", end="", flush=True)
+    r = (color >> 16) & 0xFF
+    g = (color >> 8) & 0xFF
+    b = color & 0xFF
+
+    end_time = time.monotonic() + duration
+    while time.monotonic() < end_time:
+        clear(strip)
+        # 1-2 zufaellige LEDs
+        for _ in range(random.randint(1, min(3, strip.numPixels()))):
+            idx = random.choice(layout.ring)
+            strip.setPixelColor(idx, color)
+        if layout.center is not None:
+            strip.setPixelColor(layout.center, Color(r // 4, g // 4, b // 4))
+        strip.show()
+        time.sleep(speed)
+    clear(strip)
+    print(" OK")
+
+
+def test_wave(strip, layout, color, rounds=4, speed=0.03):
+    """Helligkeits-Welle (Knight Rider)."""
+    ring = layout.ring
+    if len(ring) <= 2:
+        print("  Wave (uebersprungen — zu wenige LEDs)")
+        return
+
+    print(f"  Wave (Knight Rider)...", end="", flush=True)
+    r = (color >> 16) & 0xFF
+    g = (color >> 8) & 0xFF
+    b = color & 0xFF
+
+    positions = list(range(len(ring))) + list(range(len(ring) - 2, 0, -1))
+
+    for _ in range(rounds):
+        for center_pos in positions:
+            for i, led_idx in enumerate(ring):
+                dist = abs(i - center_pos)
+                brightness = max(0, 1.0 - dist * 0.3)
+                brightness = brightness ** 2  # Quadratischer Abfall
+                strip.setPixelColor(
+                    led_idx,
+                    Color(int(r * brightness), int(g * brightness), int(b * brightness)),
+                )
+            if layout.center is not None:
+                strip.setPixelColor(layout.center, Color(r // 5, g // 5, b // 5))
+            strip.show()
+            time.sleep(speed)
+    clear(strip)
+    print(" OK")
+
+
+def test_fill_sequential(strip, layout, color, speed=0.1):
+    """LEDs nacheinander auffuellen."""
+    print(f"  Fill Sequential...", end="", flush=True)
+    clear(strip)
+
+    for led_idx in layout.ring:
+        strip.setPixelColor(led_idx, color)
+        strip.show()
+        time.sleep(speed)
+
+    if layout.center is not None:
+        strip.setPixelColor(layout.center, color)
+        strip.show()
+        time.sleep(speed)
+
+    time.sleep(0.5)
+    clear(strip)
+    print(" OK")
+
+
+def test_trixy_states(strip, layout):
+    """Simuliert die Trixy-Zustandsmaschine."""
+    print("\n=== Trixy-Zustandssimulation ===")
+
+    # Boot
+    print("  [BOOT] Gruen hell...")
+    test_single_color(strip, layout, rgb(0, 255, 0), "Boot", 1.5)
+
+    # Standby
+    print("  [STANDBY] Gruen gedimmt...")
+    test_single_color(strip, layout, rgb(0, 60, 0), "Standby", 2.0)
+
+    # Wakeword
+    print("  [WAKEWORD] 2x Blau blinken...")
+    test_blink(strip, layout, rgb(0, 0, 255), times=2, speed=0.15)
+
+    # Listening
+    print("  [LISTENING] Rot (Mikrofon aktiv)...")
+    test_single_color(strip, layout, rgb(255, 0, 0), "Listening", 2.0)
+
+    # Thinking
+    print("  [THINKING] Orange pulsierend...")
+    test_breathe(strip, layout, rgb(255, 165, 0).intColor if hasattr(rgb(255, 165, 0), 'intColor') else Color(255, 165, 0), cycles=2, steps=30)
+
+    # Speaking
+    print("  [SPEAKING] Blau atmend...")
+    test_breathe(strip, layout, Color(0, 100, 255), cycles=2, steps=60)
+
+    # Error
+    print("  [ERROR] 3x Rot blinken...")
+    test_blink(strip, layout, rgb(255, 0, 0), times=3, speed=0.15)
+
+    # Disconnect
+    print("  [DISCONNECT] Rot pulsierend...")
+    test_breathe(strip, layout, Color(255, 0, 0), cycles=2, steps=40)
+
+    # Zurueck zu Standby
+    print("  [STANDBY] Gruen gedimmt...")
+    test_single_color(strip, layout, rgb(0, 60, 0), "Standby", 1.0)
+
+    clear(strip)
+    print("  Simulation fertig!")
+
+
+# === Hauptprogramm ===
+
+def main():
+    parser = argparse.ArgumentParser(
+        description="WS2812/NeoPixel LED-Test fuer Raspberry Pi",
+        formatter_class=argparse.RawDescriptionHelpFormatter,
+        epilog="""
+Beispiele:
+  sudo python3 led_test.py --type jewel             # Jewel 7x testen
+  sudo python3 led_test.py --leds 12 --type ring12   # Ring 12x
+  sudo python3 led_test.py --leds 1 --type single    # Breakout 1x
+  sudo python3 led_test.py --type jewel --test states # Nur Trixy-Simulation
+  sudo python3 led_test.py --type jewel --brightness 50  # Niedrigere Helligkeit
+  sudo python3 led_test.py --pin 12 --type ring12    # Anderer GPIO Pin
+
+Verkabelung:
+  Pi GPIO 18 (Pin 12)  --[330 Ohm]--  DIN (Daten-Eingang)
+  Pi 5V (Pin 2 oder 4)  ------------  VCC (Strom)
+  Pi GND (Pin 6)         ------------  GND
+
+WICHTIG: Muss als root/sudo ausgefuehrt werden!
+        """,
+    )
+    parser.add_argument(
+        "--type", choices=list(LAYOUTS.keys()), default="jewel",
+        help="LED-Typ (default: jewel)",
+    )
+    parser.add_argument(
+        "--leds", type=int, default=0,
+        help="Anzahl LEDs (ueberschreibt --type Default)",
+    )
+    parser.add_argument(
+        "--pin", type=int, default=18,
+        help="GPIO BCM Pin (default: 18 = PWM0)",
+    )
+    parser.add_argument(
+        "--brightness", type=int, default=80,
+        help="Helligkeit 0-255 (default: 80)",
+    )
+    parser.add_argument(
+        "--test", choices=["all", "colors", "animations", "states"],
+        default="all",
+        help="Test-Modus (default: all)",
+    )
+    args = parser.parse_args()
+
+    layout = LAYOUTS[args.type]
+    led_count = args.leds if args.leds > 0 else layout.count
+
+    print(f"╔═══════════════════════════════════════════╗")
+    print(f"║       WS2812 LED Test — Trixy            ║")
+    print(f"╠═══════════════════════════════════════════╣")
+    print(f"║  Typ:        {layout.name:28s} ║")
+    print(f"║  LEDs:       {led_count:<28d} ║")
+    print(f"║  GPIO Pin:   {args.pin:<28d} ║")
+    print(f"║  Helligkeit: {args.brightness:<28d} ║")
+    print(f"╚═══════════════════════════════════════════╝")
+    print()
+
+    # LED-Strip initialisieren
+    strip = PixelStrip(
+        led_count, args.pin,
+        LED_FREQ_HZ, LED_DMA, LED_INVERT,
+        args.brightness, LED_CHANNEL,
+    )
+    strip.begin()
+
+    # SIGINT abfangen → LEDs aus
+    def cleanup(sig, frame):
+        print("\n\nAbbruch — LEDs werden ausgeschaltet...")
+        clear(strip)
+        sys.exit(0)
+    signal.signal(signal.SIGINT, cleanup)
+
+    try:
+        if args.test in ("all", "colors"):
+            print("=== Farbtest ===")
+            test_single_color(strip, layout, rgb(255, 0, 0), "Rot")
+            test_single_color(strip, layout, rgb(0, 255, 0), "Gruen")
+            test_single_color(strip, layout, rgb(0, 0, 255), "Blau")
+            test_single_color(strip, layout, rgb(255, 255, 0), "Gelb")
+            test_single_color(strip, layout, rgb(255, 0, 255), "Magenta")
+            test_single_color(strip, layout, rgb(0, 255, 255), "Cyan")
+            test_single_color(strip, layout, rgb(255, 255, 255), "Weiss")
+            test_single_color(strip, layout, rgb(255, 165, 0), "Orange")
+            print()
+
+        if args.test in ("all", "animations"):
+            print("=== Animationen ===")
+            test_blink(strip, layout, rgb(0, 0, 255), times=3)
+            test_breathe(strip, layout, Color(0, 100, 255), cycles=2)
+            test_circulate(strip, layout, rgb(0, 255, 0))
+            test_circular_fade(strip, layout, rgb(0, 0, 255), tail=3)
+            test_chase(strip, layout, rgb(255, 165, 0), group=3)
+            test_rainbow(strip, layout, rounds=2)
+            test_wave(strip, layout, rgb(255, 0, 0))
+            test_sparkle(strip, layout, rgb(255, 255, 255), duration=3.0)
+            test_fill_sequential(strip, layout, rgb(0, 255, 0))
+            print()
+
+        if args.test in ("all", "states"):
+            test_trixy_states(strip, layout)
+
+        print("\n✓ Alle Tests abgeschlossen!")
+
+    finally:
+        clear(strip)
+
+
+if __name__ == "__main__":
+    main()

+ 1 - 0
tools/led_test/requirements.txt

@@ -0,0 +1 @@
+rpi-ws281x