Added device types and vest mini app
All checks were successful
Deploy Docs / build-and-deploy (push) Successful in 22s

This commit is contained in:
2026-01-10 09:07:49 +01:00
parent 978f93ec3d
commit ce4d0d1a44
31 changed files with 621 additions and 273 deletions

View File

@@ -63,27 +63,27 @@ Kompakte passive Power-Up-Geräte mit Druckschalter.
### 2.1 Akkusystem
**Standard:** 2S LiPo (7.4 V nominal, 8.4 V voll geladen, 6.0 V Entladungsschutz).
**Standard:** 2S LiPo (7.4V nominal, 8.4V voll geladen, 6.0V Entladungsschutz).
**Alternative:** 1S nur für Tests oder Low-Power-Prototypen ungeeignet für hohe IR-LED-Ströme (siehe Abschnitt 4.1).
**Schutz & Laden:**
- **Zellenschutz-IC:** HY2120-CB + FS8205A (Dual-FET) schützt vor Über-/Unterspannung, Überstrom, Kurzschluss.
- **Lade-IC:** IP2326 (2S Balancing, USB-C) ermöglicht einfaches Laden ohne externe Balancer.
- **Fuel Gauge:** Spannungsteiler-basierte ADC-Messung (R1=100k, R2=47k) → Software-Mapping auf Ladestand (6.0 V = 0 %, 8.4 V = 100 %).
- **Fuel Gauge:** Spannungsteiler-basierte ADC-Messung (R1=100k, R2=47k) → Software-Mapping auf Ladestand (6.0V = 0 %, 8.4V = 100 %).
!!! info "Warum 2S?"
2S-Systeme bieten ausreichend Headroom für IR-LED-Konstantstromquellen (>4.8 V nötig bei 3A) und stabile Versorgung auch bei hoher Last. 1S-Zellen brechen unter 1A+ schnell auf 3.43.6 V ein.
2S-Systeme bieten ausreichend Headroom für IR-LED-Konstantstromquellen (>4.8V nötig bei 3A) und stabile Versorgung auch bei hoher Last. 1S-Zellen brechen unter 1A+ schnell auf 3.43.6V ein.
### 2.2 Spannungsebenen & Wandler
**Primär-Rail (Batterie, 6.08.4 V):**
**Primär-Rail (Batterie, 6.08.4V):**
Direkt gespeist: IR-LED-Treiber, Muzzle-Flash-LED, Solenoid 6V (Open Frame, taktiles Feedback Rückstoss).
**Sekundär-Rail (5.0 V, ~1.5 A):**
**Sekundär-Rail (5.0V, ~1.5A):**
Buck-Converter (z.B. MP2315, TPS62130) für Audio-IC (MAX98357A), adressierbare LEDs (WS2812B) und nachgeschalteten LDO. Hohe Schaltfrequenz gewünscht (geringe Induktivität, kompakte Bauform).
**Tertiär-Rail (3.3 V, ~30 mA):**
**Tertiär-Rail (3.3V, ~30mA):**
LDO (z.B. MCP1826, AMS1117-3.3) aus 5V Buck-Ausgang für nRF52840 und QSPI Flash. Geringer Dropout (5V → 3.3V = 1.7V) reduziert Wärmeentwicklung; Low-Noise-Design minimiert Störungen auf der RF-Schaltung.
**IR-Empfänger (5V mit Level-Shift):**
@@ -100,23 +100,23 @@ Die Weste hat durch LEDs und Audio den höchsten Verbrauch; hier die Peak-Absch
| Komponente | Zustand | Strom | Leistung |
| :--- | :--- | ---: | ---: |
| Audio (MAX98357A) | Volllast (3W @ 90% η) | ~670 mA | 3.35 W |
| WS2812B LEDs (5×) | 100 % Weiß | 300 mA | 1.50 W |
| IR-Empfänger (5×) | Dauerbetrieb @ 5V | ~50 mA | 0.25 W |
| LDO 3.3V (Durchleitung) | 30 mA @ 3.3V | ~30 mA | 0.15 W |
| **Gesamt 5V (Peak)** | | **~1.05 A** | **5.25 W** |
| Audio (MAX98357A) | Volllast (3W @ 90% η) | ~670mA | 3.35W |
| WS2812B LEDs (5×) | 100 % Weiß | 300mA | 1.50W |
| IR-Empfänger (5×) | Dauerbetrieb @ 5V | ~50mA | 0.25W |
| LDO 3.3V (Durchleitung) | 30mA @ 3.3V | ~30mA | 0.15W |
| **Gesamt 5V (Peak)** | | **~1.05A** | **5.25W** |
**3.3V-Rail (LDO aus 5V Buck):**
| Komponente | Zustand | Strom | Leistung |
| :--- | :--- | ---: | ---: |
| nRF52840 | BLE+Thread aktiv | ~15 mA | 0.05 W |
| QSPI Flash | Read/Write Burst | ~15 mA | 0.05 W |
| **Gesamt 3.3V (Peak)** | | **~30 mA** | **0.10 W** |
| nRF52840 | BLE+Thread aktiv | ~15mA | 0.05W |
| QSPI Flash | Read/Write Burst | ~15mA | 0.05W |
| **Gesamt 3.3V (Peak)** | | **~30mA** | **0.10W** |
**Auslegung:**
- **Buck-Regler:** 1.5 A Nennstrom (30 % Reserve), hohe Schaltfrequenz (>1 MHz) für kompakte Drossel/Kondensatoren.
- **LDO:** 100 mA Nennstrom ausreichend; Verlustleistung bei 30 mA: $P_{loss} = (5V - 3.3V) \cdot 30mA = 51 mW$ (unkritisch). Low-Noise-Design (< 50 µVrms) für sauberen RF-Betrieb.
- **Buck-Regler:** 1.5A Nennstrom (30 % Reserve), hohe Schaltfrequenz (>1MHz) für kompakte Drossel/Kondensatoren.
- **LDO:** 100mA Nennstrom ausreichend; Verlustleistung bei 30mA: $P_{loss} = (5V - 3.3V) \cdot 30mA = 51mW$ (unkritisch). Low-Noise-Design (< 50 µVrms) für sauberen RF-Betrieb.
### 2.4 Blockschaltbild Energieversorgung
@@ -169,7 +169,7 @@ Diese Tabelle gibt einen Überblick über die groben Komponenten pro Einheit. De
#### Funktionsprinzip
Hybride PNP/NPN-Topologie für präzisen, modulierten IR-Puls (38 kHz). Die Stromquelle stellt sicher, dass bei wechselnder Batteriespannung der LED-Strom konstant bleibt (→ reproduzierbare Reichweite).
Hybride PNP/NPN-Topologie für präzisen, modulierten IR-Puls (38kHz). Die Stromquelle stellt sicher, dass bei wechselnder Batteriespannung der LED-Strom konstant bleibt (→ reproduzierbare Reichweite).
![LED DRIVER](../img/concept_hardware_led_driver.svg)
@@ -183,10 +183,10 @@ $$R_{set} = \frac{0,65\,\text{V}}{I_{\text{LED}}}$$
| $I_{\text{LED}}$ | $R_{set}$ | Einsatz |
| :--- | :--- | :--- |
| 0,5 A | 1,30 Ω | Standard/Nahkampf |
| 1,0 A | 0,65 Ω | Hohe Reichweite (SFH 4550) |
| 2,0 A | 0,33 Ω | Pulsbetrieb (extreme Leistung) |
| 3,0 A | 0,22 Ω | Scharfschütze (Oslon Black) |
| 0,5A | 1,30Ω | Standard/Nahkampf |
| 1,0A | 0,65Ω | Hohe Reichweite (SFH 4550) |
| 2,0A | 0,33Ω | Pulsbetrieb (extreme Leistung) |
| 3,0A | 0,22Ω | Scharfschütze (Oslon Black) |
**Thermik:** Bei 38-kHz-Modulation (Duty-Cycle ~30 %) ist $P_{\text{avg}} = R_{set} \cdot I^2_{\text{LED}} \cdot DC$ → deutlich unter Peak. $R_{set}$ muss aber Spitzenstrom verkraften → impulsfeste Typen (Metallschicht, Drahtwiderstand).
@@ -198,22 +198,22 @@ $$V_{\text{CC,min}} = V_{f(\text{LED})} + 0,65\,\text{V} + 1,0\,\text{V}_{\text{
| $I_{\text{LED}}$ | $V_f$ (typ.) | $V_{\text{CC,min}}$ | Akku |
| :--- | :--- | :--- | :--- |
| 0,5 A | 2,0 V | 3,65 V | 1S (nur voll geladen) |
| 1,0 A | 2,4 V | 4,05 V | 2S empfohlen |
| 2,0 A | 2,8 V | 4,45 V | 2S erforderlich |
| 3,0 A | 3,2 V | 4,85 V | 2S erforderlich |
| 0,5A | 2,0V | 3,65V | 1S (nur voll geladen) |
| 1,0A | 2,4V | 4,05V | 2S empfohlen |
| 2,0A | 2,8V | 4,45V | 2S erforderlich |
| 3,0A | 3,2V | 4,85V | 2S erforderlich |
!!! warning "1S ungeeignet für >1A"
1S-Akkus brechen unter Last auf 3,43,6 V ein → Regelung versagt, Reichweite bricht ein. 2S liefert auch bei Teilentladung (7,0 V) genug Headroom.
1S-Akkus brechen unter Last auf 3,43,6V ein → Regelung versagt, Reichweite bricht ein. 2S liefert auch bei Teilentladung (7,0V) genug Headroom.
### 4.2 Adressierbare LEDs (WS2812B)
**Anforderung:** 5V-Versorgung, aber Daten-Pegel kompatibel mit nRF52840 (3.3V Logic).
**Level-Shift:** SN74AHCT1G125 (3.3V → 5V, Single-Gate); schnell genug für WS2812-Timing (800 kHz).
**Serienwiderstand:** ~330 Ω nach dem Shifter → dämpft Reflexionen auf der Data-Leitung, verhindert Überschwinger.
**Level-Shift:** SN74AHCT1G125 (3.3V → 5V, Single-Gate); schnell genug für WS2812-Timing (800kHz).
**Serienwiderstand:** ~330Ω nach dem Shifter → dämpft Reflexionen auf der Data-Leitung, verhindert Überschwinger.
**Layout:** Data-Leitung kurz halten; bei mehreren LEDs in Serie: Bypass-Kondensator (100 nF + 10 µF) pro 35 LEDs.
**Layout:** Data-Leitung kurz halten; bei mehreren LEDs in Serie: Bypass-Kondensator (100nF + 10µF) pro 35 LEDs.
### 4.3 Audio-Verstärker (MAX98357A)
@@ -222,39 +222,39 @@ $$V_{\text{CC,min}} = V_{f(\text{LED})} + 0,65\,\text{V} + 1,0\,\text{V}_{\text{
**Architektur:** I2S Class-D Verstärker DAC + Endstufe integriert, filterlose Topologie (wenige Bauteile).
* **Schnittstelle:** I2S (digital); Audio-Stream per EasyDMA vom nRF52840 → CPU bleibt frei für Game Logic.
* **Leistung:** ~3.2 W @ 4Ω laut genug für Outdoor-Einsatz.
* **Leistung:** ~3.2W @ 4Ω laut genug für Outdoor-Einsatz.
* **Effizienz:** ~90 % → Akku-schonend; geringer Ruhestrom im Idle.
* **Layout:** Kurze, symmetrische Leitungen zu Speaker-Terminals; separate Ground-Plane; Entkopplung (10 µF + 100 nF) nahe VDD-Pin.
* **Layout:** Kurze, symmetrische Leitungen zu Speaker-Terminals; separate Ground-Plane; Entkopplung (10µF + 100nF) nahe VDD-Pin.
### 4.4 Flash-Speicher (QSPI)
**Aufgabe:** Audio-Files (Schuss-FX, Ansagen) und Spiel-Logs (optional Treffer-Historie).
* **Technik:** QSPI-NOR-Flash (z.B. W25Q128JV, GD25Q16C); 1.8 V oder 3.3 V; XIP-fähig (Execute-in-Place für Code möglich).
* **Kapazität:** 816 MB; reicht für ~3 min @ 22 kHz oder ~1.5 min @ 44 kHz (16 bit mono). Empfehlung: 22 kHz höhere Sample-Rate bringt bei Outdoor-Speaker kaum Mehrwert.
* **Technik:** QSPI-NOR-Flash (z.B. W25Q128JV, GD25Q16C); 1.8V oder 3.3V; XIP-fähig (Execute-in-Place für Code möglich).
* **Kapazität:** 816MB; reicht für ~3min @ 22kHz oder ~1.5min @ 44kHz (16bit mono). Empfehlung: 22kHz höhere Sample-Rate bringt bei Outdoor-Speaker kaum Mehrwert.
* **Interface:** QSPI (4-Bit parallel); nRF52840 unterstützt DMA-basierten Zugriff → schnelle Reads ohne CPU-Last.
* **Layout:** Flash nahe am MCU (< 5 cm Leitungslänge); Differenzen in Trace-Längen < 1 mm; saubere Ground-Plane; JEDEC-ID beim Boot prüfen.
* **Layout:** Flash nahe am MCU (< 5cm Leitungslänge); Differenzen in Trace-Längen < 1mm; saubere Ground-Plane; JEDEC-ID beim Boot prüfen.
### 4.5 Akku-Überwachung (Fuel Gauge)
**Prinzip:** Spannungsteiler + ADC für 2S-Akkus (08.4 V) → Software-basierte Ladezustandsschätzung (kein dediziertes Fuel-Gauge-IC nötig).
**Prinzip:** Spannungsteiler + ADC für 2S-Akkus (08.4V) → Software-basierte Ladezustandsschätzung (kein dediziertes Fuel-Gauge-IC nötig).
**Schaltungskomponenten:**
| Bauteil | Wert | Funktion |
| :--- | :--- | :--- |
| $R_1$ | 100 kΩ | Spannungsteiler oberer Zweig |
| $R_2$ | 47 kΩ | Spannungsteiler unterer Zweig (→ ADC) |
| $C_1$ | 100 nF | Tiefpass-Glättung am ADC-Eingang |
| $R_1$ | 100kΩ | Spannungsteiler oberer Zweig |
| $R_2$ | 47kΩ | Spannungsteiler unterer Zweig (→ ADC) |
| $C_1$ | 100nF | Tiefpass-Glättung am ADC-Eingang |
**Softwarelogik:**
1. **ADC-Konvertierung:** 12-bit ADC liest $V_{\text{div}}$ (max. 3.3 V bei VRef = 3.3 V).
1. **ADC-Konvertierung:** 12-bit ADC liest $V_{\text{div}}$ (max. 3.3V bei VRef = 3.3V).
2. **Rückrechnung:** $V_{\text{bat}} = V_{\text{adc}} \cdot \frac{R_1 + R_2}{R_2} = V_{\text{adc}} \cdot 3.13$
3. **Mapping:** Lookup-Table oder linear interpoliert:
- 8.4 V → 100 % (voll geladen)
- 7.4 V → ~50 % (nominal)
- 6.0 V → 0 % (Schutzschaltung aktiv)
- 8.4V → 100 % (voll geladen)
- 7.4V → ~50 % (nominal)
- 6.0V → 0 % (Schutzschaltung aktiv)
**Kalibrierung:** Einmalig bei Produktion: Spannung an bekanntem Referenzpunkt messen, Offset/Gain in NVS speichern.
@@ -267,23 +267,23 @@ Konsolidierte Liste der Schlüsselkomponenten mit konkreten Part-Vorschlägen. D
| Kategorie | Bauteil/Funktion | Vorschlag | Alternativen | Anmerkung |
| :--- | :--- | :--- | :--- | :--- |
| **MCU** | Mikrocontroller | nRF52840 | — | Zephyr-Support, BLE+Thread |
| **Energie** | 2S-Akku | Li-Po 7.4V, 10002000 mAh | — | Kapazität je nach Gerät |
| **Energie** | 2S-Akku | Li-Po 7.4V, 10002000mAh | — | Kapazität je nach Gerät |
| | Zellenschutz | HY2120-CB + FS8205A | DW01A + 8205A | OV/UV/OC-Protection |
| | Lade-IC | IP2326 (2S Balancing) | TP4056 (nur 1S) | USB-C, Balancing integriert |
| | Buck 5V | MP2315, TPS62130 | — | 1.5 A, >1 MHz Schaltfrequenz |
| | Buck 5V | MP2315, TPS62130 | — | 1.5A, >1MHz Schaltfrequenz |
| | LDO 3.3V | MCP1826, AMS1117-3.3 | XC6206P332MR | Low-Noise für RF, < 0.5V Dropout |
| **IR** | IR-LED | SFH 4550, Oslon Black | TSAL6400 | 940 nm, >50 m Reichweite |
| | IR-Empfänger | TSOP4838, TSOP38438 | VS1838B | 38 kHz Demodulator, 5V Supply |
| **IR** | IR-LED | SFH 4550, Oslon Black | TSAL6400 | 940 nm, >50m Reichweite |
| | IR-Empfänger | TSOP4838, TSOP38438 | VS1838B | 38kHz Demodulator, 5V Supply |
| | LED-Treiber | PNP/NPN diskret | IRL530 (Logic-FET) | Konstantstrom, PWM-fähig |
| | Level-Shifter IR | AO4300A (N-Ch MOSFET) | BSS138, 2N7002 | 5V → 3.3V, invertierend |
| **LED** | Adressierbare | WS2812B (5050) | SK6812, APA102 | 5V, ~60 mA/LED @ weiß |
| **LED** | Adressierbare | WS2812B (5050) | SK6812, APA102 | 5V, ~60mA/LED @ weiß |
| | Level-Shift | SN74AHCT1G125 | 74HCT245 (8-Kanal) | 3.3V → 5V, single-gate |
| **Audio** | Class-D Amp | MAX98357A | PAM8302, TPA2005D1 | I2S, 3.2W @ 4Ω |
| | Speaker | 4Ω, 35W | 8Ω (lower SPL) | Outdoor-tauglich |
| **Speicher** | QSPI Flash | W25Q128JV (16 MB) | GD25Q16C (2 MB) | NOR-Flash, 3.3V |
| **Speicher** | QSPI Flash | W25Q128JV (16MB) | GD25Q16C (2MB) | NOR-Flash, 3.3V |
| **Feedback** | Solenoid | 6V Open Frame | — | Rückstoss direkt ab Batterie |
| | Muzzle LED | Weiß/Gelb, 1W+ | Cree XP-E2 | Sichtbar bei Tag |
| **Passiv** | $R_{\text{set}}$ (IR) | 0.221.3 Ω, 3W | Metallschicht, Draht | Impulsfest |
| **Passiv** | $R_{\text{set}}$ (IR) | 0.221.3Ω, 3W | Metallschicht, Draht | Impulsfest |
| | Spannungsteiler | 100k + 47k, 1% | 0.1% für Präzision | Fuel Gauge |
| **Mechanik** | Stecker | JST-XH (2.54mm) | Molex PicoBlade | Verriegelnd, 35 Pole |
| | Taster | Omron B3F, Alps SKQG | Cherry MX (größer) | Trigger, Reload |
@@ -292,7 +292,26 @@ Konsolidierte Liste der Schlüsselkomponenten mit konkreten Part-Vorschlägen. D
- **IR-LED:** Oslon Black für extreme Reichweite (3A-Betrieb), SFH 4550 für Standard (12A).
- **Audio:** MAX98357A ist quasi-Standard; Alternativen (PAM8302) haben höheren THD, aber OK für SFX.
- **Flash:** 16 MB erlauben ~6 min Audio @ 22 kHz gut für zukünftige Erweiterungen (z.B. mehrsprachige Ansagen).
- **Flash:** 16MB erlauben ~6min Audio @ 22kHz gut für zukünftige Erweiterungen (z.B. mehrsprachige Ansagen).
- **Stecker:** JST-XH ist weit verbreitet und günstig; Molex PicoBlade kompakter, aber teurer.
### 5.1 IR-LEDs
| Typ | Leistung |Bemerkungen |
|-----|----------|------------|
| **SFH 4725S** | 3W | Standardmodell für 940nm<br>**Vorteile:** Sehr bewährt, gute Effizienz |
| **SFH 4726S** | 3W | Ähnlich wie die 4725S, aber oft mit einer leicht anderen internen Linsencharakteristik (breiterer Abstrahlwinkel ohne externe Optik). |
| **SFH 4727AS** | 5W | Das 940-nm-Gegenstück zu deiner 4715AS. <br>**Vorteil:** Für deine 3-A-Pulse im Outdoor-Modus die stabilste Wahl. Sie verträgt die hohen Pulsströme thermisch am besten.|
|**SFH 4725AS**| 3W | Eine neuere "A"-Revision mit verbesserter Wärmeableitung.|
Es wird empfohlen, entsprechende "STAR"-Aluplatinen zu verwenden, um die Wärmeableitung zu garantieren.
### IR-Empfänger
| Typ | Bemerkungen |
|-----|------------|
| **TSOP34456 / TSOP38456** | Der Standard für 56kHz.<br>**Charakteristik**: Besitzt eine sehr agressive **AGC (Automatic Gain Controll)**<br>**Problem:** Bei extrem starken Signalen im Nahbereich kann die AGC "zumachen" und die Hüllkurve verzerren. |
| **TSSP4056 / TSSP77056** | **Vorteil:** Er hat eine **feste Verstärkung (Fixed Gain)**. Er regelt also nicht ab, wenn das Signal stark wird.<br>**Nutzen:** Das Signal bleibt viel konstanter als bei bei einem TSOP.|
*Stand: 04.01.2026*

View File

@@ -116,9 +116,9 @@ sequenceDiagram
7. *(Optional)* Weste B sendet UDP-Paket an Leader für Live-Scoreboard (Best Effort).
!!! info "Warum kein MilesTag2?"
MilesTag2 wurde als Basis erwogen, ist aber mit ~40 ms Frame-Zeit und starren 8-Bit-IDs zu langsam und unflexibel. Unser Custom-Protokoll bietet:
MilesTag2 wurde als Basis erwogen, ist aber mit ~40ms Frame-Zeit und starren 8-Bit-IDs zu langsam und unflexibel. Unser Custom-Protokoll bietet:
- **Kürzere Frames:** ~36 ms vs. ~40 ms (weniger anfällig für Zittern/Bewegung)
- **Kürzere Frames:** ~36ms vs. ~40ms (weniger anfällig für Zittern/Bewegung)
- **Flexible Type-Codes:** Hit/Heal/PowerUp/Admin in einem Format
- **CRC8-Prüfung:** >99.5% Fehlerrate-Erkennung bei Sonnenlicht
- **Variable Daten:** 13-Bit-Payload anpassbar pro Type
@@ -219,7 +219,7 @@ Die Logik für die Modi wird primär auf den Westen implementiert (Regelwerk), g
* **Medipacks (Objekte):** Aktiv durch Tastendruck; senden erst nach Button-Press ein Heal-IR mit breiter Streuung.
* **Wirkung:**
* Heal-IR ist als eigene Damage-Class kodiert (negativer Schaden → Heilung).
* Reichweite absichtlich klein (< 23 m) und stark gestreut, damit Heilen ein Positionierungs-Feature bleibt.
* Reichweite absichtlich klein (< 23m) und stark gestreut, damit Heilen ein Positionierungs-Feature bleibt.
* Treffer-Logik auf der Weste interpretiert diese Pakete als Heilung (+HP, begrenzt durch `health_max`).
* **Balancing:**
* Heal pro Tick konfigurierbar; zusätzlich wählbar: max. Anzahl Heilungen, Mindest-Pause zwischen Heilungen oder beides.
@@ -238,7 +238,7 @@ Die Logik für die Modi wird primär auf den Westen implementiert (Regelwerk), g
* `foe`: HP-Delta für andere Teams (-10 Schaden)
* `rssi`: Mindest-RSSI (dBm) für Wirksamkeit (z.B. -70 → nur nahe dran)
* `warn`: Anzahl Aussendungen als Warnung, bevor der Effekt scharf wird
* **Instant-Death Beispiel:** `team: 0, friend: -128, foe: -128, warn: 0, rssi: -60` → Jeder Empfänger mit RSSI besser als -60 dBm fällt sofort auf 0 HP.
* **Instant-Death Beispiel:** `team: 0, friend: -128, foe: -128, warn: 0, rssi: -60` → Jeder Empfänger mit RSSI besser als -60dBm fällt sofort auf 0 HP.
---
@@ -309,7 +309,7 @@ struct zone_effect_packet {
uint8_t team_id; // Besitzer der Zone (0=neutral)
int8_t friend_delta; // HP-Delta für eigenes Team (z.B. +20 Heal)
int8_t foe_delta; // HP-Delta für andere Teams (z.B. -10 Schaden)
int8_t rssi_thresh_dbm; // Mindest-RSSI für Wirksamkeit (z.B. -70 dBm)
int8_t rssi_thresh_dbm; // Mindest-RSSI für Wirksamkeit (z.B. -70dBm)
uint8_t warn_count; // Anzahl Warn-Pakete vor scharfem Effekt
} __packed;
@@ -389,7 +389,7 @@ Payloads von Power-Up-Stationen und Buzzer-Boxen (Protokoll-ID `0xBB`):
| Von | Nach | Auslöser | Aktion | Bedingung |
| :--- | :--- | :--- | :--- | :--- |
| Idle | Lobby | CoAP `GAME_STATE_LOBBY` | LED idle-Animation, warten auf Start | Multicast vom Leader |
| Lobby | Countdown | CoAP `GAME_START_COUNTDOWN` | Audio-Countdown 10→1 sec, Countdown-Timer init | Payload: 10 sek |
| Lobby | Countdown | CoAP `GAME_START_COUNTDOWN` | Audio-Countdown 10→1sec, Countdown-Timer init | Payload: 10 sek |
| Countdown | Running | Countdown = 0 | Health reset, Treffer-Sensor aktivieren, Waffe unlock | Timer lokal abgelaufen |
| Running | Dead | Health <= 0 | LED rot/aus, Dead-Sound, CoAP CMD_DISABLE an Waffe | Nach IR-Hit-Verarbeitung |
| Dead | Running | Respawn-Timer = 0 | Health reset, CoAP CMD_ENABLE an Waffe, Sensor on | Optional; Config-abhängig |

View File

@@ -39,7 +39,7 @@ Diese Roadmap führt vom nRF52840DK bis zum fertigen Produkt.
- [ ] Zephyr Setup: Installation des nRF Connect SDK (NCS) und VS Code.
- [ ] Custom Board Definition: Board-File anlegen, das die Pins des nRF52840DK auf die geplanten Funktionen mappt (PWM für IR, GPIO für Buttons).
- [ ] Thread Mesh: Minimalen OpenThread-Stack aufsetzen. Ein DK als Leader (FTD), einer als Child. UDP/CoAP-Ping bei Knopfdruck.
- [ ] IR-Engine: Custom IR-Protokoll (pulse-distance, 38 kHz) mit nrfx_pwm + PPI implementieren. Signal mit Oszilloskop/Logic Analyzer verifizieren. Sicherstellen, dass Funk die IR-Engine nicht stört. **Hinweis:** MilesTag2 wurde verworfen zugunsten eines eigenen, kürzeren Protokolls mit CRC8 (siehe [Spezifikationen](specifications/ir_protocol.md)).
- [ ] IR-Engine: Custom IR-Protokoll (pulse-distance, 38kHz) mit nrfx_pwm + PPI implementieren. Signal mit Oszilloskop/Logic Analyzer verifizieren. Sicherstellen, dass Funk die IR-Engine nicht stört. **Hinweis:** MilesTag2 wurde verworfen zugunsten eines eigenen, kürzeren Protokolls mit CRC8 (siehe [Spezifikationen](specifications/ir_protocol.md)).
#### Phase 2: Der "Prototyp" (Integration)
**Ziel:** Einbindung von Audio und Solenoid.

View File

@@ -2,78 +2,63 @@
## Übersicht
Das Infrarot-Kommunikationsprotokoll basiert auf Pulse-Distance-Codierung mit 38 kHz Träger und ist ähnlich Sony SIRC, aber optimiert für die Anforderungen des Lasertag-Systems. Das Protokoll bietet robuste Übertragung mit CRC-Fehlerprüfung und kurzen Frame-Zeiten (~36 ms).
Das Infrarot-Kommunikationsprotokoll basiert auf Pulse-Distance-Codierung mit 38kHz Träger und ist ähnlich Sony SIRC, aber optimiert für die Anforderungen des Lasertag-Systems. Das Protokoll bietet robuste Übertragung mit CRC-Fehlerprüfung und kurzen Frame-Zeiten (~36ms).
## Physikalische Schicht
| Parameter | Wert | Anmerkung |
|-----------|------|----------|
| Trägerfrequenz | 38 kHz | Standard für TSOP48xx Empfänger |
| Trägerfrequenz | 38kHz | Standard für TSOP48xx Empfänger |
| Tastgrad (Duty Cycle) | 50 % | Konfigurierbar (2575 %) |
| Modulation | PWM mit Pulse-Distance-Codierung | Hardware-basiert via nRF52 PWM-Peripheral |
| Empfänger | TSOP4838 (kompatibel) | Active-Low Ausgang, 38 kHz Bandpass |
| Empfänger | TSOP4838 (kompatibel) | Active-Low Ausgang, 38kHz Bandpass |
## Timing-Spezifikation
| Symbol | Dauer | Toleranz | Beschreibung |
|--------|-------|----------|-------------|
| **Start Burst** | 2400 µs | ±200 µs | Frame-Synchronisations-Impuls |
| **Mark** | 600 µs | ±100 µs | Träger AN (konstant für alle Bits) |
| **Space 0** | 600 µs | ±100 µs | Träger AUS für logisch 0 |
| **Space 1** | 1200 µs | ±150 µs | Träger AUS für logisch 1 |
| **Start Burst** | 4 × Basistakt (Standard 2400µs) | ±200µs | Träger AN, Frame-Synchronisations-Impuls |
| **Gap** | 1 × Basistakt (Standard 600µs) | ±100µs | Träger AUS nach Start (optional, konfigurierbar) |
| **Mark** | 1 × Basistakt (Standard 600µs) | ±100µs | Träger AN (konstant für alle Bits) |
| **Space 0** | 1 × Basistakt (Standard 600µs) | ±100µs | Träger AUS für logisch 0 |
| **Space 1** | 2 × Basistakt (Standard 1200µs) | ±150µs | Träger AUS für logisch 1 |
**Basistakt:** `CONFIG_IR_PROTO_BASE_US` (Standard 600µs). Alle Zeiten ergeben sich daraus per Multiplikatoren (`IR_PROTO_*_MULT`).
### Bit-Codierung
```
Bit 0: [Mark 600µs] + [Space 600µs] = 1.2 ms
Bit 1: [Mark 600µs] + [Space 1.2ms] = 1.8 ms
Bit 0: [Mark 600µs] + [Space 600µs] = 1.2ms
Bit 1: [Mark 600µs] + [Space 1.2ms] = 1.8ms
```
### Beispiel-Wellenform (3 Bits: `101`)
### Beispiel-Wellenform (3Bits: `101`)
**Träger-Timing für Bits 1-0-1:**
| Segment | Dauer | State | Bit-Wert |
|---------|-------|-------|---------|
| Mark | 600 µs | AN | |
| Space 1 | 1200 µs | AUS | **1** (1.8 ms total) |
| Mark | 600 µs | AN | |
| Space 0| 600 µs | AUS | **0** (1.2 ms total) |
| Mark | 600 µs | AN | |
| Space 1| 1200 µs | AUS | **1** (1.8 ms total) |
```mermaid
block-beta
columns 8
mark1["Mark<br>600 µs"] space1["Space 1<br>1200 µs"]:2 mark2["Mark<br>600 µs"] space2["Space 0<br>600 µs"] mark3["Mark<br>600 µs"] space3["Space 1<br>1200 µs"]:2
bit1["<b>1</b>"]:3 bit2["<b>0</b>"]:2 bit3["<b>1</b>"]:3
style space1 fill:none
style space2 fill:none
style space3 fill:none
```
| Mark | 600µs | AN | |
| Space 1 | 1200µs | AUS | **1** (1.8ms total) |
| Mark | 600µs | AN | |
| Space 0 | 600µs | AUS | **0** (1.2ms total) |
| Mark | 600µs | AN | |
| Space 1 | 1200µs | AUS | **1** (1.8ms total) |
## Frame-Format
Alle Frames bestehen aus 24 Bits, übertragen MSB-first:
Alle Frames bestehen aus 24Bits, übertragen MSB-first:
| Feld | Start Burst | Type | Data | CRC8 |
|------|-------------|------|------|------|
| **Dauer** | 2400 µs | 3 Bits | 13 Bits | 8 Bits |
| **Funktion** | Synchronisation | Frame-Typ | Payload | Fehlerprüfung |
| **Summe** | | | | **24 Bits** |
```mermaid
packet-beta
title Frame
+3: "Typ"
+13: "Payload"
+8: "Fehlerprüfung"
```
| Feld | Start Burst + Gap | Type | Data | CRC8 |
|------|-------------------|------|------|------|
| **Dauer** | (Start: 4× Basis) + (Gap: 1× Basis) | 3Bits | 13Bits | 8Bits |
| **Funktion** | Synchronisation | Frame-Typ | Payload | Fehlerprüfung |
| **Summe** | | | | **24Bits** |
**Gesamte Frame-Zeit:** ~36 ms (Start + 24 × 1.5 ms durchschnittliche Bit-Zeit)
**Gesamte Frame-Zeit:** ~39ms bei Standardwerten (Start 2400µs + Gap 600µs + 24 × 1.5ms Bit-Durchschnitt). Mit angepasstem Basistakt/Multi skaliert alles linear.
### Type-Feld (3 Bits)
### Type-Feld (3Bits)
| Wert | Typ | Beschreibung |
|------|-----|-------------|
@@ -83,7 +68,7 @@ title Frame
| `011` | Admin | System-Steuerbefehle |
| `100``111` | Reserviert | Zukünftige Nutzung |
### Data-Feld (13 Bits) Type-abhängig
### Data-Feld (13Bits) Type-abhängig
#### Hit-Frame (`000`)
@@ -112,12 +97,12 @@ title Frame
|-----------|------|-------------|-------------|
| 012 | Command Data | 08191 | Implementierungsdefinierte Steuerbefehle |
### CRC-Feld (8 Bits)
### CRC-Feld (8Bits)
- **Algorithmus:** CRC-8-CCITT
- **Polynom:** 0x07 (x⁸ + x² + x + 1)
- **Initialwert:** 0x00
- **Eingabe:** Type (3 Bits) + Data (13 Bits) = 16 Bits
- **Eingabe:** Type (3Bits) + Data (13Bits) = 16Bits
- **Zweck:** Fehlererkennung bei Bitfehlern durch Umgebungslicht oder Interferenzen
**Erwartete Fehlererkennungsrate:** >99.5 % für Einfach- oder Doppelbitfehler
@@ -131,7 +116,7 @@ Type: 000 (Hit)
Data: 00101010 01010 (ShooterID=42, Damage=10)
CRC8: [berechnet aus obigen Daten]
Kompletter Frame (24 Bits):
Kompletter Frame (24Bits):
000 00101010 01010 CCCCCCCC
│ │ │ └─ CRC8
│ │ └─ Damage (10)
@@ -141,10 +126,10 @@ Kompletter Frame (24 Bits):
**Übertragungsabfolge:**
1. Start Burst: 2400 µs Träger AN
2. Bit 0 (Type): 600 µs Mark + 600 µs Space
3. Bit 1 (Type): 600 µs Mark + 600 µs Space
4. Bit 2 (Type): 600 µs Mark + 600 µs Space
1. Start Burst: 2400µs Träger AN
2. Bit 0 (Type): 600µs Mark + 600µs Space
3. Bit 1 (Type): 600µs Mark + 600µs Space
4. Bit 2 (Type): 600µs Mark + 600µs Space
5. ... (21 weitere Bits)
6. Ende: Träger AUS
@@ -158,9 +143,9 @@ Kompletter Frame (24 Bits):
### Software-State-Machine
1. **IDLE:** Auf Start Burst warten (20002800 µs)
1. **IDLE:** Auf Start Burst warten (20002800µs)
2. **SYNC:** Start Burst erkannt, Vorbereitung zur Bit-Empfang
3. **DATA:** Space nach jedem Mark messen, 24 Bits dekodieren
3. **DATA:** Space nach jedem Mark messen, 24Bits dekodieren
4. **VALIDATE:** CRC prüfen, Frame bei Gültigkeit verarbeiten
### Timing-Toleranzen
@@ -173,12 +158,14 @@ Kompletter Frame (24 Bits):
Die Protokoll-Timing kann via Kconfig für verschiedene Umgebungen angepasst werden:
- `CONFIG_IR_SEND_CARRIER_HZ`: Trägerfrequenz (3045 kHz)
- `CONFIG_IR_SEND_CARRIER_HZ`: Trägerfrequenz (3045kHz)
- `CONFIG_IR_SEND_DUTY_CYCLE_PERCENT`: PWM Tastgrad (2575 %)
- `CONFIG_IR_SEND_MARK_US`: Mark-Dauer (3001000 µs)
- `CONFIG_IR_SEND_SPACE0_US`: Space für Bit 0 (3001000 µs)
- `CONFIG_IR_SEND_SPACE1_US`: Space für Bit 1 (8002000 µs)
- `CONFIG_IR_SEND_START_BURST_US`: Start Burst (15004000 µs)
- `CONFIG_IR_PROTO_BASE_US`: Basistakt (3001000µs)
- `CONFIG_IR_PROTO_START_MULT`: Startburst-Faktor (28)
- `CONFIG_IR_PROTO_GAP_MULT`: Gap-Faktor (04; 0 = kein Gap)
- `CONFIG_IR_PROTO_MARK_MULT`: Mark-Faktor (12)
- `CONFIG_IR_PROTO_SPACE0_MULT`: Space0-Faktor (13)
- `CONFIG_IR_PROTO_SPACE1_MULT`: Space1-Faktor (14)
Die Standardwerte folgen Sony SIRC Timing-Konventionen für bewährte Zuverlässigkeit.
@@ -186,12 +173,12 @@ Die Standardwerte folgen Sony SIRC Timing-Konventionen für bewährte Zuverläss
| Metrik | Wert |
|--------|------|
| Frame-Zeit | ~36 ms |
| Datenrate | ~670 bit/s |
| Frame-Zeit | ~39ms |
| Datenrate | ~410bit/s |
| Max. Spieler-IDs | 256 |
| Reichweite (Außen) | ~50100 m (abhängig von Sender-Leistung und Umgebungslicht) |
| Reichweite (Außen) | ~50100m (abhängig von Sender-Leistung und Umgebungslicht) |
| Fehler-Erkennung | >99.5 % via CRC-8 |
| Störfestigkeit | Hoch (Hardware-Bandpass 38 kHz) |
| Störfestigkeit | Hoch (Hardware-Bandpass 38kHz) |
---

View File

@@ -189,6 +189,6 @@ struct hit_report_packet {
Das IR-Signal nutzt eine 38kHz Trägerfrequenz (NEC-ähnlich).
Payload (32-bit):
* `8 bit` Protokoll-ID (Magic Byte zur Unterscheidung von Fernbedienungen)
* `16 bit` Shooter ID
* `8 bit` Info (4 bit Team, 4 bit Damage Class)
* `8bit` Protokoll-ID (Magic Byte zur Unterscheidung von Fernbedienungen)
* `16bit` Shooter ID
* `8bit` Info (4bit Team, 4bit Damage Class)

View File

@@ -12,7 +12,7 @@ int main(void)
lasertag_utils_init();
/* Initialize and start BLE management for provisioning */
int rc = ble_mgmt_init();
int rc = ble_mgmt_init(LT_TYPE_LEADER);
if (rc) {
LOG_ERR("BLE initialization failed (err %d)", rc);
return rc;
@@ -20,6 +20,14 @@ int main(void)
LOG_INF("BLE Management initialized successfully.");
}
/* Start BLE advertising */
rc = ble_mgmt_adv_start();
if (rc) {
LOG_ERR("BLE advertising start failed (err %d)", rc);
} else {
LOG_INF("BLE advertising started.");
}
/* Initialize and start OpenThread stack */
rc = thread_mgmt_init();
if (rc) {

View File

@@ -0,0 +1,13 @@
cmake_minimum_required(VERSION 3.20)
# Tell Zephyr to look into our libs folder for extra modules
list(APPEND ZEPHYR_EXTRA_MODULES ${CMAKE_CURRENT_SOURCE_DIR}/../../libs)
# Set board root to find custom board overlays in firmware/boards
set(BOARD_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/../..)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(lasertag_vest)
# Define application source files
target_sources(app PRIVATE src/main.c)

View File

@@ -0,0 +1,41 @@
# Console and Logging
CONFIG_LOG=y
# Shell and Built-in Commands
CONFIG_SHELL=y
CONFIG_KERNEL_SHELL=y
CONFIG_DEVICE_SHELL=y
CONFIG_REBOOT=y
# --- STACK SIZE UPDATES (Fixes the Hard Fault) ---
CONFIG_MAIN_STACK_SIZE=4096
CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=2048
CONFIG_BT_RX_STACK_SIZE=2048
# Storage and Settings (NVS)
CONFIG_FLASH=y
CONFIG_FLASH_MAP=y
CONFIG_NVS=y
CONFIG_SETTINGS=y
# Network and OpenThread
CONFIG_NETWORKING=y
CONFIG_NET_L2_OPENTHREAD=y
CONFIG_OPENTHREAD=y
CONFIG_OPENTHREAD_FTD=y
CONFIG_OPENTHREAD_SHELL=y
# --- CoAP & UDP Features ---
CONFIG_OPENTHREAD_COAP=y
CONFIG_OPENTHREAD_MANUAL_START=y
# Bluetooth
CONFIG_BT=y
CONFIG_BT_PERIPHERAL=y
CONFIG_BT_DEVICE_NAME="Lasertag-Device"
CONFIG_BT_DEVICE_NAME_DYNAMIC=y
# Enable Lasertag Shared Modules
CONFIG_LASERTAG_UTILS=y
CONFIG_THREAD_MGMT=y
CONFIG_BLE_MGMT=y

View File

@@ -0,0 +1,46 @@
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <lasertag_utils.h>
#include <thread_mgmt.h>
#include <ble_mgmt.h>
LOG_MODULE_REGISTER(vest_app, CONFIG_LOG_DEFAULT_LEVEL);
int main(void)
{
/* Initialize shared project logic and NVS */
lasertag_utils_init();
/* Initialize and start BLE management for provisioning */
int rc = ble_mgmt_init(LT_TYPE_VEST);
if (rc) {
LOG_ERR("BLE initialization failed (err %d)", rc);
return rc;
} else {
LOG_INF("BLE Management initialized successfully.");
}
/* Start BLE advertising */
rc = ble_mgmt_adv_start();
if (rc) {
LOG_ERR("BLE advertising start failed (err %d)", rc);
} else {
LOG_INF("BLE advertising started.");
}
/* Initialize and start OpenThread stack */
rc = thread_mgmt_init();
if (rc) {
LOG_ERR("Thread initialization failed (err %d)", rc);
} else {
LOG_INF("Leader Application successfully started with Thread Mesh.");
return rc;
}
while (1) {
/* Main loop - handle high-level game logic here */
k_sleep(K_MSEC(1000));
}
return 0;
}

View File

@@ -1,6 +1,6 @@
cmake_minimum_required(VERSION 3.20)
# Zephyr mitteilen, dass unsere Libs Teil des Projekts sind
# Tell Zephyr that our libs are part of the project
list(APPEND ZEPHYR_EXTRA_MODULES ${CMAKE_CURRENT_SOURCE_DIR}/../../libs)
# Set board root to find custom board overlays in firmware/boards

View File

@@ -4,12 +4,12 @@
#include <openthread/thread.h>
#include <openthread/coap.h>
/* Unsere neue Library */
/* Our new library */
#include "game_logic.h"
LOG_MODULE_REGISTER(weapon_app, LOG_LEVEL_INF);
/* Spiel-Kontext */
/* Game context */
static struct game_ctx game;
/* Forward Declarations */
@@ -19,15 +19,15 @@ static void on_button_changed(uint32_t button_state, uint32_t has_changed);
static void on_game_state_change(enum game_state new_state)
{
LOG_INF("APP: Spielstatus geändert -> %d", new_state);
LOG_INF("APP: Game state changed -> %d", new_state);
switch (new_state) {
case GAME_STATE_RUNNING:
dk_set_led_on(DK_LED1); // LED an wenn Spiel läuft
dk_set_led_on(DK_LED1); // LED on when game is running
break;
case GAME_STATE_FINISHED:
dk_set_led_off(DK_LED1);
// Blinken oder ähnliches
// Blink or similar
break;
default:
dk_set_led_off(DK_LED1);
@@ -37,46 +37,46 @@ static void on_game_state_change(enum game_state new_state)
static void on_hit_received(uint16_t shooter_id)
{
LOG_WARN("APP: AUA! Getroffen von Spieler %d. Leben: %d", shooter_id, game.health);
LOG_WARN("APP: OUCH! Hit by player %d. Health: %d", shooter_id, game.health);
// Visuelles Feedback: LED 2 blinkt kurz
dk_set_led_on(DK_LED2);
k_msleep(200);
dk_set_led_off(DK_LED2);
// TODO: Hier später CoAP Nachricht an Leader senden!
// TODO: Send CoAP message to leader later!
// send_hit_report_to_leader(...);
}
static void on_shot_fired(void)
{
LOG_INF("APP: PENG! Schuss abgefeuert.");
// TODO: Hier IR-Protokoll senden (NEC/RC5)
LOG_INF("APP: BANG! Shot fired.");
// TODO: Send IR protocol here (NEC/RC5)
}
/* --- Hardware Callbacks --- */
static void on_button_changed(uint32_t button_state, uint32_t has_changed)
{
// Button 1: Schießen
// Button 1: Shoot
if ((has_changed & DK_BTN1_MSK) && (button_state & DK_BTN1_MSK)) {
if (game.current_state == GAME_STATE_RUNNING) {
on_shot_fired();
} else {
LOG_INF("Schuss blockiert - Spiel läuft nicht.");
LOG_INF("Shot blocked - game not running.");
}
}
// Button 2: Treffer simulieren (Self-Hit Test)
// Button 2: Simulate hit (Self-Hit Test)
if ((has_changed & DK_BTN2_MSK) && (button_state & DK_BTN2_MSK)) {
LOG_INF("Simuliere Treffer durch Spieler 99...");
LOG_INF("Simulating hit by player 99...");
struct game_hit_packet hit_packet;
// Wir tun so, als hätte der IR-Sensor Spieler 99 erkannt
// Pretend the IR sensor detected player 99
if (game_logic_register_hit(99, &hit_packet)) {
// Wenn Treffer gültig war (Spiel läuft, wir leben noch), haben wir jetzt ein Paket
// das wir via Thread versenden könnten.
LOG_INF("Treffer registriert! Damage: %d", hit_packet.damage);
// If hit was valid (game running, we're still alive), we now have a packet
// that we could send via Thread.
LOG_INF("Hit registered! Damage: %d", hit_packet.damage);
}
}
}
@@ -89,18 +89,18 @@ void main(void)
int err = dk_buttons_init(on_button_changed);
if (err) {
LOG_ERR("Buttons konnten nicht initialisiert werden (err %d)", err);
LOG_ERR("Buttons could not be initialized (err %d)", err);
}
// Game Logic Setup
game.on_state_change = on_game_state_change;
game.on_hit_received = on_hit_received;
// Initialisiere als Spieler mit ID aus Kconfig (oder NVS später)
// Initialize as player with ID from Kconfig (or NVS later)
game_logic_init(&game, CONFIG_LASERTAG_PLAYER_ID_DEFAULT);
// Zum Testen setzen wir den Status manuell auf RUNNING,
// bis wir das Start-Signal vom Leader via Thread empfangen.
// For testing, we manually set the status to RUNNING,
// until we receive the start signal from the leader via Thread.
struct game_state_packet fake_start = {.state = GAME_STATE_RUNNING};
game_logic_handle_state_update(&fake_start);

View File

@@ -15,6 +15,6 @@ CONFIG_LASERTAG_GAME_LOGIC=y
CONFIG_LASERTAG_ROLE_PLAYER=y
CONFIG_LASERTAG_PLAYER_ID_DEFAULT=2
# Optional: Shell für Debugging
# Optional: Shell for debugging
CONFIG_SHELL=y
CONFIG_OPENTHREAD_SHELL=y

View File

@@ -3,4 +3,4 @@
add_subdirectory(ble_mgmt)
add_subdirectory(thread_mgmt)
add_subdirectory(lasertag_utils)
add_subdirectory(ir_send)
add_subdirectory(ir)

View File

@@ -2,4 +2,4 @@
rsource "lasertag_utils/Kconfig"
rsource "thread_mgmt/Kconfig"
rsource "ble_mgmt/Kconfig"
rsource "ir_send/Kconfig"
rsource "ir/Kconfig"

View File

@@ -3,14 +3,25 @@
/**
* @file ble_mgmt.h
* @brief Bluetooth Low Energy management for provisioning.
* @brief Bluetooth Low Energy management for provisioning and game communication.
* This module handles Bluetooth initialization, advertising, and stopping advertising.
*/
/**
* @brief Device types for LaserTag devices.
*/
#define LT_TYPE_LEADER 0x01
#define LT_TYPE_WEAPON 0x02
#define LT_TYPE_VEST 0x03
#define LT_TYPE_BEACON 0x04
/**
* @brief Initialize Bluetooth and prepare services.
*
* @param device_type The type of the device (e.g., leader, weapon, vest, beacon).
* @return 0 on success.
*/
int ble_mgmt_init(void);
int ble_mgmt_init(uint8_t device_type);
/**
* @brief Start Bluetooth advertising so the web app can find the device.

View File

@@ -13,62 +13,83 @@
LOG_MODULE_REGISTER(ble_mgmt, CONFIG_BLE_MGMT_LOG_LEVEL);
/**
* Basis UUID: 03afe2cf-6c64-4a22-9289-c3ae820cXXXX
* Alias-Stellen: Byte 12 & 13
* Base UUID: 03afe2cf-6c64-4a22-9289-c3ae820cXXXX
* Alias positions: Byte 12 & 13
*/
#define LT_UUID_BASE_VAL \
BT_UUID_128_ENCODE(0x03afe2cf, 0x6c64, 0x4a22, 0x9289, 0xc3ae820c0000)
BT_UUID_128_ENCODE(0x03afe2cf, 0x6c64, 0x4a22, 0x9289, 0xc3ae820c0000)
/* ==========================================================================
SERVICE 1: PROVISIONING (0x10XX)
========================================================================== */
#define BT_UUID_LT_PROV_SERVICE BT_UUID_DECLARE_128(BT_UUID_128_ENCODE(0x03afe2cf, 0x6c64, 0x4a22, 0x9289, 0xc3ae820c1000))
#define BT_UUID_LT_PROV_SERVICE BT_UUID_DECLARE_128(BT_UUID_128_ENCODE(0x03afe2cf, 0x6c64, 0x4a22, 0x9289, 0xc3ae820c1000))
#define BT_UUID_LT_PROV_NAME_CHAR BT_UUID_DECLARE_128(BT_UUID_128_ENCODE(0x03afe2cf, 0x6c64, 0x4a22, 0x9289, 0xc3ae820c1001))
#define BT_UUID_LT_PROV_PANID_CHAR BT_UUID_DECLARE_128(BT_UUID_128_ENCODE(0x03afe2cf, 0x6c64, 0x4a22, 0x9289, 0xc3ae820c1002))
#define BT_UUID_LT_PROV_CHAN_CHAR BT_UUID_DECLARE_128(BT_UUID_128_ENCODE(0x03afe2cf, 0x6c64, 0x4a22, 0x9289, 0xc3ae820c1003))
#define BT_UUID_LT_PROV_EXTPAN_CHAR BT_UUID_DECLARE_128(BT_UUID_128_ENCODE(0x03afe2cf, 0x6c64, 0x4a22, 0x9289, 0xc3ae820c1004))
#define BT_UUID_LT_PROV_NETKEY_CHAR BT_UUID_DECLARE_128(BT_UUID_128_ENCODE(0x03afe2cf, 0x6c64, 0x4a22, 0x9289, 0xc3ae820c1005))
#define BT_UUID_LT_PROV_NAME_CHAR BT_UUID_DECLARE_128(BT_UUID_128_ENCODE(0x03afe2cf, 0x6c64, 0x4a22, 0x9289, 0xc3ae820c1001))
#define BT_UUID_LT_PROV_PANID_CHAR BT_UUID_DECLARE_128(BT_UUID_128_ENCODE(0x03afe2cf, 0x6c64, 0x4a22, 0x9289, 0xc3ae820c1002))
#define BT_UUID_LT_PROV_CHAN_CHAR BT_UUID_DECLARE_128(BT_UUID_128_ENCODE(0x03afe2cf, 0x6c64, 0x4a22, 0x9289, 0xc3ae820c1003))
#define BT_UUID_LT_PROV_EXTPAN_CHAR BT_UUID_DECLARE_128(BT_UUID_128_ENCODE(0x03afe2cf, 0x6c64, 0x4a22, 0x9289, 0xc3ae820c1004))
#define BT_UUID_LT_PROV_NETKEY_CHAR BT_UUID_DECLARE_128(BT_UUID_128_ENCODE(0x03afe2cf, 0x6c64, 0x4a22, 0x9289, 0xc3ae820c1005))
#define BT_UUID_LT_PROV_NETNAME_CHAR BT_UUID_DECLARE_128(BT_UUID_128_ENCODE(0x03afe2cf, 0x6c64, 0x4a22, 0x9289, 0xc3ae820c1006))
#define BT_UUID_LT_PROV_NODES_CHAR BT_UUID_DECLARE_128(BT_UUID_128_ENCODE(0x03afe2cf, 0x6c64, 0x4a22, 0x9289, 0xc3ae820c1007))
#define BT_UUID_LT_PROV_NODES_CHAR BT_UUID_DECLARE_128(BT_UUID_128_ENCODE(0x03afe2cf, 0x6c64, 0x4a22, 0x9289, 0xc3ae820c1007))
#define BT_UUID_LT_PROV_TYPE_CHAR BT_UUID_DECLARE_128(BT_UUID_128_ENCODE(0x03afe2cf, 0x6c64, 0x4a22, 0x9289, 0xc3ae820c1008))
/* ==========================================================================
SERVICE 2: GAME (0x20XX)
========================================================================== */
#define BT_UUID_LT_GAME_SERVICE BT_UUID_DECLARE_128(BT_UUID_128_ENCODE(0x03afe2cf, 0x6c64, 0x4a22, 0x9289, 0xc3ae820c2000))
#define BT_UUID_LT_GAME_SERVICE BT_UUID_DECLARE_128(BT_UUID_128_ENCODE(0x03afe2cf, 0x6c64, 0x4a22, 0x9289, 0xc3ae820c2000))
#define BT_UUID_LT_GAME_CONFIG_CHAR BT_UUID_DECLARE_128(BT_UUID_128_ENCODE(0x03afe2cf, 0x6c64, 0x4a22, 0x9289, 0xc3ae820c2001))
#define BT_UUID_LT_GAME_CONFIG_CHAR BT_UUID_DECLARE_128(BT_UUID_128_ENCODE(0x03afe2cf, 0x6c64, 0x4a22, 0x9289, 0xc3ae820c2001))
#define BT_UUID_LT_GAME_COMMAND_CHAR BT_UUID_DECLARE_128(BT_UUID_128_ENCODE(0x03afe2cf, 0x6c64, 0x4a22, 0x9289, 0xc3ae820c2002))
#define BT_UUID_LT_GAME_LOG_DATA_CHAR BT_UUID_DECLARE_128(BT_UUID_128_ENCODE(0x03afe2cf, 0x6c64, 0x4a22, 0x9289, 0xc3ae820c2003))
/* --- Global Variables --- */
static uint8_t device_role = 0; // Store device type for provisioning
/* --- GATT Callbacks --- */
static ssize_t read_lasertag_val(struct bt_conn *conn, const struct bt_gatt_attr *attr,
void *buf, uint16_t len, uint16_t offset)
void *buf, uint16_t len, uint16_t offset)
{
const char *val_ptr = NULL;
size_t val_len = 0;
if (bt_uuid_cmp(attr->uuid, BT_UUID_LT_PROV_NAME_CHAR) == 0) {
if (bt_uuid_cmp(attr->uuid, BT_UUID_LT_PROV_TYPE_CHAR) == 0)
{
val_ptr = (char *)&device_role;
val_len = sizeof(device_role);
}
else if (bt_uuid_cmp(attr->uuid, BT_UUID_LT_PROV_NAME_CHAR) == 0)
{
val_ptr = lasertag_get_device_name();
val_len = strlen(val_ptr);
} else if (bt_uuid_cmp(attr->uuid, BT_UUID_LT_PROV_PANID_CHAR) == 0) {
}
else if (bt_uuid_cmp(attr->uuid, BT_UUID_LT_PROV_PANID_CHAR) == 0)
{
static uint16_t pan_id;
pan_id = lasertag_get_thread_pan_id();
val_ptr = (char *)&pan_id;
val_len = sizeof(pan_id);
} else if (bt_uuid_cmp(attr->uuid, BT_UUID_LT_PROV_CHAN_CHAR) == 0) {
}
else if (bt_uuid_cmp(attr->uuid, BT_UUID_LT_PROV_CHAN_CHAR) == 0)
{
static uint8_t chan;
chan = lasertag_get_thread_channel();
val_ptr = (char *)&chan;
val_len = sizeof(chan);
} else if (bt_uuid_cmp(attr->uuid, BT_UUID_LT_PROV_EXTPAN_CHAR) == 0) {
}
else if (bt_uuid_cmp(attr->uuid, BT_UUID_LT_PROV_EXTPAN_CHAR) == 0)
{
val_ptr = (char *)lasertag_get_thread_ext_pan_id();
val_len = 8;
} else if (bt_uuid_cmp(attr->uuid, BT_UUID_LT_PROV_NETKEY_CHAR) == 0) {
}
else if (bt_uuid_cmp(attr->uuid, BT_UUID_LT_PROV_NETKEY_CHAR) == 0)
{
val_ptr = (char *)lasertag_get_thread_network_key();
val_len = 16;
} else if (bt_uuid_cmp(attr->uuid, BT_UUID_LT_PROV_NETNAME_CHAR) == 0) {
}
else if (bt_uuid_cmp(attr->uuid, BT_UUID_LT_PROV_NETNAME_CHAR) == 0)
{
val_ptr = lasertag_get_thread_network_name();
val_len = strlen(val_ptr);
}
@@ -77,110 +98,138 @@ static ssize_t read_lasertag_val(struct bt_conn *conn, const struct bt_gatt_attr
}
static ssize_t write_lasertag_val(struct bt_conn *conn, const struct bt_gatt_attr *attr,
const void *buf, uint16_t len, uint16_t offset, uint8_t flags)
const void *buf, uint16_t len, uint16_t offset, uint8_t flags)
{
int rc = 0;
if (bt_uuid_cmp(attr->uuid, BT_UUID_LT_PROV_NAME_CHAR) == 0) {
if (bt_uuid_cmp(attr->uuid, BT_UUID_LT_PROV_NAME_CHAR) == 0)
{
rc = lasertag_set_device_name(buf, len);
} else if (bt_uuid_cmp(attr->uuid, BT_UUID_LT_PROV_PANID_CHAR) == 0) {
if (len != 2) return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
rc = lasertag_set_thread_pan_id(*(uint16_t*)buf);
} else if (bt_uuid_cmp(attr->uuid, BT_UUID_LT_PROV_CHAN_CHAR) == 0) {
if (len != 1) return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
rc = lasertag_set_thread_channel(*(uint8_t*)buf);
} else if (bt_uuid_cmp(attr->uuid, BT_UUID_LT_PROV_EXTPAN_CHAR) == 0) {
if (len != 8) return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
}
else if (bt_uuid_cmp(attr->uuid, BT_UUID_LT_PROV_PANID_CHAR) == 0)
{
if (len != 2)
return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
rc = lasertag_set_thread_pan_id(*(uint16_t *)buf);
}
else if (bt_uuid_cmp(attr->uuid, BT_UUID_LT_PROV_CHAN_CHAR) == 0)
{
if (len != 1)
return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
rc = lasertag_set_thread_channel(*(uint8_t *)buf);
}
else if (bt_uuid_cmp(attr->uuid, BT_UUID_LT_PROV_EXTPAN_CHAR) == 0)
{
if (len != 8)
return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
rc = lasertag_set_thread_ext_pan_id(buf);
} else if (bt_uuid_cmp(attr->uuid, BT_UUID_LT_PROV_NETKEY_CHAR) == 0) {
if (len != 16) return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
}
else if (bt_uuid_cmp(attr->uuid, BT_UUID_LT_PROV_NETKEY_CHAR) == 0)
{
if (len != 16)
return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
rc = lasertag_set_thread_network_key(buf);
} else if (bt_uuid_cmp(attr->uuid, BT_UUID_LT_PROV_NETNAME_CHAR) == 0) {
}
else if (bt_uuid_cmp(attr->uuid, BT_UUID_LT_PROV_NETNAME_CHAR) == 0)
{
rc = lasertag_set_thread_network_name(buf, len);
}
if (rc) return BT_GATT_ERR(BT_ATT_ERR_UNLIKELY);
if (rc)
return BT_GATT_ERR(BT_ATT_ERR_UNLIKELY);
return len;
}
static ssize_t read_discovered_nodes(struct bt_conn *conn, const struct bt_gatt_attr *attr,
void *buf, uint16_t len, uint16_t offset)
void *buf, uint16_t len, uint16_t offset)
{
const char *list = thread_mgmt_get_discovered_list();
return bt_gatt_attr_read(conn, attr, buf, len, offset, list, strlen(list));
}
static ssize_t write_discover_cmd(struct bt_conn *conn, const struct bt_gatt_attr *attr,
const void *buf, uint16_t len, uint16_t offset, uint8_t flags)
const void *buf, uint16_t len, uint16_t offset, uint8_t flags)
{
/* Wenn irgendwas geschrieben wird, triggere Discovery im Thread Mesh */
/* If anything is written, trigger discovery in Thread Mesh */
thread_mgmt_discover_nodes();
return len;
}
/* Service Definition */
BT_GATT_SERVICE_DEFINE(provisioning_svc,
BT_GATT_PRIMARY_SERVICE(BT_UUID_LT_PROV_SERVICE),
/* Gerätename */
BT_GATT_CHARACTERISTIC(BT_UUID_LT_PROV_NAME_CHAR,
BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE,
BT_GATT_PERM_READ | BT_GATT_PERM_WRITE,
read_lasertag_val, write_lasertag_val, NULL),
/* Thread PAN ID */
BT_GATT_CHARACTERISTIC(BT_UUID_LT_PROV_PANID_CHAR,
BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE,
BT_GATT_PERM_READ | BT_GATT_PERM_WRITE,
read_lasertag_val, write_lasertag_val, NULL),
BT_GATT_PRIMARY_SERVICE(BT_UUID_LT_PROV_SERVICE),
/* Thread Kanal */
BT_GATT_CHARACTERISTIC(BT_UUID_LT_PROV_CHAN_CHAR,
BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE,
BT_GATT_PERM_READ | BT_GATT_PERM_WRITE,
read_lasertag_val, write_lasertag_val, NULL),
/* Device name */
BT_GATT_CHARACTERISTIC(BT_UUID_LT_PROV_NAME_CHAR,
BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE,
BT_GATT_PERM_READ | BT_GATT_PERM_WRITE,
read_lasertag_val, write_lasertag_val, NULL),
/* Extended PAN ID */
BT_GATT_CHARACTERISTIC(BT_UUID_LT_PROV_EXTPAN_CHAR,
BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE,
BT_GATT_PERM_READ | BT_GATT_PERM_WRITE,
read_lasertag_val, write_lasertag_val, NULL),
/* Device Type (Read-only) */
BT_GATT_CHARACTERISTIC(BT_UUID_LT_PROV_TYPE_CHAR,
BT_GATT_CHRC_READ,
BT_GATT_PERM_READ,
read_lasertag_val, NULL, NULL),
/* Thread PAN ID */
BT_GATT_CHARACTERISTIC(BT_UUID_LT_PROV_PANID_CHAR,
BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE,
BT_GATT_PERM_READ | BT_GATT_PERM_WRITE,
read_lasertag_val, write_lasertag_val, NULL),
/* Netzwerk Key */
BT_GATT_CHARACTERISTIC(BT_UUID_LT_PROV_NETKEY_CHAR,
BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE,
BT_GATT_PERM_READ | BT_GATT_PERM_WRITE,
read_lasertag_val, write_lasertag_val, NULL),
/* Thread Channel */
BT_GATT_CHARACTERISTIC(BT_UUID_LT_PROV_CHAN_CHAR,
BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE,
BT_GATT_PERM_READ | BT_GATT_PERM_WRITE,
read_lasertag_val, write_lasertag_val, NULL),
/* Thread Netzwerk Name */
BT_GATT_CHARACTERISTIC(BT_UUID_LT_PROV_NETNAME_CHAR,
BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE,
BT_GATT_PERM_READ | BT_GATT_PERM_WRITE,
read_lasertag_val, write_lasertag_val, NULL),
/* Extended PAN ID */
BT_GATT_CHARACTERISTIC(BT_UUID_LT_PROV_EXTPAN_CHAR,
BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE,
BT_GATT_PERM_READ | BT_GATT_PERM_WRITE,
read_lasertag_val, write_lasertag_val, NULL),
/* Knoten-Liste / Discovery Trigger */
BT_GATT_CHARACTERISTIC(BT_UUID_LT_PROV_NODES_CHAR,
BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE,
BT_GATT_PERM_READ | BT_GATT_PERM_WRITE,
read_discovered_nodes, write_discover_cmd, NULL),
);
/* Network Key */
BT_GATT_CHARACTERISTIC(BT_UUID_LT_PROV_NETKEY_CHAR,
BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE,
BT_GATT_PERM_READ | BT_GATT_PERM_WRITE,
read_lasertag_val, write_lasertag_val, NULL),
/* Thread Network Name */
BT_GATT_CHARACTERISTIC(BT_UUID_LT_PROV_NETNAME_CHAR,
BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE,
BT_GATT_PERM_READ | BT_GATT_PERM_WRITE,
read_lasertag_val, write_lasertag_val, NULL),
/* Node List / Discovery Trigger */
BT_GATT_CHARACTERISTIC(BT_UUID_LT_PROV_NODES_CHAR,
BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE,
BT_GATT_PERM_READ | BT_GATT_PERM_WRITE,
read_discovered_nodes, write_discover_cmd, NULL), );
static uint8_t mfg_data[] = { 0xff, 0xff, 0x00 }; // Last byte for device role
static const struct bt_data ad[] = {
BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)),
BT_DATA_BYTES(BT_DATA_UUID128_ALL,
0x00, 0xbc, 0x0c, 0x82, 0xae, 0xc3, 0x89, 0x92,
0x22, 0x4a, 0x64, 0x6c, 0xcf, 0xe2, 0xaf, 0x03),
BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)),
BT_DATA_BYTES(BT_DATA_UUID128_ALL,
0x00, 0x10, 0x0c, 0x82, 0xae, 0xc3, 0x89, 0x92,
0x22, 0x4a, 0x64, 0x6c, 0xcf, 0xe2, 0xaf, 0x03),
BT_DATA(BT_DATA_MANUFACTURER_DATA, mfg_data, sizeof(mfg_data)),
};
int ble_mgmt_init(void)
int ble_mgmt_init(uint8_t device_type)
{
device_role = device_type;
int err = bt_enable(NULL);
if (err) return err;
LOG_INF("Bluetooth initialisiert");
if (err)
return err;
LOG_INF("Bluetooth initialized");
return 0;
}
int ble_mgmt_adv_start(void)
{
const char set_device_role = device_role;
mfg_data[2] = set_device_role; // Update device role in advertising data
const char *name = lasertag_get_device_name();
bt_set_name(name);
@@ -196,8 +245,9 @@ int ble_mgmt_adv_start(void)
};
int err = bt_le_adv_start(&adv_param, ad, ARRAY_SIZE(ad), dynamic_sd, ARRAY_SIZE(dynamic_sd));
if (!err) {
LOG_INF("Advertising gestartet als: %s", name);
if (!err)
{
LOG_INF("Advertising started as: %s, type: %d", name, device_role);
}
return err;
}
@@ -205,8 +255,9 @@ int ble_mgmt_adv_start(void)
int ble_mgmt_adv_stop(void)
{
int err = bt_le_adv_stop();
if (!err) {
LOG_INF("Advertising gestoppt");
if (!err)
{
LOG_INF("Advertising stopped");
}
return err;
}

View File

@@ -0,0 +1,2 @@
add_subdirectory(send)
add_subdirectory(recv)

73
firmware/libs/ir/Kconfig Normal file
View File

@@ -0,0 +1,73 @@
menu "IR protocol"
config IR_PROTO
bool "IR protocol core"
help
Enable shared IR protocol settings used by send/receive libraries.
if IR_PROTO
menu "IR protocol configuration"
config IR_SEND_CARRIER_HZ
int "IR carrier frequency (Hz)"
default 38000
range 30000 45000
help
Carrier frequency for PWM generation. Standard value is 38 kHz for TSOP48xx receivers.
config IR_SEND_DUTY_CYCLE_PERCENT
int "Carrier duty cycle (%)"
default 50
range 25 75
help
PWM duty cycle percentage. Some receivers prefer 33%, others 50%.
Default 50% works with most TSOP-series receivers.
config IR_PROTO_BASE_US
int "IR base period (microseconds)"
default 600
range 300 1000
help
Base timing used for start/mark/space. Default 600 µs (similar to Sony SIRC).
config IR_PROTO_START_MULT
int "Start burst factor"
default 4
range 2 8
help
Start burst duration = base × factor. Default 4× for robust sync.
config IR_PROTO_GAP_MULT
int "Start gap factor"
default 1
range 0 4
help
Gap after start burst (carrier off) = base × factor. 0 disables the gap.
config IR_PROTO_MARK_MULT
int "Mark factor"
default 1
range 1 2
help
Mark duration = base × factor. Default 1×.
config IR_PROTO_SPACE0_MULT
int "Space0 factor (bit 0)"
default 1
range 1 3
help
Space for bit 0 = base × factor. Default 1×.
config IR_PROTO_SPACE1_MULT
int "Space1 factor (bit 1)"
default 2
range 1 4
help
Space for bit 1 = base × factor. Default 2× (double base time).
endmenu
endif
endmenu

View File

@@ -0,0 +1,5 @@
if(CONFIG_IR_RECV)
zephyr_library()
zephyr_sources(src/ir_recv.c)
zephyr_include_directories(include)
endif()

View File

@@ -0,0 +1,6 @@
config IR_RECV
bool "IR Receive Library"
select IR_PROTO
help
Enable IR receive library for the laser tag system.
Placeholder implementation; timing parameters come from IR_PROTO.*

View File

@@ -0,0 +1,14 @@
#ifndef IR_RECV_H
#define IR_RECV_H
#include <zephyr/device.h>
/**
* @brief Initialize IR receive pipeline (stub).
*
* Intended to configure GPIO/interrupts/ppi for future implementation.
* @return 0 on success, negative errno otherwise.
*/
int ir_recv_init(void);
#endif /* IR_RECV_H */

View File

@@ -0,0 +1,12 @@
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include "ir_recv.h"
LOG_MODULE_REGISTER(ir_recv, LOG_LEVEL_INF);
int ir_recv_init(void)
{
LOG_INF("IR receive stub initialized (no implementation yet)");
return 0;
}

View File

@@ -0,0 +1,6 @@
config IR_SEND
bool "IR Send Library"
select IR_PROTO
help
Enable IR transmission library for laser tag system.
Provides PWM-based IR carrier generation with pulse-distance coding.

View File

@@ -24,37 +24,47 @@ config IR_SEND_DUTY_CYCLE_PERCENT
PWM duty cycle percentage. Some receivers prefer 33%, others 50%.
Default 50% works with most TSOP-series receivers.
config IR_SEND_MARK_US
int "Mark duration (microseconds)"
config IR_PROTO_BASE_US
int "IR Basistakt (Mikrosekunden)"
default 600
range 300 1000
help
Duration of carrier burst (mark) for each bit. Standard is 600 µs
following Sony SIRC timing.
Gemeinsamer Basistakt für Mark/Space/Start. Standard 600 µs (Sony SIRC ähnlich).
config IR_SEND_SPACE0_US
int "Space duration for bit 0 (microseconds)"
default 600
range 300 1000
config IR_PROTO_START_MULT
int "Startburst Faktor"
default 4
range 2 8
help
Carrier-off duration (space) after mark for logical 0.
Default 600 µs creates 1.2 ms total bit time.
Startburst-Dauer = Basistakt × Faktor. Standard 4× zur sicheren Synchronisation.
config IR_SEND_SPACE1_US
int "Space duration for bit 1 (microseconds)"
default 1200
range 800 2000
config IR_PROTO_GAP_MULT
int "Start-Gap Faktor"
default 1
range 0 4
help
Carrier-off duration (space) after mark for logical 1.
Default 1200 µs creates 1.8 ms total bit time.
Gap nach Startburst (Träger AUS) = Basistakt × Faktor. 0 deaktiviert Gap.
config IR_SEND_START_BURST_US
int "Start burst duration (microseconds)"
default 2400
range 1500 4000
config IR_PROTO_MARK_MULT
int "Mark Faktor"
default 1
range 1 2
help
Initial synchronization burst at frame start. Receivers detect
this to sync on incoming frames. Default 2400 µs = 4× mark time.
Mark-Dauer = Basistakt × Faktor. Standard 1×.
config IR_PROTO_SPACE0_MULT
int "Space0 Faktor (Bit 0)"
default 1
range 1 3
help
Space für Bit 0 = Basistakt × Faktor. Standard 1×.
config IR_PROTO_SPACE1_MULT
int "Space1 Faktor (Bit 1)"
default 2
range 1 4
help
Space für Bit 1 = Basistakt × Faktor. Standard 2× (doppelte Basiszeit).
endmenu

View File

@@ -49,4 +49,48 @@ const uint8_t* lasertag_get_thread_ext_pan_id(void);
*/
const uint8_t* lasertag_get_thread_network_key(void);
/**
* @brief Set the device name.
* @param name Pointer to the name string.
* @param len Length of the name.
* @return 0 on success, negative error code otherwise.
*/
int lasertag_set_device_name(const char *name, size_t len);
/**
* @brief Set the Thread PAN ID.
* @param pan_id 16-bit PAN ID.
* @return 0 on success, negative error code otherwise.
*/
int lasertag_set_thread_pan_id(uint16_t pan_id);
/**
* @brief Set the Thread Network Name.
* @param name Pointer to the network name string.
* @param len Length of the name.
* @return 0 on success, negative error code otherwise.
*/
int lasertag_set_thread_network_name(const char *name, size_t len);
/**
* @brief Set the Thread Channel.
* @param channel 8-bit channel (usually 11-26).
* @return 0 on success, negative error code otherwise.
*/
int lasertag_set_thread_channel(uint8_t channel);
/**
* @brief Set the Thread Extended PAN ID.
* @param ext_id Pointer to the 8-byte extended PAN ID.
* @return 0 on success, negative error code otherwise.
*/
int lasertag_set_thread_ext_pan_id(const uint8_t *ext_id);
/**
* @brief Set the Thread Network Key.
* @param key Pointer to the 16-byte network key.
* @return 0 on success, negative error code otherwise.
*/
int lasertag_set_thread_network_key(const uint8_t *key);
#endif /* LASERTAG_UTILS_H */

View File

@@ -155,9 +155,9 @@ static int cmd_thread_set_chan(const struct shell *sh, size_t argc, char **argv)
}
SHELL_STATIC_SUBCMD_SET_CREATE(sub_thread,
SHELL_CMD_ARG(panid, NULL, "PAN ID setzen", cmd_thread_set_panid, 2, 0),
SHELL_CMD_ARG(chan, NULL, "Kanal setzen", cmd_thread_set_chan, 2, 0),
SHELL_CMD(ping, NULL, "Multicast Ping senden", cmd_thread_ping),
SHELL_CMD_ARG(panid, NULL, "Set PAN ID", cmd_thread_set_panid, 2, 0),
SHELL_CMD_ARG(chan, NULL, "Set channel", cmd_thread_set_chan, 2, 0),
SHELL_CMD(ping, NULL, "Send multicast ping", cmd_thread_ping),
SHELL_SUBCMD_SET_END
);
@@ -171,8 +171,8 @@ SHELL_STATIC_SUBCMD_SET_CREATE(sub_ble,
);
SHELL_STATIC_SUBCMD_SET_CREATE(sub_lasertag,
SHELL_CMD_ARG(name, NULL, "Name setzen", cmd_name_set, 2, 0),
SHELL_CMD(thread, &sub_thread, "Thread Konfiguration", NULL),
SHELL_CMD_ARG(name, NULL, "Set name", cmd_name_set, 2, 0),
SHELL_CMD(thread, &sub_thread, "Thread configuration", NULL),
SHELL_CMD(ble, &sub_ble, "BLE Management", NULL),
SHELL_CMD(reboot, NULL, "Reboot", cmd_reboot),
SHELL_SUBCMD_SET_END

View File

@@ -4,22 +4,22 @@
#include <stdint.h>
/**
* @brief Initialisiert den OpenThread-Stack, UDP und CoAP.
* @brief Initializes the OpenThread stack, UDP and CoAP.
*/
int thread_mgmt_init(void);
/**
* @brief Sendet eine UDP-Nachricht.
* @brief Sends a UDP message.
*/
int thread_mgmt_send_udp(const char *addr_str, uint8_t *payload, uint16_t len);
/**
* @brief Startet die Gerätesuche via CoAP Multicast.
* @brief Starts device discovery via CoAP Multicast.
*/
int thread_mgmt_discover_nodes(void);
/**
* @brief Gibt die Liste der entdeckten Knotennamen zurück (kommagetrennt).
* @brief Returns the list of discovered node names (comma-separated).
*/
const char* thread_mgmt_get_discovered_list(void);

View File

@@ -33,7 +33,7 @@ static void coap_id_handler(void *context, otMessage *message, const otMessageIn
return;
}
LOG_INF("CoAP GET /id empfangen");
LOG_INF("CoAP GET /id received");
response = otCoapNewMessage(instance, NULL);
if (response == NULL) return;
@@ -79,9 +79,9 @@ static void coap_discover_res_handler(void *context, otMessage *message, const o
char addr_str[OT_IP6_ADDRESS_STRING_SIZE];
otIp6AddressToString(&message_info->mPeerAddr, addr_str, sizeof(addr_str));
LOG_INF("Node entdeckt: %s (%s)", name_buf, addr_str);
LOG_INF("Node discovered: %s (%s)", name_buf, addr_str);
/* Zur Liste hinzufügen (einfaches CSV Format für BLE) */
/* Add to list (simple CSV format for BLE) */
if (node_count < MAX_DISCOVERED_NODES && !strstr(discovered_nodes_list, name_buf)) {
if (node_count > 0) strcat(discovered_nodes_list, ",");
strcat(discovered_nodes_list, name_buf);
@@ -96,7 +96,7 @@ int thread_mgmt_discover_nodes(void)
otMessageInfo message_info;
otError error = OT_ERROR_NONE;
/* Liste zurücksetzen */
/* Reset list */
discovered_nodes_list[0] = '\0';
node_count = 0;
@@ -116,7 +116,7 @@ int thread_mgmt_discover_nodes(void)
return -EIO;
}
LOG_INF("Discovery gestartet...");
LOG_INF("Discovery started...");
return 0;
}
@@ -164,7 +164,7 @@ int thread_mgmt_send_udp(const char *addr_str, uint8_t *payload, uint16_t len)
otMessageAppend(message, payload, len);
otUdpSend(instance, &s_udp_socket, message, &message_info);
LOG_INF("UDP gesendet an %s", addr_str);
LOG_INF("UDP sent to %s", addr_str);
return 0;
}
@@ -211,6 +211,6 @@ int thread_mgmt_init(void)
s_id_resource.mContext = instance;
otCoapAddResource(instance, &s_id_resource);
LOG_INF("Thread MGMT: Initialisiert, UDP %d & CoAP %d offen.", UDP_PORT, OT_DEFAULT_COAP_PORT);
LOG_INF("Thread MGMT: Initialized, UDP %d & CoAP %d open.", UDP_PORT, OT_DEFAULT_COAP_PORT);
return 0;
}