feat: add Home Assistant integration and improve CLI/UI
Home Assistant Integration: - New homeassistant.py module with webhook support - Webhooks for scan results, new devices, and device departures - Absence detection with configurable timeout - Documentation in docs/HOME_ASSISTANT.md CLI Improvements: - Replace 'web' command with start/stop/restart/status - Background daemon mode with PID file management - Foreground mode for debugging (--foreground) Web UI Enhancements: - Improved device list styling and layout - Better floor assignment UI - Enhanced map visualization Documentation: - Add CHANGELOG.md - Add docs/API.md with full endpoint reference - Add docs/CHEATSHEET.md for quick reference - Update project documentation Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
750
docs/API.md
Normal file
750
docs/API.md
Normal file
@@ -0,0 +1,750 @@
|
||||
# RF Mapper API Reference
|
||||
|
||||
REST API documentation for RF Mapper web interface.
|
||||
|
||||
**Base URL:** `http://localhost:5000`
|
||||
|
||||
---
|
||||
|
||||
## Scanning
|
||||
|
||||
### Trigger Scan
|
||||
|
||||
Performs WiFi and/or Bluetooth scan.
|
||||
|
||||
```
|
||||
POST /api/scan
|
||||
```
|
||||
|
||||
**Request Body:**
|
||||
|
||||
| Field | Type | Default | Description |
|
||||
|-------|------|---------|-------------|
|
||||
| `location` | string | `"web_scan"` | Location label for the scan |
|
||||
| `lat` | number | config value | GPS latitude |
|
||||
| `lon` | number | config value | GPS longitude |
|
||||
| `scan_wifi` | boolean | `true` | Include WiFi networks |
|
||||
| `scan_bluetooth` | boolean | `true` | Include Bluetooth devices |
|
||||
|
||||
**Example:**
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:5000/api/scan \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"location": "kitchen", "scan_wifi": true, "scan_bluetooth": true}'
|
||||
```
|
||||
|
||||
**Response:**
|
||||
|
||||
```json
|
||||
{
|
||||
"timestamp": "2026-02-01T14:30:00.123456",
|
||||
"location": "kitchen",
|
||||
"gps": {"lat": 50.8585, "lon": 4.3978},
|
||||
"wifi_networks": [
|
||||
{
|
||||
"ssid": "HomeNetwork",
|
||||
"bssid": "AA:BB:CC:DD:EE:FF",
|
||||
"rssi": -45,
|
||||
"channel": 6,
|
||||
"frequency": 2437,
|
||||
"encryption": "WPA2",
|
||||
"manufacturer": "Cisco",
|
||||
"estimated_distance_m": 3.5,
|
||||
"signal_quality": "Excellent",
|
||||
"floor": null,
|
||||
"height_m": null
|
||||
}
|
||||
],
|
||||
"bluetooth_devices": [
|
||||
{
|
||||
"address": "11:22:33:44:55:66",
|
||||
"name": "iPhone",
|
||||
"rssi": -60,
|
||||
"device_class": "Phone",
|
||||
"device_type": "Smartphone",
|
||||
"manufacturer": "Apple",
|
||||
"estimated_distance_m": 5.2,
|
||||
"signal_quality": "Good",
|
||||
"floor": null,
|
||||
"height_m": null
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Quick Bluetooth Scan
|
||||
|
||||
Fast BLE-only scan for real-time tracking (uses bleak library).
|
||||
|
||||
```
|
||||
POST /api/scan/bt
|
||||
```
|
||||
|
||||
**Response:** Same as `/api/scan` but only `bluetooth_devices` populated.
|
||||
|
||||
---
|
||||
|
||||
### Get Latest Scan
|
||||
|
||||
Retrieve the most recent scan results.
|
||||
|
||||
```
|
||||
GET /api/latest
|
||||
```
|
||||
|
||||
**Response:** Same format as `/api/scan` response.
|
||||
|
||||
---
|
||||
|
||||
### List Scans
|
||||
|
||||
List saved scan files (most recent 50).
|
||||
|
||||
```
|
||||
GET /api/scans
|
||||
```
|
||||
|
||||
**Response:**
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"filename": "scan_20260201_143000_kitchen.json",
|
||||
"timestamp": "2026-02-01T14:30:00",
|
||||
"location": "kitchen",
|
||||
"wifi_count": 12,
|
||||
"bt_count": 5
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Get Specific Scan
|
||||
|
||||
Retrieve a specific scan by filename.
|
||||
|
||||
```
|
||||
GET /api/scans/<filename>
|
||||
```
|
||||
|
||||
**Example:**
|
||||
|
||||
```bash
|
||||
curl http://localhost:5000/api/scans/scan_20260201_143000_kitchen.json
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Position & Configuration
|
||||
|
||||
### GPS Position
|
||||
|
||||
Get or set current GPS position.
|
||||
|
||||
```
|
||||
GET /api/position
|
||||
POST /api/position
|
||||
```
|
||||
|
||||
**POST Body:**
|
||||
|
||||
```json
|
||||
{"lat": 50.8585, "lon": 4.3978}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
|
||||
```json
|
||||
{"lat": 50.8585, "lon": 4.3978}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Configuration
|
||||
|
||||
Get or update application configuration.
|
||||
|
||||
```
|
||||
GET /api/config
|
||||
POST /api/config
|
||||
```
|
||||
|
||||
**POST Body:**
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `gps.latitude` | number | GPS latitude |
|
||||
| `gps.longitude` | number | GPS longitude |
|
||||
| `scanner.path_loss_exponent` | number | Distance calculation parameter |
|
||||
| `save` | boolean | Persist changes to config file |
|
||||
|
||||
**Example:**
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:5000/api/config \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"gps": {"latitude": 50.85, "longitude": 4.39}, "save": true}'
|
||||
```
|
||||
|
||||
**Response:**
|
||||
|
||||
```json
|
||||
{
|
||||
"gps": {"latitude": 50.85, "longitude": 4.39},
|
||||
"web": {"host": "0.0.0.0", "port": 5000},
|
||||
"scanner": {
|
||||
"wifi_interface": "wlan0",
|
||||
"bt_scan_timeout": 10,
|
||||
"path_loss_exponent": 2.5
|
||||
},
|
||||
"config_file": "/home/user/git/rf-mapper/config.yaml"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Building Configuration
|
||||
|
||||
Get or update building configuration for 3D visualization.
|
||||
|
||||
```
|
||||
GET /api/building
|
||||
POST /api/building
|
||||
```
|
||||
|
||||
**POST Body:**
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `enabled` | boolean | Enable 3D building view |
|
||||
| `name` | string | Building name |
|
||||
| `floors` | integer | Number of floors |
|
||||
| `floor_height_m` | number | Height per floor (meters) |
|
||||
| `ground_floor_number` | integer | Ground floor number (0 or 1) |
|
||||
| `current_floor` | integer | Scanner's current floor |
|
||||
| `save` | boolean | Persist changes |
|
||||
|
||||
**Response:**
|
||||
|
||||
```json
|
||||
{
|
||||
"enabled": true,
|
||||
"name": "Home",
|
||||
"floors": 3,
|
||||
"floor_height_m": 3.0,
|
||||
"ground_floor_number": 0,
|
||||
"current_floor": 1
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Device Management
|
||||
|
||||
### Set Device Floor
|
||||
|
||||
Assign a floor to a device (persists in database).
|
||||
|
||||
```
|
||||
POST /api/device/<device_id>/floor
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
- `device_id`: BSSID (WiFi) or address (Bluetooth)
|
||||
|
||||
**Request Body:**
|
||||
|
||||
```json
|
||||
{"floor": 2, "height_m": 6.0}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "updated",
|
||||
"device_id": "AA:BB:CC:DD:EE:FF",
|
||||
"device_type": "wifi",
|
||||
"floor": 2,
|
||||
"height_m": 6.0
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Set Device Distance
|
||||
|
||||
Override estimated distance for a device.
|
||||
|
||||
```
|
||||
POST /api/device/<device_id>/distance
|
||||
```
|
||||
|
||||
**Request Body:**
|
||||
|
||||
```json
|
||||
{"distance": 5.5}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Get All Floor Assignments
|
||||
|
||||
Retrieve all saved floor assignments.
|
||||
|
||||
```
|
||||
GET /api/device/floors
|
||||
```
|
||||
|
||||
**Response:**
|
||||
|
||||
```json
|
||||
{
|
||||
"AA:BB:CC:DD:EE:FF": 2,
|
||||
"11:22:33:44:55:66": 1
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Bluetooth Identification
|
||||
|
||||
### Identify Single Device
|
||||
|
||||
Get detailed info about a Bluetooth device.
|
||||
|
||||
```
|
||||
GET /api/bluetooth/identify/<address>
|
||||
```
|
||||
|
||||
**Example:**
|
||||
|
||||
```bash
|
||||
curl http://localhost:5000/api/bluetooth/identify/11:22:33:44:55:66
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Batch Identify
|
||||
|
||||
Identify multiple devices (max 10).
|
||||
|
||||
```
|
||||
POST /api/bluetooth/identify
|
||||
```
|
||||
|
||||
**Request Body:**
|
||||
|
||||
```json
|
||||
{"addresses": ["11:22:33:44:55:66", "AA:BB:CC:DD:EE:FF"]}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Auto-Scan
|
||||
|
||||
### Get Status
|
||||
|
||||
```
|
||||
GET /api/autoscan
|
||||
```
|
||||
|
||||
**Response:**
|
||||
|
||||
```json
|
||||
{
|
||||
"enabled": true,
|
||||
"running": true,
|
||||
"interval_minutes": 5,
|
||||
"location_label": "auto_scan",
|
||||
"scan_wifi": true,
|
||||
"scan_bluetooth": true,
|
||||
"last_scan_time": "2026-02-01T14:30:00",
|
||||
"last_scan_result": {"timestamp": "...", "wifi_count": 12, "bt_count": 5},
|
||||
"scan_count": 42
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Start Auto-Scan
|
||||
|
||||
```
|
||||
POST /api/autoscan/start
|
||||
```
|
||||
|
||||
**Request Body:**
|
||||
|
||||
| Field | Type | Default | Description |
|
||||
|-------|------|---------|-------------|
|
||||
| `interval_minutes` | integer | 5 | Scan interval |
|
||||
| `location_label` | string | `"auto_scan"` | Location label |
|
||||
| `scan_wifi` | boolean | `true` | Include WiFi |
|
||||
| `scan_bluetooth` | boolean | `true` | Include Bluetooth |
|
||||
| `save` | boolean | `false` | Persist to config |
|
||||
|
||||
---
|
||||
|
||||
### Stop Auto-Scan
|
||||
|
||||
```
|
||||
POST /api/autoscan/stop
|
||||
```
|
||||
|
||||
**Request Body:**
|
||||
|
||||
```json
|
||||
{"save": true}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Update Settings
|
||||
|
||||
```
|
||||
POST /api/autoscan/settings
|
||||
```
|
||||
|
||||
**Request Body:**
|
||||
|
||||
```json
|
||||
{"interval_minutes": 10, "location_label": "living_room", "save": true}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Historical Data API
|
||||
|
||||
All history endpoints require database to be enabled in config.
|
||||
|
||||
### List Devices
|
||||
|
||||
Get all tracked devices with statistics.
|
||||
|
||||
```
|
||||
GET /api/history/devices
|
||||
```
|
||||
|
||||
**Query Parameters:**
|
||||
|
||||
| Param | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `type` | string | Filter by `wifi` or `bluetooth` |
|
||||
| `since` | string | ISO timestamp filter |
|
||||
| `limit` | integer | Max results (default: 100) |
|
||||
|
||||
**Response:**
|
||||
|
||||
```json
|
||||
{
|
||||
"devices": [
|
||||
{
|
||||
"device_id": "AA:BB:CC:DD:EE:FF",
|
||||
"device_type": "wifi",
|
||||
"name": "HomeNetwork",
|
||||
"ssid": "HomeNetwork",
|
||||
"manufacturer": "Cisco",
|
||||
"first_seen": "2026-01-15T10:00:00",
|
||||
"last_seen": "2026-02-01T14:30:00",
|
||||
"total_observations": 500,
|
||||
"custom_label": "Main Router",
|
||||
"is_favorite": 1,
|
||||
"assigned_floor": 1
|
||||
}
|
||||
],
|
||||
"count": 1
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Get Device Details
|
||||
|
||||
```
|
||||
GET /api/history/devices/<device_id>
|
||||
```
|
||||
|
||||
**Response:**
|
||||
|
||||
```json
|
||||
{
|
||||
"device": { ... },
|
||||
"stats": {
|
||||
"avg_rssi": -55.3,
|
||||
"min_rssi": -75,
|
||||
"max_rssi": -40,
|
||||
"avg_distance_m": 4.2,
|
||||
"min_distance_m": 1.5,
|
||||
"max_distance_m": 12.0,
|
||||
"total_observations": 500
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Get RSSI History
|
||||
|
||||
Time series RSSI data for a device.
|
||||
|
||||
```
|
||||
GET /api/history/devices/<device_id>/rssi
|
||||
```
|
||||
|
||||
**Query Parameters:**
|
||||
|
||||
| Param | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `since` | string | ISO timestamp filter |
|
||||
| `limit` | integer | Max results (default: 1000) |
|
||||
|
||||
**Response:**
|
||||
|
||||
```json
|
||||
{
|
||||
"device_id": "AA:BB:CC:DD:EE:FF",
|
||||
"observations": [
|
||||
{"timestamp": "2026-02-01T14:30:00", "rssi": -55, "distance_m": 4.2, "floor": 1}
|
||||
],
|
||||
"count": 100
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Get Activity Pattern
|
||||
|
||||
Hourly/daily activity pattern for a device.
|
||||
|
||||
```
|
||||
GET /api/history/devices/<device_id>/activity
|
||||
```
|
||||
|
||||
**Query Parameters:**
|
||||
|
||||
| Param | Type | Default | Description |
|
||||
|-------|------|---------|-------------|
|
||||
| `days` | integer | 7 | Analysis period |
|
||||
|
||||
**Response:**
|
||||
|
||||
```json
|
||||
{
|
||||
"device_id": "AA:BB:CC:DD:EE:FF",
|
||||
"period_days": 7,
|
||||
"hourly_pattern": {
|
||||
"8": {"count": 50, "avg_rssi": -55.0},
|
||||
"9": {"count": 45, "avg_rssi": -52.3}
|
||||
},
|
||||
"daily_pattern": {
|
||||
"0": 120,
|
||||
"1": 115
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Set Device Label
|
||||
|
||||
```
|
||||
POST /api/history/devices/<device_id>/label
|
||||
```
|
||||
|
||||
**Request Body:**
|
||||
|
||||
```json
|
||||
{"label": "Living Room TV"}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Toggle Favorite
|
||||
|
||||
```
|
||||
POST /api/history/devices/<device_id>/favorite
|
||||
```
|
||||
|
||||
**Request Body:**
|
||||
|
||||
```json
|
||||
{"favorite": true}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Get Movement Events
|
||||
|
||||
```
|
||||
GET /api/history/movement
|
||||
```
|
||||
|
||||
**Query Parameters:**
|
||||
|
||||
| Param | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `device_id` | string | Filter by device |
|
||||
| `since` | string | ISO timestamp filter |
|
||||
| `limit` | integer | Max results (default: 100) |
|
||||
|
||||
**Response:**
|
||||
|
||||
```json
|
||||
{
|
||||
"events": [
|
||||
{
|
||||
"id": 1,
|
||||
"device_id": "11:22:33:44:55:66",
|
||||
"timestamp": "2026-02-01T14:30:00",
|
||||
"rssi_delta": 10,
|
||||
"distance_delta_m": -2.5,
|
||||
"direction": "approaching",
|
||||
"velocity_m_s": 0.5
|
||||
}
|
||||
],
|
||||
"count": 1
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Get Alerts
|
||||
|
||||
```
|
||||
GET /api/history/alerts
|
||||
```
|
||||
|
||||
**Query Parameters:**
|
||||
|
||||
| Param | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `acknowledged` | boolean | Filter by acknowledged status |
|
||||
| `type` | string | Filter by type: `new_device`, `device_absent`, `rssi_threshold` |
|
||||
| `limit` | integer | Max results (default: 50) |
|
||||
|
||||
**Response:**
|
||||
|
||||
```json
|
||||
{
|
||||
"alerts": [
|
||||
{
|
||||
"id": 1,
|
||||
"alert_type": "new_device",
|
||||
"device_id": "11:22:33:44:55:66",
|
||||
"timestamp": "2026-02-01T14:30:00",
|
||||
"message": "New Bluetooth device detected: iPhone (Apple)",
|
||||
"acknowledged": 0
|
||||
}
|
||||
],
|
||||
"count": 1
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Acknowledge Alert
|
||||
|
||||
```
|
||||
POST /api/history/alerts/<alert_id>/acknowledge
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Get Activity Summary
|
||||
|
||||
```
|
||||
GET /api/history/activity
|
||||
```
|
||||
|
||||
**Query Parameters:**
|
||||
|
||||
| Param | Type | Default | Description |
|
||||
|-------|------|---------|-------------|
|
||||
| `hours` | integer | 24 | Period in hours |
|
||||
|
||||
**Response:**
|
||||
|
||||
```json
|
||||
{
|
||||
"period_hours": 24,
|
||||
"since": "2026-01-31T14:30:00",
|
||||
"active_wifi_devices": 12,
|
||||
"active_bt_devices": 8,
|
||||
"total_observations": 2500,
|
||||
"movement_events": 15,
|
||||
"new_devices": 2,
|
||||
"scan_count": 288
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Get Database Stats
|
||||
|
||||
```
|
||||
GET /api/history/stats
|
||||
```
|
||||
|
||||
**Response:**
|
||||
|
||||
```json
|
||||
{
|
||||
"total_devices": 45,
|
||||
"total_observations": 50000,
|
||||
"total_scans": 1200,
|
||||
"total_movement_events": 300,
|
||||
"unread_alerts": 5,
|
||||
"database_size_bytes": 5242880,
|
||||
"database_size_mb": 5.0
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Manual Cleanup
|
||||
|
||||
Trigger data cleanup (removes old records).
|
||||
|
||||
```
|
||||
POST /api/history/cleanup
|
||||
```
|
||||
|
||||
**Request Body:**
|
||||
|
||||
```json
|
||||
{"retention_days": 30}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
|
||||
```json
|
||||
{
|
||||
"retention_days": 30,
|
||||
"cutoff": "2026-01-02T14:30:00",
|
||||
"cleaned_at": "2026-02-01T14:30:00"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Error Responses
|
||||
|
||||
All endpoints return errors in this format:
|
||||
|
||||
```json
|
||||
{"error": "Error message here"}
|
||||
```
|
||||
|
||||
**Common HTTP Status Codes:**
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| 200 | Success |
|
||||
| 400 | Bad request (missing/invalid parameters) |
|
||||
| 404 | Resource not found |
|
||||
| 500 | Internal server error |
|
||||
| 503 | Database not enabled |
|
||||
277
docs/CHEATSHEET.md
Normal file
277
docs/CHEATSHEET.md
Normal file
@@ -0,0 +1,277 @@
|
||||
# RF Mapper Cheatsheet
|
||||
|
||||
Quick reference for RF Mapper commands and configuration.
|
||||
|
||||
---
|
||||
|
||||
## CLI Commands
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| `rf-mapper` | Interactive scan mode |
|
||||
| `rf-mapper scan` | Run scan with defaults |
|
||||
| `rf-mapper scan -l kitchen` | Scan with location label |
|
||||
| `rf-mapper scan --no-bt` | WiFi only |
|
||||
| `rf-mapper scan --no-wifi` | Bluetooth only |
|
||||
| `rf-mapper visualize` | ASCII radar display |
|
||||
| `rf-mapper analyze` | RF environment analysis |
|
||||
| `rf-mapper list` | List saved scans |
|
||||
| `rf-mapper start` | Start web server (background) |
|
||||
| `rf-mapper stop` | Stop web server |
|
||||
| `rf-mapper restart` | Restart web server |
|
||||
| `rf-mapper status` | Check if server is running |
|
||||
| `rf-mapper config` | Show configuration |
|
||||
|
||||
---
|
||||
|
||||
## Web Server
|
||||
|
||||
```bash
|
||||
# Lifecycle
|
||||
rf-mapper start # Start (background daemon)
|
||||
rf-mapper stop # Stop
|
||||
rf-mapper restart # Restart
|
||||
rf-mapper status # Check if running
|
||||
|
||||
# Start options
|
||||
rf-mapper start -f # Foreground mode
|
||||
rf-mapper start -H 127.0.0.1 # Bind to localhost only
|
||||
rf-mapper start -p 8080 # Custom port
|
||||
rf-mapper start --debug # Debug mode (requires -f)
|
||||
rf-mapper start --profile-requests # Per-request profiling
|
||||
rf-mapper start --log-requests # Request logging
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Configuration Commands
|
||||
|
||||
```bash
|
||||
# Show current config
|
||||
rf-mapper config
|
||||
|
||||
# Set GPS coordinates
|
||||
rf-mapper config --set-gps 50.8585 4.3978 --save
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Profiling
|
||||
|
||||
```bash
|
||||
rf-mapper --profile scan # CPU profiling
|
||||
rf-mapper --profile-memory scan # Memory profiling
|
||||
rf-mapper --profile --profile-output scan.prof scan
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Common API Calls
|
||||
|
||||
```bash
|
||||
# Trigger scan
|
||||
curl -X POST http://localhost:5000/api/scan \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"location": "office"}'
|
||||
|
||||
# Get latest scan
|
||||
curl http://localhost:5000/api/latest
|
||||
|
||||
# Quick BT scan (real-time tracking)
|
||||
curl -X POST http://localhost:5000/api/scan/bt
|
||||
|
||||
# List scans
|
||||
curl http://localhost:5000/api/scans
|
||||
|
||||
# Get auto-scan status
|
||||
curl http://localhost:5000/api/autoscan
|
||||
|
||||
# Start auto-scan
|
||||
curl -X POST http://localhost:5000/api/autoscan/start \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"interval_minutes": 5}'
|
||||
|
||||
# Stop auto-scan
|
||||
curl -X POST http://localhost:5000/api/autoscan/stop
|
||||
|
||||
# Set device floor
|
||||
curl -X POST http://localhost:5000/api/device/AA:BB:CC:DD:EE:FF/floor \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"floor": 2}'
|
||||
|
||||
# Get all floor assignments
|
||||
curl http://localhost:5000/api/device/floors
|
||||
|
||||
# Get device history
|
||||
curl "http://localhost:5000/api/history/devices?type=bluetooth&limit=20"
|
||||
|
||||
# Get RSSI history for device
|
||||
curl http://localhost:5000/api/history/devices/AA:BB:CC:DD:EE:FF/rssi
|
||||
|
||||
# Get database stats
|
||||
curl http://localhost:5000/api/history/stats
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Config File Locations
|
||||
|
||||
Checked in order:
|
||||
|
||||
1. `./config.yaml` (project directory)
|
||||
2. `~/.config/rf-mapper/config.yaml`
|
||||
3. `/etc/rf-mapper/config.yaml`
|
||||
|
||||
---
|
||||
|
||||
## Environment Variables
|
||||
|
||||
| Variable | Description |
|
||||
|----------|-------------|
|
||||
| `RF_MAPPER_LAT` | Override GPS latitude |
|
||||
| `RF_MAPPER_LON` | Override GPS longitude |
|
||||
| `RF_MAPPER_HOST` | Override web server host |
|
||||
| `RF_MAPPER_PORT` | Override web server port |
|
||||
| `HA_TOKEN` | Home Assistant API token |
|
||||
| `HA_URL` | Home Assistant URL |
|
||||
|
||||
---
|
||||
|
||||
## Key Config Options
|
||||
|
||||
```yaml
|
||||
# GPS position
|
||||
gps:
|
||||
latitude: 50.8585
|
||||
longitude: 4.3978
|
||||
|
||||
# Web server
|
||||
web:
|
||||
host: "0.0.0.0"
|
||||
port: 5000
|
||||
debug: false
|
||||
|
||||
# Scanner settings
|
||||
scanner:
|
||||
wifi_interface: "wlan0"
|
||||
bt_scan_timeout: 10
|
||||
path_loss_exponent: 2.5 # 2.0=open, 2.5=indoor, 3.5=walls
|
||||
auto_identify_bluetooth: true
|
||||
|
||||
# Data storage
|
||||
data:
|
||||
directory: "data"
|
||||
max_scans: 100
|
||||
|
||||
# Database
|
||||
database:
|
||||
enabled: true
|
||||
filename: "devices.db"
|
||||
retention_days: 30
|
||||
auto_cleanup: true
|
||||
|
||||
# Building (3D view)
|
||||
building:
|
||||
enabled: true
|
||||
floors: 3
|
||||
floor_height_m: 3.0
|
||||
current_floor: 1
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Data Locations
|
||||
|
||||
| Path | Content |
|
||||
|------|---------|
|
||||
| `data/` | Scan JSON files |
|
||||
| `data/devices.db` | SQLite database |
|
||||
| `data/profiles/` | Request profiles |
|
||||
| `data/logs/` | Request logs |
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Permission denied (WiFi/BT)
|
||||
|
||||
```bash
|
||||
sudo rf-mapper scan
|
||||
```
|
||||
|
||||
### No Bluetooth adapter
|
||||
|
||||
```bash
|
||||
sudo systemctl start bluetooth
|
||||
sudo hciconfig hci0 up
|
||||
```
|
||||
|
||||
### No RSSI from Bluetooth
|
||||
|
||||
The app uses `bleak` library for BLE scanning. Ensure:
|
||||
- BlueZ service running: `systemctl status bluetooth`
|
||||
- D-Bus available
|
||||
- Bluetooth adapter powered on
|
||||
|
||||
### Check WiFi interface
|
||||
|
||||
```bash
|
||||
iw dev # List interfaces
|
||||
sudo iw dev wlan0 scan # Test scan
|
||||
```
|
||||
|
||||
### Database issues
|
||||
|
||||
```bash
|
||||
# Check database
|
||||
sqlite3 data/devices.db ".tables"
|
||||
sqlite3 data/devices.db "SELECT COUNT(*) FROM devices"
|
||||
|
||||
# Manual cleanup
|
||||
curl -X POST http://localhost:5000/api/history/cleanup \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"retention_days": 7}'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Web Interface Views
|
||||
|
||||
| View | Description | Best For |
|
||||
|------|-------------|----------|
|
||||
| Radar | Distance rings | Quick overview |
|
||||
| World Map | Leaflet 2D | Geographic context |
|
||||
| 3D Map | MapLibre GL | Building/floor view |
|
||||
|
||||
### Keyboard Shortcuts (Web UI)
|
||||
|
||||
| Key | Action |
|
||||
|-----|--------|
|
||||
| `R` | Refresh scan |
|
||||
| `1` | Radar view |
|
||||
| `2` | World map view |
|
||||
| `3` | 3D map view |
|
||||
|
||||
---
|
||||
|
||||
## Signal Quality Reference
|
||||
|
||||
| RSSI Range | Quality | Est. Distance |
|
||||
|------------|---------|---------------|
|
||||
| -30 to -50 | Excellent | < 2m |
|
||||
| -50 to -60 | Good | 2-5m |
|
||||
| -60 to -70 | Fair | 5-10m |
|
||||
| -70 to -80 | Weak | 10-20m |
|
||||
| < -80 | Poor | > 20m |
|
||||
|
||||
---
|
||||
|
||||
## Path Loss Exponent Guide
|
||||
|
||||
| Value | Environment |
|
||||
|-------|-------------|
|
||||
| 2.0 | Free space (outdoor) |
|
||||
| 2.5 | Light indoor (open plan) |
|
||||
| 3.0 | Normal indoor |
|
||||
| 3.5 | Dense indoor (walls) |
|
||||
| 4.0+ | Heavy obstructions |
|
||||
360
docs/HOME_ASSISTANT.md
Normal file
360
docs/HOME_ASSISTANT.md
Normal file
@@ -0,0 +1,360 @@
|
||||
# Home Assistant Integration
|
||||
|
||||
RF Mapper integrates with Home Assistant using webhooks. RF Mapper sends events to HA, and HA automations handle the logic.
|
||||
|
||||
## Features
|
||||
|
||||
| Feature | Description | HA Entity Type |
|
||||
|---------|-------------|----------------|
|
||||
| Device presence | Track WiFi/BT devices | `device_tracker.rf_*` |
|
||||
| New device alerts | Notify on unknown device | `automation` trigger |
|
||||
| Departure alerts | Notify when device leaves | `automation` trigger |
|
||||
| Sensor entities | Device count, nearest distance | `sensor.rf_*` |
|
||||
| Multi-scanner | Room-level presence detection | per-scanner sensors |
|
||||
|
||||
## Architecture
|
||||
|
||||
### Single Scanner
|
||||
|
||||
```
|
||||
RF Mapper Home Assistant
|
||||
------------------------------------------------------
|
||||
[Scan] ----webhook----> /api/webhook/rf_mapper_scan
|
||||
|-- automation: update sensors
|
||||
|-- automation: device_tracker.see
|
||||
|
||||
[New Device] --webhook--> /api/webhook/rf_mapper_new_device
|
||||
|-- automation: notify
|
||||
|
||||
[Device Gone] --webhook-> /api/webhook/rf_mapper_device_gone
|
||||
|-- automation: notify
|
||||
```
|
||||
|
||||
### Multi-Scanner Setup
|
||||
|
||||
```
|
||||
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
|
||||
│ Pi #1 │ │ Pi #2 │ │ Pi #3 │
|
||||
│ scanner: │ │ scanner: │ │ scanner: │
|
||||
│ id: living│ │ id: kitchen│ │ id: bedroom│
|
||||
│ floor: 0 │ │ floor: 0 │ │ floor: 1 │
|
||||
└──────┬──────┘ └──────┬──────┘ └──────┬──────┘
|
||||
│ │ │
|
||||
└───────────────────┼───────────────────┘
|
||||
▼
|
||||
┌───────────────┐
|
||||
│ Home Assistant │
|
||||
│ - Track nearest│
|
||||
│ scanner/device│
|
||||
│ - Room presence │
|
||||
└───────────────┘
|
||||
```
|
||||
|
||||
## RF Mapper Configuration
|
||||
|
||||
Enable webhooks in `config.yaml`:
|
||||
|
||||
```yaml
|
||||
# Scanner identity (for multi-scanner support)
|
||||
scanner:
|
||||
id: "living_room" # Unique scanner ID
|
||||
name: "Living Room Scanner" # Human-readable name
|
||||
latitude: 50.8584 # Scanner position (optional, falls back to gps.latitude)
|
||||
longitude: 4.3976 # Scanner position (optional, falls back to gps.longitude)
|
||||
floor: 0 # Scanner's floor (optional, falls back to building.current_floor)
|
||||
|
||||
home_assistant:
|
||||
enabled: true
|
||||
url: "http://192.168.129.10:8123"
|
||||
webhook_scan: "rf_mapper_scan"
|
||||
webhook_new_device: "rf_mapper_new_device"
|
||||
webhook_device_gone: "rf_mapper_device_gone"
|
||||
device_timeout_minutes: 5
|
||||
```
|
||||
|
||||
### Scanner Identity Settings
|
||||
|
||||
| Setting | Description |
|
||||
|---------|-------------|
|
||||
| `scanner.id` | Unique scanner identifier (auto-generated from hostname if empty) |
|
||||
| `scanner.name` | Human-readable display name (defaults to id) |
|
||||
| `scanner.latitude` | Scanner latitude (falls back to `gps.latitude`) |
|
||||
| `scanner.longitude` | Scanner longitude (falls back to `gps.longitude`) |
|
||||
| `scanner.floor` | Scanner's floor number (falls back to `building.current_floor`) |
|
||||
|
||||
### Home Assistant Settings
|
||||
|
||||
| Setting | Description |
|
||||
|---------|-------------|
|
||||
| `enabled` | Enable/disable HA integration |
|
||||
| `url` | Home Assistant URL (no trailing slash) |
|
||||
| `webhook_scan` | Webhook ID for scan results |
|
||||
| `webhook_new_device` | Webhook ID for new device alerts |
|
||||
| `webhook_device_gone` | Webhook ID for departure alerts |
|
||||
| `device_timeout_minutes` | Minutes before device is considered departed |
|
||||
|
||||
## Home Assistant Setup
|
||||
|
||||
### 1. Automations (`automations.yaml`)
|
||||
|
||||
```yaml
|
||||
# Process scan results - update device trackers with scanner info
|
||||
- alias: "RF Mapper - Update Device Trackers"
|
||||
trigger:
|
||||
- platform: webhook
|
||||
webhook_id: rf_mapper_scan
|
||||
action:
|
||||
- repeat:
|
||||
for_each: "{{ trigger.json.devices }}"
|
||||
sequence:
|
||||
- service: device_tracker.see
|
||||
data:
|
||||
dev_id: "rf_{{ repeat.item.id | replace(':', '_') }}"
|
||||
source_type: "{{ 'bluetooth' if ':' in repeat.item.id else 'router' }}"
|
||||
attributes:
|
||||
friendly_name: "{{ repeat.item.name }}"
|
||||
rssi: "{{ repeat.item.rssi }}"
|
||||
distance_m: "{{ repeat.item.distance }}"
|
||||
floor: "{{ repeat.item.floor }}"
|
||||
scanner_id: "{{ trigger.json.scanner.id }}"
|
||||
scanner_name: "{{ trigger.json.scanner.name }}"
|
||||
|
||||
# New device notification (includes which scanner detected it)
|
||||
- alias: "RF Mapper - New Device Alert"
|
||||
trigger:
|
||||
- platform: webhook
|
||||
webhook_id: rf_mapper_new_device
|
||||
action:
|
||||
- service: notify.persistent_notification
|
||||
data:
|
||||
title: "New Device Detected"
|
||||
message: >
|
||||
{{ trigger.json.device_type }}: {{ trigger.json.name }}
|
||||
({{ trigger.json.device_id }})
|
||||
detected by {{ trigger.json.scanner.name | default('unknown scanner') }}
|
||||
|
||||
# Device departure notification (includes last scanner)
|
||||
- alias: "RF Mapper - Device Left"
|
||||
trigger:
|
||||
- platform: webhook
|
||||
webhook_id: rf_mapper_device_gone
|
||||
action:
|
||||
- service: notify.persistent_notification
|
||||
data:
|
||||
title: "Device Left"
|
||||
message: >
|
||||
{{ trigger.json.name }} last seen {{ trigger.json.last_seen }}
|
||||
at {{ trigger.json.last_scanner.name | default('unknown location') }}
|
||||
```
|
||||
|
||||
### 2. Sensor Templates (`configuration.yaml`)
|
||||
|
||||
```yaml
|
||||
template:
|
||||
- trigger:
|
||||
- platform: webhook
|
||||
webhook_id: rf_mapper_scan
|
||||
sensor:
|
||||
# Per-scanner device count sensor
|
||||
- name: "RF Scanner {{ trigger.json.scanner.id }} Device Count"
|
||||
unique_id: "rf_scanner_{{ trigger.json.scanner.id }}_count"
|
||||
state: "{{ trigger.json.device_count }}"
|
||||
icon: mdi:bluetooth
|
||||
attributes:
|
||||
scanner_id: "{{ trigger.json.scanner.id }}"
|
||||
scanner_name: "{{ trigger.json.scanner.name }}"
|
||||
scanner_floor: "{{ trigger.json.scanner.floor }}"
|
||||
|
||||
# Per-scanner nearest device sensor
|
||||
- name: "RF Scanner {{ trigger.json.scanner.id }} Nearest"
|
||||
unique_id: "rf_scanner_{{ trigger.json.scanner.id }}_nearest"
|
||||
state: "{{ trigger.json.devices | map(attribute='distance') | min | round(1) if trigger.json.devices else 'none' }}"
|
||||
unit_of_measurement: "m"
|
||||
icon: mdi:map-marker-distance
|
||||
attributes:
|
||||
scanner_id: "{{ trigger.json.scanner.id }}"
|
||||
```
|
||||
|
||||
## Webhook Payload Reference
|
||||
|
||||
### Scan Results (`rf_mapper_scan`)
|
||||
|
||||
```json
|
||||
{
|
||||
"timestamp": "2026-02-01T12:34:56.789",
|
||||
"scan_type": "bluetooth",
|
||||
"scanner": {
|
||||
"id": "living_room",
|
||||
"name": "Living Room Scanner",
|
||||
"latitude": 50.8584,
|
||||
"longitude": 4.3976,
|
||||
"floor": 0
|
||||
},
|
||||
"scanner_floor": 0,
|
||||
"device_count": 5,
|
||||
"devices": [
|
||||
{
|
||||
"id": "AA:BB:CC:DD:EE:FF",
|
||||
"name": "iPhone",
|
||||
"rssi": -65,
|
||||
"distance": 3.2,
|
||||
"floor": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Description |
|
||||
|-------|-------------|
|
||||
| `scanner` | Full scanner identity object |
|
||||
| `scanner.id` | Unique scanner identifier |
|
||||
| `scanner.name` | Human-readable scanner name |
|
||||
| `scanner.latitude` | Scanner GPS latitude |
|
||||
| `scanner.longitude` | Scanner GPS longitude |
|
||||
| `scanner.floor` | Floor where scanner is located |
|
||||
| `scanner_floor` | (Deprecated) Same as `scanner.floor`, for backward compatibility |
|
||||
|
||||
### New Device (`rf_mapper_new_device`)
|
||||
|
||||
```json
|
||||
{
|
||||
"timestamp": "2026-02-01T12:34:56.789",
|
||||
"device_id": "AA:BB:CC:DD:EE:FF",
|
||||
"name": "Unknown Device",
|
||||
"device_type": "bluetooth",
|
||||
"manufacturer": "Apple, Inc.",
|
||||
"rssi": -70,
|
||||
"distance_m": 5.5,
|
||||
"scanner": {
|
||||
"id": "living_room",
|
||||
"name": "Living Room Scanner",
|
||||
"latitude": 50.8584,
|
||||
"longitude": 4.3976,
|
||||
"floor": 0
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Device Gone (`rf_mapper_device_gone`)
|
||||
|
||||
```json
|
||||
{
|
||||
"timestamp": "2026-02-01T12:34:56.789",
|
||||
"device_id": "AA:BB:CC:DD:EE:FF",
|
||||
"name": "iPhone",
|
||||
"device_type": "bluetooth",
|
||||
"last_seen": "2026-02-01T12:29:00.000",
|
||||
"last_scanner": {
|
||||
"id": "living_room",
|
||||
"name": "Living Room Scanner",
|
||||
"latitude": 50.8584,
|
||||
"longitude": 4.3976,
|
||||
"floor": 0
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Verification Steps
|
||||
|
||||
1. **Enable integration**: Set `home_assistant.enabled: true` in config.yaml
|
||||
2. **Add HA automations**: Copy webhook automations to HA
|
||||
3. **Restart RF Mapper**: `rf-mapper restart`
|
||||
4. **Run scan**: Trigger BT scan in RF Mapper web UI
|
||||
5. **Check HA**: Verify `device_tracker.rf_*` entities appear
|
||||
6. **Test new device**: Clear device from DB, re-scan, verify notification
|
||||
7. **Test departure**: Wait for timeout, verify departure notification
|
||||
8. **Check sensors**: Verify `sensor.rf_mapper_*` values update
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
| Issue | Solution |
|
||||
|-------|----------|
|
||||
| Webhooks not received | Check HA URL in config, ensure no firewall blocking |
|
||||
| No device trackers | Verify automation is enabled in HA |
|
||||
| Departure not triggered | Increase `device_timeout_minutes` |
|
||||
| Connection timeout | Check network connectivity between RF Mapper and HA |
|
||||
|
||||
## Multi-Scanner Setup
|
||||
|
||||
Configure each scanner with a unique ID and position:
|
||||
|
||||
**Pi #1 (Living Room):**
|
||||
```yaml
|
||||
scanner:
|
||||
id: "living_room"
|
||||
name: "Living Room Scanner"
|
||||
floor: 0
|
||||
latitude: 50.8584
|
||||
longitude: 4.3976
|
||||
```
|
||||
|
||||
**Pi #2 (Kitchen):**
|
||||
```yaml
|
||||
scanner:
|
||||
id: "kitchen"
|
||||
name: "Kitchen Scanner"
|
||||
floor: 0
|
||||
latitude: 50.8585
|
||||
longitude: 4.3978
|
||||
```
|
||||
|
||||
**Pi #3 (Bedroom):**
|
||||
```yaml
|
||||
scanner:
|
||||
id: "bedroom"
|
||||
name: "Bedroom Scanner"
|
||||
floor: 1
|
||||
latitude: 50.8584
|
||||
longitude: 4.3977
|
||||
```
|
||||
|
||||
### Room Presence Automation
|
||||
|
||||
```yaml
|
||||
# Track which room a device is in (nearest scanner)
|
||||
- alias: "RF Mapper - Track Room Presence"
|
||||
trigger:
|
||||
- platform: webhook
|
||||
webhook_id: rf_mapper_scan
|
||||
action:
|
||||
- repeat:
|
||||
for_each: "{{ trigger.json.devices }}"
|
||||
sequence:
|
||||
- service: input_text.set_value
|
||||
target:
|
||||
entity_id: "input_text.rf_{{ repeat.item.id | replace(':', '_') }}_room"
|
||||
data:
|
||||
value: "{{ trigger.json.scanner.id }}"
|
||||
```
|
||||
|
||||
## Advanced: Presence-based Automations
|
||||
|
||||
```yaml
|
||||
# Turn on lights when specific device arrives in living room
|
||||
- alias: "Welcome Home - Living Room"
|
||||
trigger:
|
||||
- platform: webhook
|
||||
webhook_id: rf_mapper_scan
|
||||
condition:
|
||||
- condition: template
|
||||
value_template: >
|
||||
{{ trigger.json.scanner.id == 'living_room' and
|
||||
trigger.json.devices | selectattr('id', 'eq', 'AA:BB:CC:DD:EE:FF') | list | count > 0 }}
|
||||
action:
|
||||
- service: light.turn_on
|
||||
target:
|
||||
entity_id: light.living_room
|
||||
|
||||
# Turn off lights when device leaves a room
|
||||
- alias: "Room Empty Check"
|
||||
trigger:
|
||||
- platform: webhook
|
||||
webhook_id: rf_mapper_scan
|
||||
condition:
|
||||
- condition: template
|
||||
value_template: "{{ trigger.json.scanner.id == 'bedroom' and trigger.json.device_count == 0 }}"
|
||||
action:
|
||||
- service: light.turn_off
|
||||
target:
|
||||
entity_id: light.bedroom
|
||||
```
|
||||
Reference in New Issue
Block a user