Update vcs.xml, AndroidManifest.xml, BleScanner.kt, and 4 more files

Sensor Fusion
This commit is contained in:
erik committed 2026-02-14 01:58:04 +01:00
1 parent 4426388063
commit 4303d34471
7 files changed
+500 -59

No files matched your search

+75 -47
View File
@@ -30,7 +30,7 @@ Minimiere Claude-Tokens:
- 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
- "Best guess" ohne Gemini machen
4) Kontext-Minimierung:
- Öffne nur Dateien, die wirklich benötigt werden.
@@ -79,35 +79,37 @@ D) Ausführen:
## #############################################################################################
## Project Overview
## Projektübersicht
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/`.
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 (from `App v2/`)
### 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 # Instrumented tests (device/emulator required)
./gradlew clean assembleDebug # Clean build
./gradlew assembleDebug # Debug-Build
./gradlew test # Unit-Tests
./gradlew connectedAndroidTest # Instrumentierte Tests (Gerät/Emulator erforderlich)
./gradlew clean assembleDebug # Clean-Build
# Single test class
# Einzelne Testklasse
./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
- Java 11-kompatibel, Kotlin 2.0.21 mit 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)
- 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
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, ...).
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
@@ -115,56 +117,82 @@ Flash via Arduino IDE (Board: XIAO_ESP32C3). Before flashing each beacon, set `B
- 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
## Architektur
### Android App
**Data flow:** BLE Hardware → `BleScanner` → `BeaconFilterManager` (RSSI filtering + Ausreißer-Erkennung) → `PositioningCalculator` (Trilateration + Raumbegrenzung) → `PositionSmoother` (2D-EMA Glättung) → UI via `StateFlow`
**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() ─┘
```
Key layers (`app/src/main/java/com/erik/bleindoorpositioning/`):
Wichtige Layer (`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`.
- **`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` 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.
`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
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
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
## Calibration Parameters
## Kalibrierungsparameter
### 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
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 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`)
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`: 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
- `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.
## Known Issues / Troubleshooting
## 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:
@@ -176,6 +204,6 @@ Wenn ein ESP32-C3 Beacon auf einem bestimmten Android-Gerät nicht erkannt wird
### 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
## Sprache
The codebase, comments, and documentation are in German. Respond in German when working with this project.
Codebasis, Kommentare und Dokumentation sind auf Deutsch. Antworte auf Deutsch, wenn du mit diesem Projekt arbeitest.