Files
2026-02-25 19:29:10 +01:00

210 lines
13 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## #############################################################################################
# Agent Policy: Gemini-as-Generator / Claude-as-Executor (Token-Saver)
## Rolle
Du bist ein Executor-Agent. Du sollst so wenig wie möglich selbst schreiben/erfinden.
Alle inhaltliche Generierung (Code, Text, Refactoring-Vorschläge, Architekturentscheidungen)
muss zuerst an Gemini delegiert werden.
## Primäres Ziel
Minimiere Claude-Tokens:
- keine langen Erklärungen
- keine großen Codeblöcke aus Claude heraus
- stattdessen: Gemini via CLI fragen -> Ergebnis als Patch anwenden -> kurz verifizieren
## Hard Rules (MUSS)
1) Bevor du Code erzeugst oder eine Lösung ausformulierst:
- rufe Gemini auf: `gemini -p "<PROMPT>"`
2) Fordere von Gemini standardmäßig:
- Ausgabeformat: **unified diff**
- keine Erklärtexte
- nur die minimal nötigen Änderungen
3) Du darfst aus Claude heraus NICHT:
- eigenständig große Implementierungen schreiben
- ganze Dateien neu ausgeben (außer Gemini liefert es als Diff)
- lange Analysen/Prosatexte erzeugen
- "Best guess" ohne Gemini machen
4) Kontext-Minimierung:
- Öffne nur Dateien, die wirklich benötigt werden.
- Frage den Nutzer, wenn unklar ist, welche Dateien/Module betroffen sind.
- Sende an Gemini nur relevante Ausschnitte (kleine Snippets), keine Secrets.
5) Anwendung:
- Übernimm den Diff von Gemini
- wende ihn an
- führe passende Tests/Lint/Build aus
- wenn Tests fehlschlagen: sende Fehlermeldung + relevante Snippets an Gemini und fordere einen Fix-Diff an.
## Standard-Workflow (immer)
A) Klären:
- Wenn Aufgabe unklar: max. 1–3 gezielte Rückfragen stellen.
B) Sammeln:
- Öffne nur relevante Dateien/Abschnitte.
- Notiere kurz: Dateinamen + relevante Funktionen + Constraints.
C) Delegieren:
- Sende an Gemini einen Prompt nach dem Template unten.
- Erwarte unified diff.
D) Ausführen:
- Patch anwenden.
- Tests/Lint/Build laufen lassen.
- Kurz berichten: was geändert wurde + Testresultat.
## Gemini Prompt Template (verwende dieses Format)
- Ziel: kurz
- Constraints :
- minimaler Diff
- keine neuen Dependencies (falls gewünscht)
- bestehender Stil
- Relevante Dateien/Snippets:
- File: path/to/file.ext
```<kurzer relevanter Ausschnitt>```
- Gewünschte Ausgabe:
- "OUTPUT ONLY unified diff, no explanations."
## Ausgabe-Regeln (Claude)
- Antworte kurz.
- Keine langen Erklärungen.
- Wenn du Code zeigen musst: zeige nur den Diff oder die wichtigsten geänderten Stellen.
## #############################################################################################
## Projektübersicht
BLE-basiertes Indoor-Positionierungssystem, bestehend aus zwei Komponenten:
1. **Android App** (Kotlin/Compose) — schätzt die Nutzerposition mittels RSSI-basierter Trilateration. Die aktive Version befindet sich in `App v2/`. **Die Version in `App v1_2/` ist eine Legacy-Version und soll NICHT bearbeitet werden.**
2. **ESP32 Firmware** (Arduino) — iBeacon-Sender auf einem Seeed XIAO ESP32-C3. Die aktive Version befindet sich in `ESP32 iBeacon Code/ESP32_iBeacon_v2/`.
## Build & Run
### Android App
Alle Gradle-Kommandos müssen aus dem `App v2/`-Verzeichnis ausgeführt werden (`cd App v2/`).
```bash
./gradlew assembleDebug # Debug-Build
./gradlew test # Unit-Tests
./gradlew connectedAndroidTest # Instrumentierte Tests (Gerät/Emulator erforderlich)
./gradlew clean assembleDebug # Clean-Build
# Einzelne Testklasse
./gradlew test --tests "com.erik.bleindoorpositioning.ExampleUnitTest"
```
- Compile/Target SDK: 36, Min SDK: 26
- Java 11-kompatibel, Kotlin 2.0.21 mit Compose Compiler-Plugin
- AGP 9.0.0, Compose BOM 2024.09.00
- Abhängigkeiten werden über `gradle/libs.versions.toml` verwaltet.
- Zusätzlich: kotlinx-serialization (Persistenz der JSON-Konfiguration), material-icons-extended (Sonne/Mond-Icons)
### ESP32 Firmware
Flashen über die Arduino IDE (Board: XIAO_ESP32C3). Vor dem Flashen jedes Beacons muss `BEACON_MINOR` in `ESP32_iBeacon_v2.ino` auf eine eindeutige ID (1, 2, 3, ...) gesetzt werden.
**ESP32-C3 Besonderheiten:**
- Nutzt native USB-CDC → `delay(2000)` am Anfang von `setup()` nötig, damit Serial-Ausgaben nicht verloren gehen
- TX-Power muss via `esp_ble_tx_power_set(ESP_BLE_PWR_TYPE_ADV, ...)` gesetzt werden, NICHT via `BLEDevice::setPower()` (setzt nur DEFAULT, nicht ADV)
- Header: `esp_mac.h` statt `esp_system.h` (ESP-IDF v4.0+), `esp_bt_device.h` existiert nicht auf ESP32-C3
- Optional: `USE_CUSTOM_MAC` aktivieren falls ein Android-Gerät den Beacon nicht findet (MAC-Blacklisting-Workaround)
## Architektur
### Android App
**Datenfluss:**
```
BLE Hardware → BleScanner → BeaconFilterManager (RSSI-Filterung + Ausreißer-Erkennung)
→ PositionCalculator (Trilateration + Raumbegrenzung) → EKF update() ─┐
├→ EKF.getPosition() → PositionSmoother (2D-EMA) → UI via StateFlow
Step Detector → SensorFusionManager (PDR: Heading + Schrittlänge) → EKF predict() ─┘
```
Wichtige Layer (`app/src/main/java/com/erik/bleindoorpositioning/`):
- **`ble/`**: `BleScanner` parst iBeacon-Pakete (Apple 0x4C00 Manufacturer-ID), validiert die UUID und extrahiert Minor/Major/RSSI. Veraltete Beacons (>3s) werden vor der Positionsberechnung ausgefiltert. Auto-Restart alle 25 Min (Android BLE Scan-Limit). Exponentielles Backoff bei Scan-Fehlern. `reloadConfig()` lädt die Konfiguration dynamisch neu. `BeaconData` enthält die `BEACON_UUID` und Standard-Kalibrierungskonstanten.
- **`positioning/`**: `RssiFilter`-Interface mit `KalmanFilter`-, `ExponentialMovingAverageFilter`- und `MovingAverageFilter`-Implementierungen. Aktuell wird `MovingAverageFilter(windowSize=20)` verwendet. Der `BeaconFilterManager` verwaltet einen Filter pro Beacon (Schlüssel: Minor ID) mit Ausreißer-Erkennung (Schwellwert: 20 dBm). `PositionCalculator` führt eine 3-stufige Pipeline aus: IDW Weighted Centroid (Exponent=4.0) → Gauss-Newton-Trilateration (Gewicht=1/d³) → adaptives Blending basierend auf der minimalen Distanz (0.5m=100% IDW, 1.5m=100% Gauss-Newton), mit Raumbegrenzung (Clamping auf dynamische Stockwerk-Grenzen). Er verwendet Beacon-spezifische Kalibrierungswerte (siehe Sektion "Kalibrierungsparameter"). `PositionSmoother` wendet einen 2D-EMA (alpha=0.25) auf die finale Position an, um Sprünge zu reduzieren. `PositionKalmanFilter` implementiert einen Extended Kalman Filter (EKF) zur Sensorfusion: `predict()` mit PDR-Schritten, `update()` mit BLE-Positionen (Zustand=[x,y], Prozessrauschen=0.5, Messrauschen=2.0, minimale Kovarianz=0.1 um Einfrieren über Zeit zu verhindern). `SensorFusionManager` ist **optional** und stellt PDR (Pedestrian Dead Reckoning) über Android-Sensoren (`TYPE_STEP_DETECTOR`, `TYPE_ROTATION_VECTOR`, `TYPE_LINEAR_ACCELERATION`) bereit. Er verfügt über Stillstandserkennung (Varianz-Schwellwert=0.05, Fenster=10 Samples) und eine dynamische Schrittlängenschätzung (0.5–1.0m). Sensor-Delays: `SENSOR_DELAY_FASTEST` für Step Detector und Linear Acceleration, `SENSOR_DELAY_GAME` für Rotation Vector. **Graceful Degradation:** Wenn die benötigten Sensoren auf dem Gerät nicht verfügbar sind, läuft die App in einem reinen BLE-Modus ohne Schrittverfolgung. Die Verfügbarkeit der Sensoren wird über die `isAvailable`-Property geprüft.
- **`map/`**: `CoordinateMapper` transformiert Meter↔Pixel mit einer dynamischen Bounding Box (unterstützt negative Koordinaten). `FloorplanView` zeichnet den Grundriss mit mehreren Räumen, Beacons, Distanzkreisen und der Position auf einer Compose Canvas. Interaktiv: Pinch-to-Zoom (0.5x–5x), Schwenken, Doppeltipp zum Zurücksetzen. Zoom-kompensierte Schriftgrößen und Strichstärken. Dark-Mode-kompatible Farben über MaterialTheme.
- **`ui/`**: Tab-Navigation mit `MainScreen` (Positionierungs- und Konfigurations-Tabs). Dark/Light-Mode-Umschalter (Sonne/Mond-Icon, persistent). `PositioningScreen`: Karte mit Vollbild-Modus, einklappbare Beacon-Liste, Scan-Kontrolle. `ConfigScreen`: Scrollbar mit einklappbaren Sektionen (Stockwerk, Räume, Beacons, Anlernen). Auto-Save bei Änderungen. Raum-Verwaltung (Hinzufügen/Bearbeiten/Löschen), Beacon-Verwaltung mit Detail-Dialog (Position, RSSI @1m, Path Loss Exponent), Beacon-Anlern-Funktion via BLE-Scan.
- **`data/`**: `AppConfig` (floorWidth/Height, rooms, beacons). `ConfigRepository` persistiert die Konfiguration in den SharedPreferences (JSON/kotlinx.serialization). Inklusive Legacy-Migration für alte Konfigurationsformate.
- **`model/`**: `BeaconConfig` (Position, rssiAtOneMeter, pathLossExponent), `RoomConfig` (Name, Position, Größe), `ScannedBeacon` (Live-Scan-Daten mit `lastSeen`-Zeitstempel, UUID), `Position`/`PositionResult`.
`MainActivity` erzeugt `BleScanner` und `ConfigRepository` und übergibt sie an `MainScreen`. Lifecycle-Handling: `onPause` stoppt den Scan, `onResume` startet ihn wieder. Kein DI-Framework — direkte Instanziierung. Der gesamte Zustand ist reaktiv über Kotlin `StateFlow` und wird in Compose mit `collectAsState()` beobachtet. Material 3 Theme mit einem Schwarz/Lila-Farbschema.
### ESP32 Firmware
Reiner iBeacon-Advertiser (kein GATT-Service, keine Verbindungen). Wichtige Konfiguration:
- Advertising-Intervall: 100ms (fest)
- TX Power: +9 dBm (Maximum für ESP32-C3), gesetzt über `esp_ble_tx_power_set()` für ADV und DEFAULT
- Signal Power: -65 dBm @ 1m (muss mit `rssiAtOneMeter` in der Android-App übereinstimmen)
- Advertising-Flags: 0x06 (LE General Discoverable + BR/EDR Not Supported)
- BLE Sleep zwischen den Advertising-Events für Batteriebetrieb aktiviert
- `BEACON_MINOR` identifiziert jeden physischen Beacon — muss mit der Beacon Minor ID in der App-Konfiguration übereinstimmen
- Optionaler MAC-Override (`USE_CUSTOM_MAC`) als Workaround für Android BLE-Blacklisting
## Kalibrierungsparameter
### Android App
Die Beacon-Kalibrierung ist **pro Beacon konfigurierbar** im Config-Tab der App:
- `rssiAtOneMeter`: Gemessener RSSI bei 1m Abstand (Standard: -65.0 dBm)
- `pathLossExponent`: Umgebungsabhängig (Standard: 5.5; Freifeld=2.0, Innenraum=2.5-5.5)
- Position (x, y) in Metern — unterstützt negative Koordinaten
Globale Standardwerte in `BeaconData.kt`:
- `RSSI_AT_ONE_METER`: Standardwert -65.0 dBm
- `PATH_LOSS_EXPONENT`: Standardwert 5.5
- `BEACON_UUID`: Muss mit der UUID auf den ESP32-Beacons übereinstimmen (`87b99b2c-9080-4d04-8f00-a5d9a3f9a0e7`)
Stockwerk-Konfiguration (Config-Tab):
- `floorWidth` / `floorHeight`: Stockwerk-Dimensionen in Metern
- `rooms`: Liste von Räumen (Name, Position, Größe) — beliebig viele, auch an negativen Koordinaten
### ESP32 (ESP32_iBeacon_v2.ino)
- `SIGNAL_POWER_1M`: Muss mit `rssiAtOneMeter` in der Android-App übereinstimmen (aktuell 0xBF = -65 dBm)
- `BEACON_MINOR`: Eindeutig pro Beacon, muss mit der Beacon Minor ID übereinstimmen, die im Config-Tab der App konfiguriert ist.
## Tuning-Parameter (Positionierungspipeline)
Die Positionierung durchläuft mehrere Glättungsstufen. Beim Tuning beachten:
**Pipeline:** RSSI → MovingAverageFilter → PositionCalculator → EKF → PositionSmoother → UI
| Parameter | Datei | Aktuell | Effekt |
|---|---|---|---|
| `MovingAverageFilter(windowSize)` | BleScanner.kt | 20 | Mehr = stabilere RSSI, aber trägere Reaktion |
| `BeaconFilterManager(outlierThreshold)` | BleScanner.kt | 20.0 dBm | Höher = mehr RSSI-Sprünge erlaubt |
| `PositionKalmanFilter(processNoise)` | BleScanner.kt | 0.5 | Höher = EKF folgt BLE schneller, PDR weniger Gewicht |
| `positionKalmanFilter.update(measurementNoise)` | BleScanner.kt | 2.0 | Niedriger = mehr Vertrauen in BLE-Position |
| `PositionSmoother(alpha)` | BleScanner.kt | 0.25 | Höher = reaktiver, niedriger = glatter |
| `BEACON_MAX_AGE_MS` | BleScanner.kt | 3000 | Kürzer = weniger Einfluss veralteter Beacons |
| `minCovariance` | PositionKalmanFilter.kt | 0.1 | Verhindert EKF-Einfrieren über Zeit |
| `STATIONARY_VARIANCE_THRESHOLD` | SensorFusionManager.kt | 0.05 | Niedriger = weniger Schritte als Stillstand gefiltert |
| `ACCELERATION_WINDOW_SIZE` | SensorFusionManager.kt | 10 | Weniger = schnellere Stillstandserkennung |
**Bekanntes Problem:** Zu viele Glättungsstufen hintereinander können die Position extrem träge machen. Beim Debugging einzelne Stufen testweise deaktivieren (EKF bypass / Smoother bypass), um die Ursache zu isolieren. Der EKF kann über Zeit einfrieren wenn die Kovarianz zu klein wird — daher `minCovariance`-Clamp.
## Bekannte Probleme / Fehlerbehebung
### ESP32 Beacon nicht von Android erkannt
Wenn ein ESP32-C3 Beacon auf einem bestimmten Android-Gerät nicht erkannt wird (auch nicht in nRF Connect), aber auf anderen Geräten sichtbar ist:
1. Android Netzwerkeinstellungen zurücksetzen (Einstellungen → Zurücksetzen → Netzwerkeinstellungen)
2. Ab Android 13: Bluetooth System-Cache kann nur via ADB gelöscht werden (`adb shell pm clear com.android.bluetooth`)
3. `USE_CUSTOM_MAC` im ESP32-Code aktivieren um dem Beacon eine neue MAC-Identität zu geben
4. ESP32 mit "Erase All Flash Before Sketch Upload" neu flashen
### ESP32-C3 Serial Monitor leer
Der XIAO ESP32-C3 nutzt native USB-CDC. Frühe `Serial.print()`-Aufrufe gehen verloren. Fix: `delay(2000)` nach `Serial.begin()`.
## Sprache
Codebasis, Kommentare und Dokumentation sind auf Deutsch. Antworte auf Deutsch, wenn du mit diesem Projekt arbeitest.