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>
361 lines
11 KiB
Markdown
361 lines
11 KiB
Markdown
# 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
|
|
```
|