Passive BLE advertisement scanning system. ESP32 sensors → WireGuard VPN → MQTT → PostgreSQL → Grafana.
Infrastructure: /opt/docker/ble-platform/ on 192.168.11.18
Grafana: http://192.168.11.18:3030 | admin / ble_rpc_2026
MQTT broker: ble-mqtt container
Database: ble-postgres, database ble_data, table advertisements
WireGuard container: wireguard-iot on 192.168.11.18
WG external port mapping: host 52187 → container 51820 (Docker), pfSense NAT: external 52187 → 11.18:52187, external 123 → 11.18:52187
| ID | Assigned Name | Location | Address | Reporter ID | Tunnel IP | Status |
|---|---|---|---|---|---|---|
| BLE1 | Mink River Basin | Entry sensor | 12010 WI-42, Ellison Bay, WI | ble-sniffer-1 | 10.99.0.2 | Deployed |
| BLE2 | Zeke's Village Market | Front of house | Sister Bay, WI | ble-sniffer-2 | 10.99.0.3 | Ready to deploy |
| BLE3 | Mink River Basin 2 | Interior sensor | 12010 WI-42, Ellison Bay, WI | ble-sniffer-3 | 10.99.0.4 | Ready to deploy |
| BLE4 | Zeke's Backroom Sensor | Back of house | Sister Bay, WI | ble-sniffer-4 | 10.99.0.5 | Ready to deploy |
| BLE5 | Unknown | Unassigned | — | ble-sniffer-5 | 10.99.0.6 | Unassigned |
| SBM-1 | Sister Bay Marina 1 | TBD | Sister Bay, WI | ble-sniffer-sbm1 | 10.99.0.7 | Ready to deploy |
| SBM-2 | Sister Bay Marina 2 | TBD | Sister Bay, WI | ble-sniffer-sbm2 | 10.99.0.8 | Ready to deploy |
| SBM-3 | Sister Bay Marina 3 | TBD | Sister Bay, WI | ble-sniffer-sbm3 | 10.99.0.9 | Ready to deploy |
BLE1 (Mink River Basin):
| Priority | SSID |
|---|---|
| 10 | IoT_PPBN |
| 7 | MinkRiverPrivate |
| 5 | MRB_IoT |
BLE2 (Zeke's Village Market) & BLE4 (Zeke's Backroom):
| Priority | SSID |
|---|---|
| 10 | Bkp |
| 7 | Zeke’s Village Market (curly apostrophe ’) |
| 4 | IoT_PPBN |
CRITICAL: Zeke's SSID uses a curly apostrophe
’(U+2019), NOT a straight apostrophe'. Wrong apostrophe = WiFi won't connect.
BLE3 (Mink River Basin 2):
| Priority | SSID |
|---|---|
| 10 | IoT_PPBN |
| 4 | Bkp |
(MinkRiverPrivate / MRB_IoT passwords needed — not yet configured)
BLE5 (Unassigned):
| Priority | SSID |
|---|---|
| 10 | IoT_PPBN |
| 4 | Bkp |
| ID | Assigned Name | Tunnel IP | WG Peer Name |
|---|---|---|---|
| BLE1 | Mink River Basin | 10.99.0.2 | esp32ble1 |
| BLE2 | Zeke's Village Market | 10.99.0.3 | esp32ble2 |
| BLE3 | Mink River Basin 2 | 10.99.0.4 | esp32ble3 |
| BLE4 | Zeke's Backroom Sensor | 10.99.0.5 | esp32ble4 |
| BLE5 | Unknown | 10.99.0.6 | esp32ble5 |
| SBM-1 | Sister Bay Marina 1 | 10.99.0.7 | esp32sbm1 |
| SBM-2 | Sister Bay Marina 2 | 10.99.0.8 | esp32sbm2 |
| SBM-3 | Sister Bay Marina 3 | 10.99.0.9 | esp32sbm3 |
WireGuard routes for .3-.6 are added manually and do not persist across container restarts:
sudo docker exec wireguard-iot ip route add 10.99.0.X/32 dev wg0
Always use firmware.factory.bin, NOT firmware.bin.
firmware.bin is the application partition only. firmware.factory.bin is the merged binary (bootloader + partition table + application). Flashing firmware.bin to address 0x0 results in a crash loop (invalid header: 0x6f6e0079).
Flash command (from Trojan Pi or 11.18):
python3 -m esptool --port /dev/ttyUSB0 --baud 230400 erase-flash
python3 -m esptool --port /dev/ttyUSB0 --baud 230400 write-flash 0x0 ~/firmware/ble-sniffer-X.factory.bin
Do NOT use 460800 baud — causes corrupt flashes that pass hash verification but fail to boot.
Factory binaries location on Trojan Pi: ~/firmware/ble-sniffer-[1-5].factory.bin
Factory binaries location on 11.18: /home/nate/ble-sniffer-X-build/build/ble-sniffer-X/.pioenvs/ble-sniffer-X/firmware.factory.bin
ESPHome compile command (on 11.18):
sudo docker run --rm \
-v /home/nate/ble-sniffer-X.yaml:/config/ble-sniffer-X.yaml \
-v /home/nate/ble-sniffer-X-build:/config/.esphome \
ghcr.io/esphome/esphome:2025.2.2 compile /config/ble-sniffer-X.yaml
DO NOT upgrade ESPHome past 2025.2.2 — breaking API changes in later versions.
A Raspberry Pi Zero 2W deployed at Zeke's providing permanent remote access and ESP32 flashing capability without physical access.
| Property | Value |
|---|---|
| Hostname | TrojanPi |
| WireGuard tunnel IP | 10.254.254.226 |
| WireGuard server | pfSense (not wireguard-iot) |
| SSH access from 11.18 | sudo -u claudessh ssh nate@10.254.254.226 |
| OS | Debian GNU/Linux 13 (Trixie), aarch64 |
| Storage | 29GB SD, ~3.8GB used |
| Network | eth0 via USB ethernet (RTL8152) at 100.127.1.2/28 |
| WiFi | wlan0 DOWN — running on USB ethernet only |
| Docker | 26.1.5 installed |
| ESPHome | 2025.2.2 Docker image pulled |
| esptool | 5.2.0 installed via pip3 |
| ESP32 connection | /dev/ttyUSB0 via USB hub |
| Firmware location | ~/firmware/ble-sniffer-[1-5].factory.bin |
SSH key: claudessh@docker2 public key added to nate@TrojanPi ~/.ssh/authorized_keys
| Port | Protocol | Status | Notes |
|---|---|---|---|
| 52187 | UDP | Blocked at Zeke's UniFi | Original WG port |
| 52187 | UDP | Blocked on cellular (Bkp) | Carrier filtered |
| 123 | UDP | Open at Zeke's + cellular | Current WG port for Zeke's sensors |
BLE2 and BLE4 WireGuard port: 123
BLE1, BLE3 WireGuard port: 52187 (MRB network allows it)
/opt/docker/ble-platform/collector/collector.py — rebuild image after edits| Dashboard | UID |
|---|---|
| Mink River Basin | ble-sniffer |
| Zeke's Village Market | ble-sniffer-zekes |
| All Locations | ble-sniffer-all |
All timestamps displayed in America/Chicago timezone.
Business hours filter (11:30 AM – 11 PM) applied to stat/table panels only. Time series charts show full 24-hour data.