# 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 ""` 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 `````` - 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.