Konzepte angepasst und erweitert
All checks were successful
Deploy Docs / build-and-deploy (push) Successful in 13s
All checks were successful
Deploy Docs / build-and-deploy (push) Successful in 13s
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -4,3 +4,5 @@
|
||||
**/.vscode
|
||||
**/__pycache__/
|
||||
*.py[cod]
|
||||
doc/site
|
||||
|
||||
|
||||
@@ -1,3 +1,15 @@
|
||||
# Eriks Lasertag
|
||||
|
||||
Das hier beschreibt das ganze Lasertag-Gedöns.
|
||||
Kurzer Überblick über das DIY-Lasertag-System auf Basis von nRF52840, Thread und BLE.
|
||||
|
||||
**Was dich hier erwartet**
|
||||
|
||||
- Architektur & Spielablauf: siehe Konzept Software.
|
||||
- Hardware-Ideen: Waffe, Weste, Leader, Stromversorgung.
|
||||
- Roadmap & To-Dos: grobe Phasen und Module.
|
||||
|
||||
**Schnelleinstieg**
|
||||
|
||||
- Konzept Software: Rolle Leader/Weste/Waffe, Game Loop, CoAP-API.
|
||||
- Konzept Hardware: Aufbau der Einheiten, LED-Treiber, Akku-Setup.
|
||||
- Planung: Zephyr-Workspace-Struktur und Roadmap.
|
||||
|
||||
172
doc/docs/konzept/hardware.md
Normal file
172
doc/docs/konzept/hardware.md
Normal file
@@ -0,0 +1,172 @@
|
||||
# Hardware-Konzept
|
||||
|
||||
Dieses Dokument beschreibt die physischen Komponenten des Lasertag-Systems. Alle Knoten basieren auf dem Nordic nRF52840 SoC.
|
||||
|
||||
**Ziele:** robuste Outdoor-Tauglichkeit, klare Rollen-Trennung (Waffe/Weste/Leader), einfache Wartung (Steckverbinder, modulare Boards) und reproduzierbare Reichweite über saubere Stromversorgung.
|
||||
|
||||
## 1. Geräteübersicht
|
||||
|
||||
### 1.1 Waffe (Weapon Unit)
|
||||
|
||||
Die Waffe ist das primäre Interaktionsgerät. Sie muss robust und reaktionsschnell sein.
|
||||
|
||||
* **Controller:** nRF52840 (Dongle oder Modul).
|
||||
* **IR-Sender:** High-Power IR-LED (940nm oder 850nm) mit Optik/Linse und Treiberstufe (Reichweite > 50m).
|
||||
* **Feedback:** Muzzle Flash (helle LED), Vibrationsmotor, Audio (Schussgeräusche, "Leer"-Klicken).
|
||||
* **Eingabe:** Abzug (Trigger), Nachladen (Taster), optionaler Schalter für Feuermodus.
|
||||
* **Stromversorgung:** 2S LiPo (7.4V) mit Step-Down auf 3.3V.
|
||||
|
||||
### 1.2 Weste (Player Hub)
|
||||
|
||||
Die Weste ist die zentrale Recheneinheit des Spielers und trägt die Sensorik.
|
||||
|
||||
* **Controller:** nRF52840 DK oder Custom Board.
|
||||
* **Sensorik (IR-Empfänger):** Verteilte Sensoren für 360° Abdeckung (Kopf, Brust/Rücken, Schultern).
|
||||
* **Beleuchtung:** Adressierbare RGB-LEDs (WS2812B) an den Sensor-Positionen für Teamfarbe/Treffer.
|
||||
* **Audio:** Leistungsstarker Lautsprecher für Sprachausgabe.
|
||||
* **Verbindung:** Zentrale Box am Rücken mit Steckverbindern zu den Sensorgruppen.
|
||||
|
||||
### 1.3 Leader Box (Game Controller)
|
||||
|
||||
Die Leader Box dient zur Spielsteuerung und als Infrastruktur-Knoten.
|
||||
|
||||
* **Controller:** nRF52840.
|
||||
* **Modi (Hardware-Schalter):** 2 DIP-Schalter zur Wahl von Leader / Repeater / Base.
|
||||
* **Ausstattung:** IR-Empfänger, RGB-LEDs, Bluetooth-Gateway zur Smartphone-App.
|
||||
* **Stromversorgung:** Großer Akku für lange Laufzeit.
|
||||
|
||||
## 2. Energie & Verkabelung
|
||||
|
||||
* **Akkus:** 2S LiPo (7.4V) als Standard; 1S nur für Tests/Low-Power-Aufbau.
|
||||
* **Spannungswandler:** Abwärtswandler auf 3.3V für Logik, separater Treiberpfad für IR-LEDs (hoher Pulsstrom, niedriger Duty-Cycle).
|
||||
* **Verkabelung Weste:** Sternförmige Abgänge zu Sensorgürteln (Kopf, Brust/Rücken, Schultern) mit verriegelnden Steckern; Datensignal (LED) + Versorgung gebündelt.
|
||||
* **Absicherung:** Polyfuse pro Ast empfohlen, Verpolschutz an jedem Modulanschluss.
|
||||
|
||||
## 3. Stückliste (Übersicht)
|
||||
|
||||
Diese Tabelle gibt einen Überblick über die groben Komponenten pro Einheit. Detaillierte Part Numbers und Bezugsquellen folgen in separaten Docs.
|
||||
|
||||
| Komponente | Waffe | Weste | Leader | Menge | Anmerkung |
|
||||
| :--- | :--- | :--- | :--- | :--- | :--- |
|
||||
| nRF52840 (SoC/Modul) | ✓ | ✓ | ✓ | 1/Gerät | Zephyr SDK Support |
|
||||
| IR-LED (High-Power) | ✓ | | | | 940nm, > 50m Reichweite |
|
||||
| IR-Empfänger (38kHz) | | ✓ | ✓ | 5–10 | Verteilt auf Kopf/Torso/Schulter |
|
||||
| RGB-LED (WS2812B) | | ✓ | ✓ | 1–3 | Teamfarbe + Status |
|
||||
| Vibrationsmotor | ✓ | | | | Taktiles Feedback Schuss |
|
||||
| Lautsprecher | ✓ | ✓ | | | Schussgeräusche + Sprachausgabe |
|
||||
| 2S LiPo Akku | ✓ | ✓ | ✓ | 1 | 7.4V, ggf. unterschiedliche Kapazität |
|
||||
| Lade-IC (IP2326) | ✓ | ✓ | ✓ | 1 | 2S Balancing |
|
||||
| Zellenschutz (FS8205A) | ✓ | ✓ | ✓ | 1 | Verpolschutz + OV/UV |
|
||||
| Spannungsteiler-ADC | ✓ | ✓ | ✓ | 1 | Fuel Gauge (R1=100k, R2=47k) |
|
||||
| Taster (Trigger/Reload) | ✓ | | | | Auch optional Dip-Switch für Leader |
|
||||
| USB-C / Pogo-Pad | ✓ | ✓ | ✓ | 1 | Laden + Debug-Konsole |
|
||||
| Steckverbinder (JST-XH) | ✓ | ✓ | | | Modular aufgebaut |
|
||||
|
||||
## 4. Schaltungskomponenten
|
||||
|
||||
### 4.1 LED-Treiber
|
||||
|
||||
#### Grundkonzept
|
||||
|
||||
Der LED-Treiber realisiert eine präzise Konstantstromquelle als Hybridschaltung aus PNP- und NPN-Transistoren. Diese Architektur ermöglicht eine stabile und effiziente Ansteuerung von Infrarot-Leuchtdioden mit definierten Stromwerten.
|
||||
|
||||

