A turnkey LoRa mesh network coverage testing and heatmap tool built for community mesh networks running MeshCore firmware.
Created by Ingo Azarvand for EGMESH.NET Elk Grove, California
Runs on a Raspberry Pi with a MeshCore companion radio connected via USB. Broadcasts its own WiFi hotspot so you can connect your phone while driving. Live web dashboard shows real-time SNR ping results and GPS coordinates from your phone. After your drive, generate an interactive HTML heatmap overlaid on OpenStreetMap street maps.
How ping measurement works: The logger sends a status request through the mesh via flood routing. The repeater re-broadcasts the packet, and the companion radio captures the echo. The SNR and RSSI from that echo tell you the signal quality between your current location and the repeater.
How GPS works: The logger tries a USB GPS dongle first. If none is
detected, it falls back to your phone's browser location via
navigator.geolocation. HTTPS (port 5000) is required for the browser to
grant geolocation permission — a self-signed cert is generated automatically
during install. A plain HTTP server on port 5001 is also available for
desktop browsers that don't need GPS.
| Component | Details |
|---|---|
| Raspberry Pi | Pi 4 or Pi Zero 2 WH (or Pi 5) |
| MeshCore companion radio | RAK4631, Heltec, T-Beam, etc. — USB Companion firmware |
| MicroSD card | 16GB+ |
| Power | USB-C power bank or 12V car adapter |
| GPIO 17 button | Momentary pushbutton — switches between hotspot and home WiFi |
| GPIO 27 green LED | Home WiFi indicator — 1kΩ resistor to GND (optional) |
| GPIO 22 blue LED | Hotspot indicator — 1kΩ resistor to GND (optional) |
Note: A USB GPS dongle is optional. If connected, it takes priority. If not present, the logger falls back to phone GPS via
navigator.geolocation.
| Setting | Value |
|---|---|
| Frequency | 910.525 MHz |
| Bandwidth | 125 kHz |
| Spreading Factor | SF10 |
| Coding Rate | 4/5 |
| TX Power | 22 dBm |
| File | Purpose |
|---|---|
app.py |
Flask web app — routes, GPS API, logger loop |
mesh_ping.py |
Serial ping module — auto-detects USB, captures SNR/RSSI |
index.html |
Web dashboard UI |
heatmap.py |
Post-drive heatmap generator |
install.sh |
One-step installer |
update.sh |
Copies repo files to runtime directory and restarts services |
wifi_toggle.py |
GPIO 17 button handler — single press: hotspot, double press: home WiFi |
egmesh.service |
systemd unit — runs logger on boot (HTTPS :5000 + HTTP :5001) |
egmesh-wifi.service |
systemd unit — runs WiFi toggle button handler on boot |
cert.pem / key.pem |
Self-signed TLS cert (generated by install.sh, required for phone GPS) |
Go to flasher.meshcore.co, select your device, choose Companion USB, and flash it.
sudo apt-get install -y git
git clone https://github.com/ingo916/egmesh-coverage-logger.git
cd egmesh-coverage-logger
sudo bash install.shThe install script will:
- Install Python dependencies
- Generate a self-signed TLS cert (
cert.pem/key.pem) if not present - Install and enable
egmesh.service(logger, HTTPS :5000 + HTTP :5001) - Install and enable
egmesh-wifi.service(GPIO 17 WiFi toggle)
Connect the companion radio to the Pi via USB. The serial port is auto-detected — no pairing or configuration needed.
Radio settings are managed from the web UI — no file editing needed.
Open the dashboard and scroll to the Radio Configuration card. Click ↻ Read from Radio to load current settings, adjust the fields, then click ⚡ Write to Radio & Reboot to apply. meshcli is not required.
Repeaters are managed from the web UI — no file editing needed.
Open the dashboard and scroll to the Repeater card. Either:
- Scan — lets the Pi query live mesh contacts and pick from the list, or
- Add manually — enter the repeater name and public key directly
Once added, tap Select to make it the active repeater. The logger will not start until a repeater is selected.
Press the GPIO 17 button once — the Pi activates its hotspot via nmcli.
Connect your phone to EGMESH-LOGGER (password: egmesh2025), then open
Safari and navigate to http://10.42.0.1:5001.
The hotspot IP is 10.42.0.1. No reboot needed — the button switches modes on the fly. Double-press to return to home WiFi.
sudo systemctl start egmeshThe service starts automatically on boot. Both ports are active:
| Port | Protocol | Use |
|---|---|---|
| 5000 | HTTPS | Phone browser — GPS logging (self-signed cert) |
| 5001 | HTTP | Desktop browser — no GPS, no cert warning |
The Ping Interval dropdown in the Status card lets you set how often the logger pings the repeater (5 / 10 / 15 / 20 / 30 seconds). Changes take effect after the current ping completes — no restart needed.
- Press GPIO 17 once → Pi switches to hotspot mode (3 slow LED blinks = ready)
- Connect your phone to EGMESH-LOGGER (password:
egmesh2025) - Open Safari and go to
http://10.42.0.1:5001— bookmark this for quick access - For GPS logging, use
https://10.42.0.1:5000instead — accept the cert warning once, then grant location permission - Drive or walk the coverage area
Tip — Add to Home Screen: In Safari, tap the Share button → Add to Home Screen → tap Add. The logger opens full screen from your home screen with one tap. Do this once after your first connection and you never have to type the URL again.
Navigate to http://<pi-ip>:5001 for plain HTTP access.
No cert warning. GPS is not available (desktop browsers require HTTPS for geolocation).
| Press | Action |
|---|---|
| Single press | Switch to hotspot mode (10.42.0.1) |
| Double press | Switch back to home WiFi |
The egmesh-wifi.service runs wifi_toggle.py on boot and listens for button events.
Two external LEDs give visual feedback without needing to see the onboard ACT LED — useful when the Pi is inside a 3D printed case.
| LED | GPIO | Pin | Resistor | Meaning |
|---|---|---|---|---|
| Green | GPIO 27 | Pin 13 | 1kΩ | Home WiFi — slow pulse |
| Blue | GPIO 22 | Pin 15 | 1kΩ | Hotspot active — slow pulse |
Wiring:
Pin 13 (GPIO 27) ── 1kΩ ── Green LED anode (+) ── cathode (−) ── GND
Pin 15 (GPIO 22) ── 1kΩ ── Blue LED anode (+) ── cathode (−) ── GND
Pin 11 (GPIO 17) ── Button ── GND
Pin 9 GND ── shared GND rail
The anode is the longer leg. The cathode is the shorter leg with the flat side on the LED body. LEDs are optional — if not wired, the pins are driven silently with no effect.
The phone browser posts location updates directly to the Pi:
POST /api/gps
Content-Type: application/json
{
"lat": 37.123,
"lon": -121.456
}
HTTPS on port 5000 is required — modern browsers block navigator.geolocation
on plain HTTP pages.
cd /root/egmesh_logger
source venv/bin/activate
python3 mesh_ping.pyYou should see:
Trying serial: /dev/ttyACM0 @ 115200 baud
Connected via SERIAL on /dev/ttyACM0
Echo received: snr=10.75 rssi=-41 rtt=0.5s
cd /root/egmesh_logger
source venv/bin/activate
python heatmap.pyThe output file is named to match the source CSV — for example coverage_EG_SE_RAK4631_RPTR_20260326_173000.csv generates heatmap_coverage_EG_SE_RAK4631_RPTR_20260326_173000.html.
You can also generate a heatmap directly from the web UI — tap Generate Heatmap in the Files card. The download is named the same way.
Opens an interactive HTML heatmap overlaid on OpenStreetMap. Internet connection required when viewing.
Make sure the companion radio is plugged in via USB and running USB Companion firmware (not BLE Companion). Check:
ls /dev/ttyACM* /dev/ttyUSB*The device may be running BLE Companion firmware instead of USB Companion. Reflash from flasher.meshcore.co — select Companion USB.
The repeater needs to advertise before it appears. Wait a few minutes after the logger starts. If it still doesn't appear, check that the radio frequency and spreading factor match the repeater's config (SF10, 910.525 MHz).
- Make sure you're on the HTTPS port (5000), not 5001
- Accept the self-signed cert warning before granting location permission
- If the browser shows "Location blocked", clear the permission in browser settings and reload
The hotspot IP is 10.42.0.1. Make sure you're connecting to EGMESH-LOGGER
and opening http://10.42.0.1:5001 in Safari after connecting.
To update to the latest version:
cd ~/egmesh-coverage-logger
git pull origin main
sudo bash update.shThis pulls the latest code from GitHub and deploys it to the runtime directory without reinstalling.
Add your repeater via the web UI Repeater card (name + key, or scan from mesh contacts).
Set your radio frequency via the web UI Radio Configuration card.
Update the header in index.html to your network name.
Common frequencies:
- US EGMESH.NET: 910.525 MHz / BW125 / SF10 / CR4/5
- EU868: 868.0 MHz / BW125 / SF9
- Phone GPS via
navigator.geolocation— USB dongle fallback if present - Dual-port server: HTTPS :5000 (phone/GPS) + HTTP :5001 (desktop)
- Self-signed cert auto-generated by
install.sh - Radio config via web UI (
mc.commands.set_radio()) — meshcli no longer required - Repeater manager via web UI (scan or manual add)
- WiFi toggle button on GPIO 17 (single: hotspot, double: home WiFi)
- Dual external LEDs — GPIO 27 green (home WiFi), GPIO 22 blue (hotspot)
- Configurable ping interval (5/10/15/20/30s) in web UI
- Heatmap filename matches source CSV name
egmesh-wifi.serviceadded to install scriptupdate.shadded for deploying repo changes to runtime directory- Install deploys to
/root/egmesh_logger/(runs as root) - Hotspot IP
10.42.0.1 - SF updated to SF10
- GPIO 17 WiFi toggle (initial implementation)
- HTTPS support with self-signed cert
- USB GPS dongle support
- Flask dashboard, heatmap generator
- meshcli-based radio configuration
Created by Ingo Azarvand for EGMESH.NET Volunteer community LoRa mesh network — Elk Grove, California Emergency preparedness and neighborhood resilience.
If you use or adapt this tool, please credit EGMESH.NET and Ingo Azarvand, Elk Grove, CA in your documentation.
Copyright (c) 2026 Ingo Azarvand / EGMESH.NET See LICENSE for full terms.