# 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. ## ############################################################################################# ## Project Overview BLE Indoor Positioning system consisting of two components: 1. **Android App** (Kotlin/Compose) — estimates user position via RSSI-based trilateration. Active version in `App v2/`. 2. **ESP32 Firmware** (Arduino) — iBeacon sender on Seeed XIAO ESP32-C3. Active version in `ESP32 iBeacon Code/ESP32_iBeacon_v2/`. ## Build & Run ### Android App (from `App v2/`) ```bash ./gradlew assembleDebug # Debug build ./gradlew test # Unit tests ./gradlew connectedAndroidTest # Instrumented tests (device/emulator required) ./gradlew clean assembleDebug # Clean build # Single test class ./gradlew test --tests "com.erik.bleindoorpositioning.ExampleUnitTest" ``` - Compile/Target SDK: 36, Min SDK: 26 - Java 11 compatibility, Kotlin 2.0.21 with Compose compiler plugin - AGP 9.0.0, Compose BOM 2024.09.00 - Dependencies managed via `gradle/libs.versions.toml` - Additional: kotlinx-serialization (JSON config persistence), material-icons-extended (Sonne/Mond Icons) ### ESP32 Firmware Flash via Arduino IDE (Board: XIAO_ESP32C3). Before flashing each beacon, set `BEACON_MINOR` in `ESP32_iBeacon_v2.ino` to a unique ID (1, 2, 3, ...). **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) ## Architecture ### Android App **Data flow:** BLE Hardware → `BleScanner` → `BeaconFilterManager` (RSSI filtering + Ausreißer-Erkennung) → `PositioningCalculator` (Trilateration + Raumbegrenzung) → `PositionSmoother` (2D-EMA Glättung) → UI via `StateFlow` Key layers (`app/src/main/java/com/erik/bleindoorpositioning/`): - **`ble/`**: `BleScanner` parses iBeacon packets (Apple 0x4C00 manufacturer ID), validates UUID, extracts Minor/Major/RSSI. Filtert veraltete Beacons (>3s) vor Positionsberechnung aus. Auto-Restart alle 25 Min (Android BLE Scan-Limit). Exponential Backoff bei Scan-Fehlern. `reloadConfig()` lädt Config dynamisch neu. `BeaconData` holds default calibration constants (RSSI_AT_ONE_METER, PATH_LOSS_EXPONENT, BEACON_UUID). - **`positioning/`**: `RssiFilter` interface with `KalmanFilter`/`ExponentialMovingAverageFilter`/`MovingAverageFilter` implementations. `BeaconFilterManager` manages one filter per beacon (keyed by Minor ID) with outlier rejection (Schwellwert: 20 dBm). `PositioningCalculator` runs a 3-stage pipeline: IDW weighted centroid (exponent=4.0) → Gauss-Newton trilateration (weight=1/d³) → adaptive blending based on minimum distance (0.5m=100% IDW, 1.5m=100% Gauss-Newton), mit Raumbegrenzung (Clamping auf dynamische Stockwerk-Grenzen aus Räumen+Beacons). Beacon-spezifische `rssiAtOneMeter` und `pathLossExponent` Werte. `PositionSmoother` applies 2D EMA (alpha=0.15) on the final position to reduce jumping. - **`map/`**: `CoordinateMapper` transforms meters↔pixels with dynamic bounding box (supports negative coordinates). `FloorplanView` draws floor with multiple rooms, beacons, distance circles, and position on a Compose Canvas. Interactive: Pinch-to-Zoom (0.5x–5x), Pan, Double-Tap reset. Zoom-kompensierte Schrift/Borders. Dark Mode kompatible Farben via MaterialTheme. - **`ui/`**: Tab-Navigation mit `MainScreen` (Positioning + Config Tabs). Dark/Light Mode Toggle (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 (Add/Edit/Delete), 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 Config in SharedPreferences (JSON/kotlinx.serialization). Legacy-Migration für alte Configs. - **`model/`**: `BeaconConfig` (position, rssiAtOneMeter, pathLossExponent), `RoomConfig` (name, position, size), `ScannedBeacon` (live scan data with `lastSeen` timestamp, uuid), `Position`/`PositionResult`. `MainActivity` creates `BleScanner` + `ConfigRepository` and passes them to `MainScreen`. Lifecycle-Handling: onPause stoppt Scan, onResume startet wieder. No DI framework — direct instantiation. All state is reactive via Kotlin `StateFlow`, observed in Compose with `collectAsState()`. Material 3 Theme mit Schwarz/Lila Farbschema. ### ESP32 Firmware Pure iBeacon advertiser (no GATT service, no connections). Key configuration: - Advertising interval: 100ms (fixed) - TX Power: +9 dBm (maximum for ESP32-C3), set via `esp_ble_tx_power_set()` for both ADV and DEFAULT - Signal Power: -65 dBm @ 1m (must match `RSSI_AT_ONE_METER` in Android app) - Advertising Flags: 0x06 (LE General Discoverable + BR/EDR Not Supported) - BLE Sleep enabled between advertising events for battery operation - `BEACON_MINOR` identifies each physical beacon — must match beacon Minor ID in the app's Config tab - Optional MAC-Override (`USE_CUSTOM_MAC`) for Android BLE-Blacklisting workaround ## Calibration Parameters ### Android App Beacon-Kalibrierung ist jetzt **pro Beacon konfigurierbar** im Config-Tab der App: - `rssiAtOneMeter`: Measured RSSI at 1m distance (default: -65.0 dBm) - `pathLossExponent`: Environment-dependent (default: 5.5; free-space=2.0, indoor=2.5-5.5) - Position (x, y) in meters — supports negative coordinates Globale Defaults in `BeaconData.kt`: - `RSSI_AT_ONE_METER`: Default -65.0 dBm - `PATH_LOSS_EXPONENT`: Default 5.5 - `BEACON_UUID`: Must match UUID on ESP32 beacons (`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`: Must match `RSSI_AT_ONE_METER` in the Android app (currently 0xBF = -65 dBm) - `BEACON_MINOR`: Unique per beacon, must match beacon Minor ID configured in the app's Config tab ## Known Issues / Troubleshooting ### 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()`. ## Language The codebase, comments, and documentation are in German. Respond in German when working with this project.