|
||||
|
||||
#### Stromeinstellung
|
||||
|
||||
Der Zielstrom wird über den Messwiderstand $R_{set}$ eingestellt. Die Berechnung folgt der Formel:
|
||||
|
||||
$$R_{set} = \frac{0,65V}{I_{LED}}$$
|
||||
|
||||
Die folgenden Tabelle zeigt typische Stromwerte, die erforderlichen Widerstände und die entsprechenden Anwendungsfälle:
|
||||
|
||||
| Stromstärke ($I_{LED}$) | Widerstand ($R_{set}$) | Ausgangsleistung ($P_{min}$) | Einsatzbereich |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| 0,5 A | 1,30 $\Omega$ | 0,5 W | Standard / Nahkampf |
|
||||
| 1,0 A | 0,65 $\Omega$ | 1,0 W | Hohe Reichweite (SFH 4550) |
|
||||
| 2,0 A | 0,33 $\Omega$ | 2,0 W | Extrem hohe Leistung (Pulsbetrieb) |
|
||||
| 3,0 A | 0,22 $\Omega$ | 3,0 W | Scharfschützen-Modus (Oslon Black) |
|
||||
|
||||
#### Thermische Betrachtung
|
||||
|
||||
Im Lasertag-Betrieb werden die Infrarot-Signale hochfrequent moduliert (beispielsweise mit 38 kHz). Dies führt zu einem signifikant geringeren mittleren Wärmeeintrag in den Messwiderstand als eine kontinuierliche Strombelastung suggeriert:
|
||||
|
||||
$$P_{avg} = (R_{set} \cdot I_{LED}^2) \cdot \text{Duty Cycle}$$
|
||||
|
||||
!!! info "Bedeutung der Widerstandsspezifikation"
|
||||
Obwohl die Duty-Cycle-Modulation die durchschnittliche Verlustleistung reduziert, müssen Widerstände für $R_{set}$ für die auftretenden Stromspitzen ausgelegt sein. Wir empfehlen impulsfeste Typen (Metallschicht- oder Drahtwiderständen), um die Stromspitzen bis zu 3 A ohne Materialermüdung zu verkraften.
|
||||
|
||||
#### Spannungsversorgung und Headroom-Anforderungen
|
||||
|
||||
Die Konstantstromquelle benötigt eine Mindestverspannung zwischen Versorgung und Ausgang, um präzise die Sollstromstärke zu halten. Diese sogenannte Headroom-Spannung errechnet sich aus:
|
||||
|
||||
$$V_{CC} > V_{f(\text{LED})} + 0,65V + 1,0V_{\text{Headroom}}$$
|
||||
|
||||
**Kritischer Aspekt bei Lithium-Ionen-Akkus:** Die Akkuspannung sinkt während der Entladung kontinuierlich. Unterschreitet $V_{CC}$ den erforderlichen Schwellwert, bricht die Regelung zusammen und der LED-Strom kann die Sollvorgabe nicht mehr erreichen. Dies führt zu einer drastischen Reduktion der Reichweite des Senders.
|
||||
|
||||
Die Minimalspannung für stabilen Betrieb wird bestimmt durch:
|
||||
|
||||
$$V_{CC,\text{min}} = V_{f(\text{LED})} + V_{R_{set}} + V_{\text{Headroom}}$$
|
||||
|
||||
Die folgende Tabelle zeigt die erforderlichen Minimalspannungen für verschiedene Stromvorgaben und die Eignung unterschiedlicher Akkusysteme:
|
||||
|
||||
| Stromstärke ($I_{LED}$) | Typ. LED-Spannung ($V_{f}$) | Erforderliche Spannung ($V_{CC,\text{min}}$) | Akku-Empfehlung |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| 0,5 A | ~2,0 V | 3,65 V | 1S (nur bei voller Ladung) |
|
||||
| 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 |
|
||||
|
||||
!!! warning "1S-System: Einschränkungen unter Last"
|
||||
Ein einzelner 1S Li-Po Akku sinkt unter hohen Stromlasten schnell auf 3,4 V bis 3,6 V ab. Für konstante Reichweite bei Strömen ab 1 A ist ein 2S-System daher technisch überlegen.
|
||||
|
||||
#### 2S-Akkusystem: Komplexität und Lösungsansätze
|
||||
|
||||
Ein 2S-Akkusystem bietet zwar Spannungsstabilität, erfordert jedoch anspruchsvollere Schutz- und Überwachungsfunktionen:
|
||||
|
||||
- **Laden:** Moderne 2S-Ladechips mit integriertem Balancing (beispielsweise der IP2326) ermöglichen vereinfachte Ladevorgänge.
|
||||
- **Zellenschutz:** Ein Zellenschutz-IC wie der HY2120-CB in Kombination mit einem Dual-Channel MOSFET (beispielsweise FS8205A) verhindert Über- und Unterspannungszustände.
|
||||
- **Fuel Gauge:** Spezielle Fuel-Gauge-ICs für 2S-Systeme sind selten oder komplex. Als praktische Alternative wird eine Spannungsteiler-ADC-Messung zur Ladezustandsabschätzung eingesetzt. Der Spannungsteiler muss durch ein Schaltgattersystem steuerbar sein.
|
||||
|
||||
### 4.2 Audio-Driver
|
||||
Um Audio (z. B. Schussgeräusche, Sprachansagen) von einem externen Flash abzuspielen, ohne die CPU zu belasten, müssen wir auf Hardware-DMA-Transfer setzen.
|
||||
|
||||
Die beste Lösung für ein kompaktes, batteriebetriebenes System wie eine Lasertag-Waffe ist ein I2S Class-D Verstärker. Dieser kombiniert DAC und Verstärker in einem Chip und wird digital angesteuert.
|
||||
|
||||
Der "Industriestandard" für Mikrocontroller ist da der MAX98357A:
|
||||
|
||||
* **Schnittstelle**: I2S (Digital)
|
||||
* **Leistung**: 3.2W an 4Ω (sollte mehr als genug laut sein)
|
||||
* **Vortiele**:
|
||||
* Kein externer DAC nötig
|
||||
* Direkter Anschluss an den Lautsprecher
|
||||
* Extrem Energieeffizient (schont die Akkus)
|
||||
* Filterlose Class-D Architektur (wenige Bauteile)
|
||||
* **CPU-Last**: Minimal, da der nRF52840 die Daten per EasyDMA schickt (zur Anpassung der Lautstärke wird etwas Rechenleistung benötigt)
|
||||
|
||||
### 4.3 Akku-Überwachung (Fuel Gauge)
|
||||
|
||||
#### Spannungsmessung bei 2S-Akkus
|
||||
|
||||
Für 2S-Akkusysteme mit Spannungen bis 8,4 V wird die Akkuspannung über einen Spannungsteiler auf den ADC-Eingangspegel reduziert. Dieser Messwert dient zur Ladezustandsabschätzung und Fehlerdiagnose.
|
||||
|
||||
#### Schaltungskomponenten
|
||||
|
||||
| Komponente | Wert | Funktion |
|
||||
| :--- | :--- | :--- |
|
||||
| $R_1$ | 100 k$\Omega$ | Spannungsteiler – oberer Zweig |
|
||||
| $R_2$ | 47 k$\Omega$ | Spannungsteiler – unterer Zweig |
|
||||
| $C_1$ | 100 nF | Glättungskondensator am ADC-Eingang |
|
||||
|
||||
#### Softwarelogik
|
||||
|
||||
Die Ladezustandsbestimmung erfolgt in drei Schritten:
|
||||
|
||||
1. **ADC-Konvertierung:** Der Rohwert des ADC-Eingangs wird eingelesen.
|
||||
2. **Spannungsrückrechnung:** Die Realspannung wird aus dem ADC-Wert berechnet: $V_{\text{bat}} = V_{\text{adc}} \cdot \frac{R_1 + R_2}{R_2}$
|
||||
3. **Ladezustand-Mapping:** Die Batteriespannung wird auf einen prozentualen Ladezustand abgebildet:
|
||||
* **6,0 V** → 0 % (Entladungsschutz aktiv)
|
||||
* **8,4 V** → 100 % (vollständig geladen)
|
||||
* Für höhere Genauigkeit können mehrere Messpunkte verwendet und linear interpoliert werden.
|
||||
|
||||
*Stand: 03.04.2025*
|
||||
322
doc/docs/konzept/software.md
Normal file
322
doc/docs/konzept/software.md
Normal file
@@ -0,0 +1,322 @@
|
||||
# Software-Konzept & Spielablauf
|
||||
|
||||
Dieses Dokument beschreibt die Software-Architektur, die Rollenverteilung und die Kommunikationsabläufe des Lasertag-Systems.
|
||||
|
||||
## Überblick
|
||||
|
||||
* Architektur: Leader als BLE/Thread-Brücke, Westen als Spiel-Authority für Health/Regeln, Waffen als Sensor/Aktor.
|
||||
* Funk: BLE für Provisionierung/App, Thread (CoAP/UDP) für Spielverkehr, IR für Treffer-Übertragung.
|
||||
* Prinzip: Dezentraler Trefferentscheid (auf der Weste) mit optionalem Live-Ticker zum Leader; Leader hält Spielstatus und sammelt Logs.
|
||||
|
||||
## 1. System-Rollen & Hardware-Typen
|
||||
|
||||
Das System basiert auf nRF52840-Chips, die über OpenThread (802.15.4) kommunizieren.
|
||||
|
||||
### A. Leader Box (Game Controller)
|
||||
* **Funktion:** Zentrale Spielsteuerung, Zeitgeber, Gateway zur Smartphone-App.
|
||||
* **Modi (wählbar via DIP-Schalter):**
|
||||
* `00` **Leader:** Spielleiter, BLE-Gateway, sammelt Punkte.
|
||||
* `01` **Repeater:** Router im Mesh zur Reichweitenverlängerung (z.B. am Baum).
|
||||
* `11` **Base:** Interaktives Ziel (z.B. für "Domination"-Modus).
|
||||
* **Hardware:** IR-Empfänger, RGB-LEDs, BLE aktiv.
|
||||
|
||||
### B. Weste (Player Hub)
|
||||
* **Funktion:** Zentrale Einheit des Spielers. Verwaltet Lebenspunkte, empfängt Treffer, steuert Audio.
|
||||
* **Sensoren:** Kopf (3x: Stirn, Links, Rechts), Brust/Rücken, Seiten (Schultern/Arme).
|
||||
* **Kommunikation:** Hält die Verbindung zur Waffe (Pairing) und zum Leader.
|
||||
|
||||
### C. Waffe
|
||||
* **Funktion:** Aussenden der IR-Signale, haptisches Feedback, Muzzle-Flash.
|
||||
* **Typen:** Pistole, Sniper, Shotgun (unterschiedliche IR-Leistung/Fokussierung).
|
||||
* **Logik:** Sendet "Schuss"-Events, empfängt "Sperren"-Befehle von der Weste (wenn tot).
|
||||
|
||||
---
|
||||
|
||||
## 2. Provisionierung & Setup (Lobby-Phase)
|
||||
|
||||
Bevor das Spiel startet, müssen Geräte dem Netzwerk beitreten und Spielern zugeordnet werden.
|
||||
|
||||
### Schritt 1: Netzwerk-Beitritt (Provisioning)
|
||||
* **Szenario:** Neue Hardware wird zum ersten Mal verwendet.
|
||||
* **Ablauf:**
|
||||
1. Spielleiter verbindet App via BLE mit **Leader Box**.
|
||||
2. Leader Box öffnet das Thread-Netzwerk (Commissioning).
|
||||
3. Neue Geräte (Waffe/Weste) werden in den Pairing-Modus versetzt (z.B. Tastenkombination).
|
||||
4. Geräte erhalten Netzwerk-Credentials und treten dem Mesh bei.
|
||||
|
||||
### Schritt 2: Spieler-Konfiguration (Assignment)
|
||||
* **Ziel:** Zuordnung von Hardware zu einer logischen `PlayerID` und einem `Team`.
|
||||
* **Identifikation:** Jedes nRF52-Board hat eine eindeutige **EUI-64** (MAC).
|
||||
* **Ablauf:**
|
||||
1. App scannt **QR-Code** oder **NFC-Tag** an der Weste/Waffe (Payload: EUI-64 Adresse).
|
||||
2. App sendet Konfiguration an Leader Box: `EUI-64 -> {PlayerID, Team, Name}`.
|
||||
3. Leader Box löst EUI-64 in aktuelle IPv6-Adresse auf (Neighbor Table / Discovery).
|
||||
4. Leader sendet Konfigurations-Paket (CoAP Unicast) an das Gerät:
|
||||
* **Weste:** Erhält PlayerID, TeamID, MaxHealth, evtl. Rolle.
|
||||
* **Waffe:** Erhält Damage-Wert, Nachladezeit, Magazingröße, Waffentyp.
|
||||
5. Geräte speichern ID/Team im RAM (optional NVS für Persistenz bei Neustart).
|
||||
|
||||
### Provisioning – Sequenzdiagramm
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
actor User as Spielleiter
|
||||
participant App
|
||||
participant Leader
|
||||
participant Weste
|
||||
participant Waffe
|
||||
|
||||
User->>App: Verbindung via BLE
|
||||
App->>Leader: Thread-Credentials (BLE)
|
||||
Leader->>Leader: Öffne Mesh für Provisioning
|
||||
|
||||
User->>Weste: Pairing-Modus aktiviert
|
||||
User->>Waffe: Pairing-Modus aktiviert
|
||||
|
||||
Weste->>Leader: Beitritt ins Thread-Netz
|
||||
Waffe->>Leader: Beitritt ins Thread-Netz
|
||||
|
||||
User->>App: QR-Code Weste scannen
|
||||
App->>Leader: PlayerID 1, Team=Rot, Name="Alice"
|
||||
Leader->>Weste: Konfig (CoAP PUT /game/conf)
|
||||
Weste->>Weste: Speichere PlayerID=1, Team=Rot
|
||||
|
||||
User->>App: QR-Code Waffe scannen
|
||||
App->>Leader: PlayerID 1, Waffentyp=Pistol
|
||||
Leader->>Waffe: Konfig (CoAP PUT /game/wconf)
|
||||
Waffe->>Waffe: Speichere Damage=10, ReloadTime=500ms
|
||||
|
||||
App->>Leader: "Spieler bereit?"
|
||||
Leader->>App: Ping alle Knoten → OK
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Spielablauf (Game Loop)
|
||||
|
||||
### Phase A: Vorbereitung
|
||||
* **Leader:** Sendet Multicast `GAME_STATE_LOBBY`.
|
||||
* **Geräte:** Spielen Idle-Animation ab, warten auf Start.
|
||||
* **Check:** Leader kann "Ping" an alle senden, um Anwesenheit zu prüfen.
|
||||
|
||||
### Phase B: Countdown
|
||||
* **Leader:** Sendet Multicast `GAME_START_COUNTDOWN` (Payload: 10 sek).
|
||||
* **Westen:** Zählen laut herunter: "10, 9, 8...".
|
||||
|
||||
### Phase C: Spiel läuft (Running)
|
||||
* **Status:** `GAME_STATE_RUNNING`.
|
||||
* **Aktion:** Waffen sind entsperrt. Sensoren sind scharf.
|
||||
* **Treffer-Logik (Dezentral):**
|
||||
1. Waffe A schießt (sendet IR-Code mit `ShooterID` + `Damage`).
|
||||
2. Weste B empfängt IR-Signal.
|
||||
3. Weste B berechnet Schaden (unter Berücksichtigung von Trefferzone-Multiplikator).
|
||||
4. Weste B zieht Lebenspunkte ab.
|
||||
5. **Feedback:** Weste B leuchtet/vibriert/spielt Sound ("Ugh!").
|
||||
6. **Speicherung:** Weste B speichert den Treffer im internen Flash-Log (`Timestamp, ShooterID, Zone, Damage`).
|
||||
7. *(Optional)* Weste B sendet UDP-Paket an Leader für Live-Scoreboard (Best Effort).
|
||||
|
||||
### Phase D: Spieler eliminiert
|
||||
* **Bedingung:** Lebenspunkte <= 0.
|
||||
* **Weste B:**
|
||||
* Spielt "Dead"-Sound.
|
||||
* Leuchtet dauerhaft in Teamfarbe (oder aus).
|
||||
* Sendet CoAP Unicast an **eigene Waffe**: `CMD_DISABLE`.
|
||||
* **Respawn (falls aktiv):**
|
||||
* Nach Zeitablauf (z.B. 30s) sendet Weste an Waffe: `CMD_ENABLE`.
|
||||
* Lebenspunkte werden zurückgesetzt.
|
||||
|
||||
### Phase E: Spielende & Auswertung
|
||||
* **Leader:** Sendet Multicast `GAME_STATE_FINISHED`.
|
||||
* **Ablauf:**
|
||||
1. Alle Spieler kommen zusammen.
|
||||
2. Spielleiter drückt in App "Daten abrufen".
|
||||
3. Leader Box fragt nacheinander (Unicast) alle bekannten Westen ab: `GET /game/log`.
|
||||
4. Westen übertragen ihre Treffer-Historie.
|
||||
5. App berechnet Highscores, MVP, Trefferquoten.
|
||||
|
||||
### Game Loop – Zustandsautomat (Weste)
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> Idle
|
||||
|
||||
Idle --> Lobby: GAME_STATE_LOBBY empfangen
|
||||
Lobby --> Countdown: GAME_START_COUNTDOWN empfangen
|
||||
Countdown --> Running: Countdown = 0
|
||||
|
||||
Running --> Running: IR-Hit empfangen → Damage abzug, Log
|
||||
Running --> Dead: Health <= 0
|
||||
|
||||
Dead --> Dead: Waffe CMD_DISABLE senden
|
||||
Dead --> Running: Respawn aktiviert (CMD_ENABLE)
|
||||
|
||||
Running --> Finished: GAME_STATE_FINISHED
|
||||
Dead --> Finished: GAME_STATE_FINISHED
|
||||
|
||||
Finished --> [*]
|
||||
|
||||
note right of Running
|
||||
- Health-Tracking
|
||||
- Treffer-Log
|
||||
- LED/Audio-Feedback
|
||||
end note
|
||||
|
||||
note right of Dead
|
||||
- Waffe gesperrt
|
||||
- LED dauerhaft an
|
||||
- Optional Respawn-Timer
|
||||
end note
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Spielmodi
|
||||
|
||||
Die Logik für die Modi wird primär auf den Westen implementiert (Regelwerk), gesteuert durch Flags vom Leader.
|
||||
|
||||
### Team Deathmatch
|
||||
* Klassisch Rot gegen Blau.
|
||||
* Friendly Fire konfigurierbar (an/aus).
|
||||
* Siegbedingung: Meiste Kills oder wenigste Tode nach Zeitablauf.
|
||||
|
||||
### Last Man Standing (Free-for-all)
|
||||
* Jeder gegen Jeden.
|
||||
* Keine Teams (oder jeder hat eigene Team-ID).
|
||||
* Kein Respawn.
|
||||
|
||||
### Zombie (Infected)
|
||||
* **Start:** 1 Spieler ist "Zombie" (Team Grün), Rest "Mensch" (Team Rot).
|
||||
* **Regel:**
|
||||
* Zombie hat unendlich Leben (oder sehr viel).
|
||||
* Mensch hat 1 Leben.
|
||||
* Wird Mensch getroffen -> Wechselt Team zu Zombie (Weste leuchtet grün, Waffe sendet ab jetzt Zombie-ID).
|
||||
* Wird Zombie getroffen -> "Stunned" (Waffe 5s gesperrt).
|
||||
* **Ziel:** Überleben bis Zeitablauf.
|
||||
|
||||
### Base Domination
|
||||
* **Hardware:** Leader-Boxen im Modus `11` (Base) verteilt im Gelände.
|
||||
* **Ablauf:**
|
||||
* Spieler schießt auf Base-Box.
|
||||
* Base-Box wechselt Farbe zu Teamfarbe des Schützen.
|
||||
* Base-Box zählt Zeit für das haltende Team.
|
||||
* Am Ende fragt Leader alle Base-Boxen ab: "Wie lange warst du Rot? Wie lange Blau?".
|
||||
|
||||
---
|
||||
|
||||
## 5. Technische Spezifikation (API & Datenstrukturen)
|
||||
|
||||
Die Kommunikation erfolgt über CoAP (UDP). Alle Payloads sind binär (`__packed` C-Structs, Little Endian für nRF52, Network Byte Order für Interop optional).
|
||||
|
||||
### 5.1 CoAP Endpunkte
|
||||
|
||||
| Ressource | Methode | Typ | Beschreibung | Payload |
|
||||
| :--- | :--- | :--- | :--- | :--- |
|
||||
| `game/state` | PUT | Multicast | Globaler Spielstatus (Start/Stop/Zeit) | `struct game_state_packet` |
|
||||
| `game/conf` | PUT | Unicast | Konfiguration für einen Spieler | `struct player_config_packet` |
|
||||
| `game/wconf` | PUT | Unicast | Konfiguration für eine Waffe | `struct weapon_config_packet` |
|
||||
| `game/hit` | POST | Unicast | Treffer-Meldung (Live-Ticker) | `struct hit_report_packet` |
|
||||
| `game/log` | GET | Unicast | Abruf der gespeicherten Trefferdaten | `struct flash_log_entry[]` |
|
||||
|
||||
### 5.2 Datenstrukturen
|
||||
|
||||
#### Spielstatus (Multicast)
|
||||
```c
|
||||
struct game_state_packet {
|
||||
uint8_t state; // 0=Idle, 1=Lobby, 2=Running, 3=Paused, 4=Finished
|
||||
uint8_t game_mode; // 0=TeamDeathmatch, 1=Zombie, 2=Base
|
||||
uint16_t game_id; // Rolling Counter zur Deduplizierung
|
||||
uint16_t remaining_sec; // Restzeit in Sekunden
|
||||
uint8_t flags; // Bitmaske (z.B. FriendlyFire)
|
||||
} __packed;
|
||||
```
|
||||
|
||||
#### Spieler-Konfiguration (Provisioning)
|
||||
```c
|
||||
struct player_config_packet {
|
||||
uint16_t player_id; // Logische ID (1-65535)
|
||||
uint8_t team_id; // 0=Rot, 1=Blau, 2=Grün (Zombie), ...
|
||||
uint8_t role; // 0=Soldier, 1=Medic, 2=Sniper
|
||||
uint8_t damage_out; // Basis-Schaden der Waffe
|
||||
uint8_t health_max; // Maximale Lebenspunkte
|
||||
char name[16]; // Anzeigename (null-terminated)
|
||||
} __packed;
|
||||
```
|
||||
|
||||
#### Waffen-Konfiguration
|
||||
```c
|
||||
struct weapon_config_packet {
|
||||
uint8_t base_damage; // Schaden pro Schuss
|
||||
uint16_t reload_time_ms;// Zeit für Nachladen
|
||||
uint8_t magazine_size; // Schuss pro Magazin
|
||||
} __packed;
|
||||
```
|
||||
|
||||
#### Treffer-Bericht (Live & Log)
|
||||
```c
|
||||
struct hit_report_packet {
|
||||
uint32_t timestamp; // ms seit Spielstart
|
||||
uint16_t shooter_id; // ID des Schützen (aus IR)
|
||||
uint16_t victim_id; // Eigene ID
|
||||
uint8_t damage; // Erlittener Schaden
|
||||
uint8_t hit_location; // 0=Unbekannt, 1=Kopf, 2=Brust, 3=Rücken
|
||||
} __packed;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. IR-Protokoll (Physical Layer)
|
||||
|
||||
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)
|
||||
|
||||
## 8. Weste – Zustandsautomat (detailliert)
|
||||
|
||||
### Übergangstabelle
|
||||
|
||||
| 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 |
|
||||
| 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 |
|
||||
| Running | Finished | CoAP `GAME_STATE_FINISHED` | Alle Daten sichern, Logs speichern, LED Idle | Multicast vom Leader |
|
||||
| Dead | Finished | CoAP `GAME_STATE_FINISHED` | Logs speichern (optional), LED Idle | Multicast vom Leader |
|
||||
|
||||
### Treffer-Verarbeitung im Running-State (Flowchart)
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A["IR-Signal empfangen"] --> B{"ShooterID prüfen"}
|
||||
B -->|Friendly Fire off + eigenes Team| C["Signal verwerfen"]
|
||||
B -->|Valid| D["Hit Location extrahieren"]
|
||||
D --> E["Zone-Multiplikator anwenden"]
|
||||
E --> F["Effektiven Schaden berechnen"]
|
||||
F --> G["Health -= Damage"]
|
||||
G --> H{"Health > 0?"}
|
||||
H -->|Ja| I["LED blinken + Sound"]
|
||||
I --> J["Hit ins Flash-Log schreiben"]
|
||||
J --> K["Optional: CoAP Hit-Report an Leader"]
|
||||
H -->|Nein| L["Health = 0"]
|
||||
L --> M["LED rot, Dead-Sound"]
|
||||
M --> N["CoAP CMD_DISABLE an Waffe"]
|
||||
N --> O["Zustand → Dead"]
|
||||
```
|
||||
|
||||
### Übergangsbeschreibungen
|
||||
|
||||
- **Idle → Lobby:** App oder Spielleiter triggert über BLE. Leader sendet `GAME_STATE_LOBBY` Multicast. Weste wechselt in Warte-Modus.
|
||||
- **Lobby → Countdown:** Leader sendet `GAME_START_COUNTDOWN` mit Sekunden-Payload (z.B. 10). Weste zählt laut herunter.
|
||||
- **Countdown → Running:** Timer lokal = 0 → Weste aktiviert Sensoren, Health voll, Waffe freigegeben.
|
||||
- **Running → Dead:** Health <= 0 nach Treffer-Verarbeitung → Waffe gesperrt, LED rot/aus, Respawn-Timer optional starten.
|
||||
- **Dead → Running:** Optional nur wenn Respawn aktiv. Leader schickt evtl. Kommando oder Timer läuft ab.
|
||||
- **{Running|Dead} → Finished:** Leader sendet `GAME_STATE_FINISHED` → Weste speichert finale Logs, bereit für Auswertung.
|
||||
|
||||
## 9. Offene Punkte & Annahmen
|
||||
|
||||
* Sicherheitsmodell: Keine Auth auf IR, minimale Auth/ACL auf BLE/Thread? (noch zu klären).
|
||||
* Anti-Cheat: Debounce IR, Rate-Limit pro Waffe, Rolling Codes möglich.
|
||||
* Telemetrie: Sampling-Intervall für Live-Ticker und Limit pro Sekunde definieren.
|
||||
@@ -1,118 +0,0 @@
|
||||
# Hardware-Konzept
|
||||
|
||||
## 1. Systemübersicht
|
||||
|
||||
Das Lasertag-System besteht aus einer hierarchischen Architektur, bei der ein Leader-Element mehrere Westeneinheiten mit zugeordneten Waffensystemen steuert.
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
Leader((Leader))
|
||||
|
||||
Leader --> Weste1[Weste 1]
|
||||
Leader --> Weste2[Weste 2]
|
||||
|
||||
Weste1 --> WaffeA(Waffe A)
|
||||
Weste1 --> WaffeB(Waffe B)
|
||||
|
||||
Weste2 --> WaffeC(Waffe C)
|
||||
|
||||
%% Styling (Optional)
|
||||
%% style Leader fill:#f96,stroke:#333,stroke-width:2px
|
||||
%% style Weste1 fill:#bbf,stroke:#333
|
||||
%% style Weste2 fill:#bbf,stroke:#333 -->
|
||||
```
|
||||
## 2. Schaltungskomponenten
|
||||
|
||||
### 2.1 LED-Treiber
|
||||
|
||||
#### Grundkonzept
|
||||
|
||||
Der LED-Treiber realisiert eine präzise Konstantstromquelle als Hybridschaltung aus PNP- und NPN-Transistoren. Diese Architektur ermöglicht eine stabile und effiziente Ansteuerung von Infrarot-Leuchtdioden mit definierten Stromwerten.
|
||||
|
||||

|
||||
|
||||
#### Stromeinstelling
|
||||
|
||||
Der Zielstrom wird über den Messwiderstand $R_{set}$ eingestellt. Die Berechnung folgt der Formel:
|
||||
|
||||
$$R_{set} = \frac{0,65V}{I_{LED}}$$
|
||||
|
||||
Die folgenden Tabelle zeigt typische Stromwerte, die erforderlichen Widerstände und die entsprechenden Anwendungsfälle:
|
||||
|
||||
| Stromstärke ($I_{LED}$) | Widerstand ($R_{set}$) | Ausgangsleistung ($P_{min}$) | Einsatzbereich |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| 0,5 A | 1,30 $\Omega$ | 0,5 W | Standard / Nahkampf |
|
||||
| 1,0 A | 0,65 $\Omega$ | 1,0 W | Hohe Reichweite (SFH 4550) |
|
||||
| 2,0 A | 0,33 $\Omega$ | 2,0 W | Extrem hohe Leistung (Pulsbetrieb) |
|
||||
| 3,0 A | 0,22 $\Omega$ | 3,0 W | Scharfschützen-Modus (Oslon Black) |
|
||||
|
||||
#### Thermische Betrachtung
|
||||
|
||||
Im Lasertag-Betrieb werden die Infrarot-Signale hochfrequent moduliert (beispielsweise mit 38 kHz). Dies führt zu einem signifikant geringeren mittleren Wärmeeintrag in den Messwiderstand als eine kontinuierliche Strombelastung suggeriert:
|
||||
|
||||
$$P_{avg} = (R_{set} \cdot I_{LED}^2) \cdot \text{Duty Cycle}$$
|
||||
|
||||
!!! info "Bedeutung der Widerstandsspezifikation"
|
||||
Obwohl die Duty-Cycle-Modulation die durchschnittliche Verlustleistung reduziert, müssen Widerstände für $R_{set}$ für die auftretenden Stromspitzen ausgelegt sein. Wir empfehlen impulsfeste Typen (Metallschicht- oder Drahtwiderständen), um die Stromspitzen bis zu 3 A ohne Materialermüdung zu verkraften.
|
||||
|
||||
#### Spannungsversorgung und Headroom-Anforderungen
|
||||
|
||||
Die Konstantstromquelle benötigt eine Mindestverspannung zwischen Versorgung und Ausgang, um präzise die Sollstromstärke zu halten. Diese sogenannte Headroom-Spannung errechnet sich aus:
|
||||
|
||||
$$V_{CC} > V_{f(\text{LED})} + 0,65V + 1,0V_{\text{Headroom}}$$
|
||||
|
||||
**Kritischer Aspekt bei Lithium-Ionen-Akkus:** Die Akkuspannung sinkt während der Entladung kontinuierlich. Unterschreitet $V_{CC}$ den erforderlichen Schwellwert, bricht die Regelung zusammen und der LED-Strom kann die Sollvorgabe nicht mehr erreichen. Dies führt zu einer drastischen Reduktion der Reichweite des Senders.
|
||||
|
||||
Die Minimalspannung für stabilen Betrieb wird bestimmt durch:
|
||||
|
||||
$$V_{CC,\text{min}} = V_{f(\text{LED})} + V_{R_{set}} + V_{\text{Headroom}}$$
|
||||
|
||||
Die folgende Tabelle zeigt die erforderlichen Minimalspannungen für verschiedene Stromvorgaben und die Eignung unterschiedlicher Akkusysteme:
|
||||
|
||||
| Stromstärke ($I_{LED}$) | Typ. LED-Spannung ($V_{f}$) | Erforderliche Spannung ($V_{CC,\text{min}}$) | Akku-Empfehlung |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| 0,5 A | ~2,0 V | 3,65 V | 1S (nur bei voller Ladung) |
|
||||
| 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 |
|
||||
|
||||
!!! warning "1S-System: Einschränkungen unter Last"
|
||||
Ein einzelner 1S Li-Po Akku sinkt unter hohen Stromlasten schnell auf 3,4 V bis 3,6 V ab. Für konstante Reichweite bei Strömen ab 1 A ist ein 2S-System daher technisch überlegen.
|
||||
|
||||
#### 2S-Akkusystem: Komplexität und Lösungsansätze
|
||||
|
||||
Ein 2S-Akkusystem bietet zwar Spannungsstabilität, erfordert jedoch anspruchsvollere Schutz- und Überwachungsfunktionen:
|
||||
|
||||
- **Laden:** Moderne 2S-Ladechips mit integriertem Balancing (beispielsweise der IP2326) ermöglichen vereinfachte Ladevorgänge.
|
||||
- **Zellenschutz:** Ein Zellenschutz-IC wie der HY2120-CB in Kombination mit einem Dual-Channel MOSFET (beispielsweise FS8205A) verhindert Über- und Unterspannungszustände.
|
||||
- **Fuel Gauge:** Spezielle Fuel-Gauge-ICs für 2S-Systeme sind selten oder komplex. Als praktische Alternative wird eine Spannungsteiler-ADC-Messung zur Ladezustandsabschätzung eingesetzt. Der Spannungsteiler muss durch ein Schaltgattersystem steuerbar sein.
|
||||
|
||||
### 2.2 Akku-Überwachung (Fuel Gauge)
|
||||
|
||||
#### Spannungsmessung bei 2S-Akkus
|
||||
|
||||
Für 2S-Akkusysteme mit Spannungen bis 8,4 V wird die Akkuspannung über einen Spannungsteiler auf den ADC-Eingangspegel reduziert. Dieser Messwert dient zur Ladezustandsabschätzung und Fehlerdiagnose.
|
||||
|
||||
#### Schaltungskomponenten
|
||||
|
||||
| Komponente | Wert | Funktion |
|
||||
| :--- | :--- | :--- |
|
||||
| $R_1$ | 100 k$\Omega$ | Spannungsteiler – oberer Zweig |
|
||||
| $R_2$ | 47 k$\Omega$ | Spannungsteiler – unterer Zweig |
|
||||
| $C_1$ | 100 nF | Glättungskondensator am ADC-Eingang |
|
||||
|
||||
#### Softwarelogik
|
||||
|
||||
Die Ladezustandsbestimmung erfolgt in drei Schritten:
|
||||
|
||||
1. **ADC-Konvertierung:** Der Rohwert des ADC-Eingangs wird eingelesen.
|
||||
|
||||
2. **Spannungsrückrechnung:** Die Realspannung wird aus dem ADC-Wert berechnet:
|
||||
$V_{\text{bat}} = V_{\text{adc}} \cdot \frac{R_1 + R_2}{R_2}$
|
||||
|
||||
3. **Ladezustand-Mapping:** Die Batteriespannung wird auf einen prozentualen Ladezustand abgebildet:
|
||||
|
||||
- **6,0 V** → 0 % (Entladungsschutz aktiv)
|
||||
- **8,4 V** → 100 % (vollständig geladen)
|
||||
|
||||
Für höhere Genauigkeit können mehrere Messpunkte verwendet und linear interpoliert werden.
|
||||
56
doc/hardware.md
Normal file
56
doc/hardware.md
Normal file
@@ -0,0 +1,56 @@
|
||||
# Hardware-Konzept
|
||||
|
||||
Dieses Dokument beschreibt die physischen Komponenten des Lasertag-Systems. Alle Knoten basieren auf dem Nordic nRF52840 SoC.
|
||||
|
||||
## 1. Waffe (Weapon Unit)
|
||||
|
||||
Die Waffe ist das primäre Interaktionsgerät. Sie muss robust und reaktionsschnell sein.
|
||||
|
||||
* **Controller:** nRF52840 (Dongle oder Modul).
|
||||
* **IR-Sender:**
|
||||
* High-Power IR-LED (940nm oder 850nm).
|
||||
* Optik/Linse zur Bündelung des Strahls (Reichweite > 50m).
|
||||
* Transistor-Treiberstufe für hohe Impulsströme.
|
||||
* **Feedback:**
|
||||
* **Muzzle Flash:** Helle weiße/gelbe LED an der Mündung (sichtbares Feedback).
|
||||
* **Haptik:** Vibrationsmotor (aktiv bei Schuss).
|
||||
* **Audio:** Lautsprecher/Piezo für Schussgeräusche ("Piu Piu") und "Leer"-Klicken.
|
||||
* **Eingabe:**
|
||||
* **Abzug (Trigger):** Taster.
|
||||
* **Nachladen (Reload):** Taster am Magazinschacht oder Boden.
|
||||
* **Modus-Wahl:** Optionaler Schalter für Feuermodus (Einzel/Auto).
|
||||
* **Stromversorgung:** 2S LiPo (7.4V) mit Step-Down auf 3.3V.
|
||||
|
||||
## 2. Weste (Player Hub)
|
||||
|
||||
Die Weste ist die zentrale Recheneinheit des Spielers und trägt die Sensorik.
|
||||
|
||||
* **Controller:** nRF52840 DK oder Custom Board.
|
||||
* **Sensorik (IR-Empfänger):**
|
||||
* Verteilt auf 3 Gruppen für 360° Abdeckung.
|
||||
* **Kopf:** 3 Sensoren (Stirn, Links, Rechts) an Stirnband/Helm.
|
||||
* **Torso:** Sensoren an Brust und Rücken.
|
||||
* **Schultern:** Optionale Sensoren an den Seiten.
|
||||
* *Technik:* TSOP4838 oder kompatible 38kHz Empfänger.
|
||||
* **Beleuchtung (Status):**
|
||||
* Adressierbare RGB-LEDs (WS2812B) an den Sensor-Positionen.
|
||||
* Funktion: Teamfarbe anzeigen, Treffer blinken, "Zombie"-Status (Grün).
|
||||
* **Audio:**
|
||||
* Leistungsstärkerer Lautsprecher für Sprachausgabe ("Game Over", "Respawn in 10s").
|
||||
* **Verbindung:**
|
||||
* Zentrale Box am Rücken mit Steckverbindern zu den Sensorgruppen.
|
||||
|
||||
## 3. Leader Box (Game Controller)
|
||||
|
||||
Die Leader Box dient zur Spielsteuerung und als Infrastruktur-Knoten.
|
||||
|
||||
* **Controller:** nRF52840.
|
||||
* **Modi (Hardware-Schalter):**
|
||||
* 2 DIP-Schalter zur Wahl von Leader / Repeater / Base.
|
||||
* **Ausstattung:**
|
||||
* **IR-Empfänger:** Um als Ziel (Base) zu fungieren.
|
||||
* **RGB-LEDs:** Großflächige Anzeige der Base-Farbe (wer hält die Base?).
|
||||
* **Bluetooth:** Dient als Gateway zum Smartphone des Spielleiters.
|
||||
* **Stromversorgung:** Großer Akku für lange Laufzeit (da oft stationär ohne Stromnetz).
|
||||
|
||||
*Stand: 03.04.2025*
|
||||
@@ -4,7 +4,8 @@ site_url: https://gitea.iten.pro/edi/lasertag
|
||||
nav:
|
||||
- Übersicht: index.md
|
||||
- Konzept:
|
||||
- Software: konzept_hardware.md
|
||||
- Hardware: konzept/hardware.md
|
||||
- Software: konzept/software.md
|
||||
- Planung: planung.md
|
||||
|
||||
theme:
|
||||
|
||||
194
doc/software.md
Normal file
194
doc/software.md
Normal file
@@ -0,0 +1,194 @@
|
||||
# Software-Konzept & Spielablauf
|
||||
|
||||
Dieses Dokument beschreibt die Software-Architektur, die Rollenverteilung und die Kommunikationsabläufe des Lasertag-Systems.
|
||||
|
||||
## 1. System-Rollen & Hardware-Typen
|
||||
|
||||
Das System basiert auf nRF52840-Chips, die über OpenThread (802.15.4) kommunizieren.
|
||||
|
||||
### A. Leader Box (Game Controller)
|
||||
* **Funktion:** Zentrale Spielsteuerung, Zeitgeber, Gateway zur Smartphone-App.
|
||||
* **Modi (wählbar via DIP-Schalter):**
|
||||
* `00` **Leader:** Spielleiter, BLE-Gateway, sammelt Punkte.
|
||||
* `01` **Repeater:** Router im Mesh zur Reichweitenverlängerung (z.B. am Baum).
|
||||
* `11` **Base:** Interaktives Ziel (z.B. für "Domination"-Modus).
|
||||
|
||||
### B. Weste (Player Hub)
|
||||
* **Funktion:** Zentrale Einheit des Spielers. Verwaltet Lebenspunkte, empfängt Treffer, steuert Audio.
|
||||
* **Kommunikation:** Hält die Verbindung zur Waffe (Pairing) und zum Leader.
|
||||
|
||||
### C. Waffe
|
||||
* **Funktion:** Aussenden der IR-Signale, haptisches Feedback, Muzzle-Flash.
|
||||
* **Logik:** Sendet "Schuss"-Events, empfängt "Sperren"-Befehle von der Weste (wenn tot).
|
||||
|
||||
---
|
||||
|
||||
## 2. Provisionierung & Setup (Lobby-Phase)
|
||||
|
||||
Bevor das Spiel startet, müssen Geräte dem Netzwerk beitreten und Spielern zugeordnet werden.
|
||||
|
||||
### Schritt 1: Netzwerk-Beitritt (Provisioning)
|
||||
* **Szenario:** Neue Hardware wird zum ersten Mal verwendet.
|
||||
* **Ablauf:**
|
||||
1. Spielleiter verbindet App via BLE mit **Leader Box**.
|
||||
2. Leader Box öffnet das Thread-Netzwerk (Commissioning).
|
||||
3. Neue Geräte (Waffe/Weste) werden in den Pairing-Modus versetzt (z.B. Tastenkombination).
|
||||
4. Geräte erhalten Netzwerk-Credentials und treten dem Mesh bei.
|
||||
|
||||
### Schritt 2: Spieler-Konfiguration (Assignment)
|
||||
* **Ziel:** Zuordnung von Hardware zu einer logischen `PlayerID` und einem `Team`.
|
||||
* **Identifikation:** Jedes nRF52-Board hat eine eindeutige **EUI-64** (MAC).
|
||||
* **Ablauf:**
|
||||
1. App scannt **QR-Code** oder **NFC-Tag** an der Weste/Waffe.
|
||||
2. Payload: Enthält die EUI-64 Adresse.
|
||||
3. App sendet Konfiguration an Leader Box: `EUI-64 -> {PlayerID: 5, Team: Rot, Name: "Rambo"}`.
|
||||
4. **Waffen-Setup:** Waffe meldet ihren Typ (z.B. "Sniper") an Leader.
|
||||
5. Leader sendet Konfigurations-Paket (CoAP Unicast) an das Gerät:
|
||||
* **Weste:** Erhält PlayerID, TeamID, MaxHealth.
|
||||
* **Waffe:** Erhält Damage-Wert, Nachladezeit, Magazingröße.
|
||||
|
||||
---
|
||||
|
||||
## 3. Spielablauf (Game Loop)
|
||||
|
||||
### Phase A: Vorbereitung
|
||||
* **Leader:** Sendet Multicast `GAME_STATE_LOBBY`.
|
||||
* **Geräte:** Spielen Idle-Animation ab, warten auf Start.
|
||||
* **Check:** Leader kann "Ping" an alle senden, um Anwesenheit zu prüfen.
|
||||
|
||||
### Phase B: Countdown
|
||||
* **Leader:** Sendet Multicast `GAME_START_COUNTDOWN` (Payload: 10 sek).
|
||||
* **Westen:** Zählen laut herunter: "10, 9, 8...".
|
||||
|
||||
### Phase C: Spiel läuft (Running)
|
||||
* **Status:** `GAME_STATE_RUNNING`.
|
||||
* **Aktion:** Waffen sind entsperrt. Sensoren sind scharf.
|
||||
* **Treffer-Logik (Dezentral):**
|
||||
1. Waffe A schießt (sendet IR-Code mit `ShooterID` + `Damage` + `TeamID`).
|
||||
2. Weste B empfängt IR-Signal.
|
||||
3. Weste B berechnet Schaden (unter Berücksichtigung von Trefferzone-Multiplikator).
|
||||
4. Weste B zieht Lebenspunkte ab.
|
||||
5. **Feedback:** Weste B leuchtet/vibriert/spielt Sound ("Ugh!").
|
||||
6. **Speicherung:** Weste B speichert den Treffer im internen Flash-Log (`Timestamp, ShooterID, Zone, Damage`).
|
||||
7. *(Optional)* Weste B sendet UDP-Paket an Leader für Live-Scoreboard (Best Effort).
|
||||
|
||||
### Phase D: Spieler eliminiert
|
||||
* **Bedingung:** Lebenspunkte <= 0.
|
||||
* **Weste B:**
|
||||
* Spielt "Dead"-Sound.
|
||||
* Leuchtet dauerhaft in Teamfarbe (oder aus).
|
||||
* Sendet CoAP Unicast an **eigene Waffe**: `CMD_DISABLE`.
|
||||
* **Respawn (falls aktiv):**
|
||||
* Nach Zeitablauf (z.B. 30s) sendet Weste an Waffe: `CMD_ENABLE`.
|
||||
* Lebenspunkte werden zurückgesetzt.
|
||||
|
||||
### Phase E: Spielende & Auswertung
|
||||
* **Leader:** Sendet Multicast `GAME_STATE_FINISHED`.
|
||||
* **Ablauf:**
|
||||
1. Alle Spieler kommen zusammen.
|
||||
2. Spielleiter drückt in App "Daten abrufen".
|
||||
3. Leader Box fragt nacheinander (Unicast) alle bekannten Westen ab: `GET /game/log`.
|
||||
4. Westen übertragen ihre Treffer-Historie.
|
||||
5. App berechnet Highscores, MVP, Trefferquoten.
|
||||
|
||||
---
|
||||
|
||||
## 4. Spielmodi
|
||||
|
||||
Die Logik für die Modi wird primär auf den Westen implementiert (Regelwerk), gesteuert durch Flags vom Leader.
|
||||
|
||||
### Team Deathmatch
|
||||
* Klassisch Rot gegen Blau.
|
||||
* Friendly Fire konfigurierbar (an/aus).
|
||||
* Siegbedingung: Meiste Kills oder wenigste Tode nach Zeitablauf.
|
||||
|
||||
### Last Man Standing (Free-for-all)
|
||||
* Jeder gegen Jeden.
|
||||
* Keine Teams (oder jeder hat eigene Team-ID).
|
||||
* Kein Respawn.
|
||||
|
||||
### Zombie (Infected)
|
||||
* **Start:** 1 Spieler ist "Zombie" (Team Grün), Rest "Mensch" (Team Rot).
|
||||
* **Regel:**
|
||||
* Zombie hat unendlich Leben (oder sehr viel).
|
||||
* Mensch hat 1 Leben.
|
||||
* Wird Mensch getroffen -> Wechselt Team zu Zombie (Weste leuchtet grün, Waffe sendet ab jetzt Zombie-ID).
|
||||
* Wird Zombie getroffen -> "Stunned" (Waffe 5s gesperrt).
|
||||
* **Ziel:** Überleben bis Zeitablauf.
|
||||
|
||||
### Base Domination
|
||||
* **Hardware:** Leader-Boxen im Modus `11` (Base) verteilt im Gelände.
|
||||
* **Ablauf:**
|
||||
* Spieler schießt auf Base-Box.
|
||||
* Base-Box wechselt Farbe zu Teamfarbe des Schützen.
|
||||
* Base-Box zählt Zeit für das haltende Team.
|
||||
* Am Ende fragt Leader alle Base-Boxen ab: "Wie lange warst du Rot? Wie lange Blau?".
|
||||
|
||||
---
|
||||
|
||||
## 5. Technische Spezifikation (API & Datenstrukturen)
|
||||
|
||||
Die Kommunikation erfolgt über CoAP (UDP). Alle Payloads sind binär (`__packed` C-Structs, Little Endian für nRF52, aber Network Byte Order Big Endian empfohlen für Portabilität - hier vereinfacht Little Endian da homogene Hardware).
|
||||
|
||||
### 5.1 CoAP Endpunkte
|
||||
|
||||
| Ressource | Methode | Typ | Beschreibung | Payload |
|
||||
| :--- | :--- | :--- | :--- | :--- |
|
||||
| `game/state` | PUT | Multicast | Globaler Spielstatus (Start/Stop/Zeit) | `struct game_state_packet` |
|
||||
| `game/conf` | PUT | Unicast | Konfiguration für einen Spieler | `struct player_config_packet` |
|
||||
| `game/hit` | POST | Unicast | Treffer-Meldung (Live-Ticker) | `struct hit_report_packet` |
|
||||
| `game/wconf` | PUT | Unicast | Konfiguration für eine Waffe | `struct weapon_config_packet` |
|
||||
| `game/log` | GET | Unicast | Abruf der gespeicherten Trefferdaten | `struct flash_log_entry[]` |
|
||||
|
||||
### 5.2 Datenstrukturen
|
||||
|
||||
#### Spielstatus (Multicast)
|
||||
```c
|
||||
struct game_state_packet {
|
||||
uint8_t state; // 0=Idle, 1=Lobby, 2=Running, 3=Paused, 4=Finished
|
||||
uint8_t game_mode; // 0=TeamDeathmatch, 1=Zombie, 2=Base
|
||||
uint16_t game_id; // Rolling Counter zur Deduplizierung
|
||||
uint16_t remaining_sec; // Restzeit in Sekunden
|
||||
uint8_t flags; // Bitmaske (z.B. FriendlyFire)
|
||||
} __packed;
|
||||
```
|
||||
|
||||
#### Spieler-Konfiguration (Provisioning)
|
||||
```c
|
||||
struct player_config_packet {
|
||||
uint16_t player_id; // Logische ID (1-65535)
|
||||
uint8_t team_id; // 0=Rot, 1=Blau, 2=Grün (Zombie), ...
|
||||
uint8_t health_max; // Maximale Lebenspunkte
|
||||
char name[16]; // Anzeigename (null-terminated)
|
||||
} __packed;
|
||||
```
|
||||
|
||||
#### Waffen-Konfiguration
|
||||
```c
|
||||
struct weapon_config_packet {
|
||||
uint8_t base_damage; // Schaden pro Schuss
|
||||
uint16_t reload_time_ms;// Zeit für Nachladen
|
||||
uint8_t magazine_size; // Schuss pro Magazin
|
||||
} __packed;
|
||||
```
|
||||
|
||||
#### Treffer-Bericht (Live & Log)
|
||||
```c
|
||||
struct hit_report_packet {
|
||||
uint32_t timestamp; // ms seit Spielstart
|
||||
uint16_t shooter_id; // ID des Schützen (aus IR)
|
||||
uint16_t victim_id; // Eigene ID
|
||||
uint8_t damage; // Erlittener Schaden
|
||||
uint8_t hit_location; // 0=Unbekannt, 1=Kopf, 2=Brust, 3=Rücken
|
||||
} __packed;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. IR-Protokoll (Physical Layer)
|
||||
|
||||
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)
|
||||
110
firmware/apps/weapon/main.c
Normal file
110
firmware/apps/weapon/main.c
Normal file
@@ -0,0 +1,110 @@
|
||||
#include <zephyr/kernel.h>
|
||||
#include <zephyr/logging/log.h>
|
||||
#include <dk_buttons_and_leds.h>
|
||||
#include <openthread/thread.h>
|
||||
#include <openthread/coap.h>
|
||||
|
||||
/* Unsere neue Library */
|
||||
#include "game_logic.h"
|
||||
|
||||
LOG_MODULE_REGISTER(weapon_app, LOG_LEVEL_INF);
|
||||
|
||||
/* Spiel-Kontext */
|
||||
static struct game_ctx game;
|
||||
|
||||
/* Forward Declarations */
|
||||
static void on_button_changed(uint32_t button_state, uint32_t has_changed);
|
||||
|
||||
/* --- Game Logic Callbacks --- */
|
||||
|
||||
static void on_game_state_change(enum game_state new_state)
|
||||
{
|
||||
LOG_INF("APP: Spielstatus geändert -> %d", new_state);
|
||||
|
||||
switch (new_state) {
|
||||
case GAME_STATE_RUNNING:
|
||||
dk_set_led_on(DK_LED1); // LED an wenn Spiel läuft
|
||||
break;
|
||||
case GAME_STATE_FINISHED:
|
||||
dk_set_led_off(DK_LED1);
|
||||
// Blinken oder ähnliches
|
||||
break;
|
||||
default:
|
||||
dk_set_led_off(DK_LED1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void on_hit_received(uint16_t shooter_id)
|
||||
{
|
||||
LOG_WARN("APP: AUA! Getroffen von Spieler %d. Leben: %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!
|
||||
// send_hit_report_to_leader(...);
|
||||
}
|
||||
|
||||
static void on_shot_fired(void)
|
||||
{
|
||||
LOG_INF("APP: PENG! Schuss abgefeuert.");
|
||||
// TODO: Hier IR-Protokoll senden (NEC/RC5)
|
||||
}
|
||||
|
||||
/* --- Hardware Callbacks --- */
|
||||
|
||||
static void on_button_changed(uint32_t button_state, uint32_t has_changed)
|
||||
{
|
||||
// Button 1: Schießen
|
||||
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.");
|
||||
}
|
||||
}
|
||||
|
||||
// Button 2: Treffer simulieren (Self-Hit Test)
|
||||
if ((has_changed & DK_BTN2_MSK) && (button_state & DK_BTN2_MSK)) {
|
||||
LOG_INF("Simuliere Treffer durch Spieler 99...");
|
||||
struct game_hit_packet hit_packet;
|
||||
|
||||
// Wir tun so, als hätte der IR-Sensor Spieler 99 erkannt
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* --- Main --- */
|
||||
|
||||
void main(void)
|
||||
{
|
||||
LOG_INF("Lasertag Weapon Start");
|
||||
|
||||
int err = dk_buttons_init(on_button_changed);
|
||||
if (err) {
|
||||
LOG_ERR("Buttons konnten nicht initialisiert werden (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)
|
||||
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.
|
||||
struct game_state_packet fake_start = {.state = GAME_STATE_RUNNING};
|
||||
game_logic_handle_state_update(&fake_start);
|
||||
|
||||
while (1) {
|
||||
k_sleep(K_FOREVER);
|
||||
}
|
||||
}
|
||||
@@ -1 +1,20 @@
|
||||
CONFIG_LASERTAG_UTILS=y
|
||||
# Logging
|
||||
CONFIG_LOG=y
|
||||
CONFIG_LASERTAG_WEAPON_LOG_LEVEL_INF=y
|
||||
|
||||
# Network / OpenThread
|
||||
CONFIG_NETWORKING=y
|
||||
CONFIG_NET_L2_OPENTHREAD=y
|
||||
CONFIG_OPENTHREAD_COAP=y
|
||||
|
||||
# Hardware (Buttons & LEDs)
|
||||
CONFIG_DK_LIBRARY=y
|
||||
|
||||
# Lasertag Game Logic
|
||||
CONFIG_LASERTAG_GAME_LOGIC=y
|
||||
CONFIG_LASERTAG_ROLE_PLAYER=y
|
||||
CONFIG_LASERTAG_PLAYER_ID_DEFAULT=2
|
||||
|
||||
# Optional: Shell für Debugging
|
||||
CONFIG_SHELL=y
|
||||
CONFIG_OPENTHREAD_SHELL=y
|
||||
Reference in New Issue
Block a user