LORA_COMPANION_GUIDE.md
January 10, 2026 · View on GitHub
MeshCore Wardrive - LoRa Companion Guide
This app now works exactly like the mesh-map.pages.dev website - using your LoRa companion device to send actual radio pings and MQTT to listen for observer responses.
How It Works
1. Phone (GPS) → USB/Bluetooth → LoRa Companion
2. LoRa Companion → LoRa Radio → MeshCore Observers
3. Observers → MQTT Broker → App listens
4. Green square = Observer heard you
5. Red square = Dead zone (no observer response)
Key Points
- LoRa device transmits the actual radio ping
- MQTT listens for responses from observers
- Tests real mesh coverage, not just internet connectivity
- Pings every ~0.5 miles (adjustable)
- Can ignore your mobile repeater to avoid false positives
Setup Steps
1. Connect LoRa Companion
Option A: USB (Recommended)
- Plug LoRa device into phone via USB-C/OTG
- Grant USB permissions when prompted
- In app: Tap "Scan USB Devices"
- Select your device from list
- Wait for "Connected via USB"
Option B: Bluetooth
- Pair LoRa device in Android Bluetooth settings
- In app: Tap "Scan Bluetooth Devices"
- Select your device (e.g., "Meshtastic_xxxx")
- Wait for "Connected via Bluetooth"
2. Connect to MQTT
- Tap "Connect to MQTT"
- Enter broker details (default:
mqtt.meshcore.io) - Enter credentials if required
- Wait for "MQTT Connected"
3. Configure Settings (Optional)
Ignore Mobile Repeater:
- If you carry a portable repeater, set its prefix
- Example: If your repeater ID is
MOB-123, enterMOB- - This prevents false positive pings
Ping Interval:
- Default: Every ~0.5 miles
- Adjust distance filter in settings
4. Start Wardriving
- Enable "Auto-Ping" toggle
- Tap green play button to start GPS tracking
- As you move:
- Every 0.5 miles → LoRa device sends ping
- Wait 30 seconds for observers to respond via MQTT
- Green = heard by observer
- Red = no response (dead zone)
Supported LoRa Devices
The app should work with:
- Meshtastic devices (T-Beam, Heltec, LILYGO, etc.)
- Custom LoRa boards with serial interface
- Any device that accepts ping commands via UART
Command Format
The app sends: ping {8-char-id}\n
Example: ping abc12345\n
Your LoRa device should:
- Transmit this as a broadcast LoRa message
- Include the ping ID in the transmission
MQTT Configuration
Default Settings
Broker: mqtt.meshcore.io
Port: 1883
Subscribe Topic: meshcore/observer/+/pong
Expected Observer Response Format
When an observer hears your ping, it should publish to MQTT:
Topic: meshcore/observer/{observer_id}/pong
Payload:
{
"ping_id": "abc12345",
"observer_id": "OBS-001",
"rssi": -85,
"snr": 7,
"lat": 47.7776,
"lon": -122.4247,
"timestamp": "2024-01-01T12:00:00Z"
}
Customization
Change MQTT Broker
Edit lib/services/lora_companion_service.dart:
// Line 69-71
static const String defaultMqttBroker = 'mqtt.meshcore.io';
static const int defaultMqttPort = 1883;
static const String baseTopic = 'meshcore';
Change MQTT Topic Pattern
Edit line 270:
final topic = '$baseTopic/observer/+/pong';
Adjust Ping Command
Edit line 314:
await _sendToDevice('ping $pingId\n');
For custom LoRa devices, change this to match your command format.
Change Ping Interval
The app pings based on distance moved. To change:
Edit lib/services/location_service.dart line 71:
distanceFilter: 5, // meters - reduce for more frequent pings
For ~0.5 miles: distanceFilter: 805 (805 meters = 0.5 miles)
Data Export
Exported samples include all ping data:
{
"id": "1234567890_c23nb2q2",
"lat": 47.7776,
"lon": -122.4247,
"timestamp": "2024-01-01T12:00:00.000Z",
"geohash": "c23nb2q2",
"rssi": -85,
"snr": 7,
"pingSuccess": true
}
pingSuccess: true= Observer heard your ping (green)pingSuccess: false= No observer response (red)pingSuccess: null= Auto-ping was disabled
Troubleshooting
LoRa Device Won't Connect
USB:
- Check USB-C cable supports data (not just charging)
- Enable USB debugging in Android settings
- Try different USB port
Bluetooth:
- Pair device in Android settings first
- Ensure device is in discoverable mode
- Check device battery
MQTT Won't Connect
- Verify broker address and port
- Check internet connection (cellular/WiFi)
- Confirm credentials if required
- Test broker with MQTT client (MQTT Explorer, mosquitto_sub)
No Observer Responses
- Verify observers are online and publishing to MQTT
- Check MQTT topic pattern matches
- Ensure LoRa device is actually transmitting
- Confirm ping command format is correct
- Check if you're in range of any observers
Ping Timeout Too Long
Default timeout is 30 seconds. To reduce:
Edit lib/services/location_service.dart line 146:
timeoutSeconds: 30, // Reduce this value
False Positives from Mobile Repeater
Set ignored repeater prefix in app settings:
- Settings → Ignore Repeater Prefix
- Enter your repeater's ID prefix (e.g.,
MOB-)
Testing Without Real Network
Test LoRa Connection
- Connect device via USB/Bluetooth
- Check device response in logs
- Send test ping manually
Test MQTT Connection
Use a public MQTT broker for testing:
broker: 'test.mosquitto.org'
port: 1883
// No authentication required
Simulate Observer Response
Use MQTT client to publish test response:
mosquitto_pub -h mqtt.meshcore.io -t meshcore/observer/TEST/pong -m '{
"ping_id": "testping",
"observer_id": "TEST-OBS",
"rssi": -75,
"snr": 9
}'
Advanced Features
Custom Ping Logic
For non-Meshtastic devices, modify _sendToDevice() in lora_companion_service.dart.
Custom Response Parsing
Modify _handleObserverResponse() (line 383) to match your MQTT response format.
Add Manual Ping Button
Access locationService.loraCompanion.ping() directly for single pings.
Performance Tips
- Ping Interval: 0.5 miles is good balance - closer intervals may slow you down waiting for responses
- Timeout: 30 seconds is reasonable for mesh networks
- Battery: USB connection drains less battery than Bluetooth
- Range: Stay within observer range for best results
Security & Privacy
- Your device ID is randomly generated
- GPS coordinates are sent to MQTT broker
- Ping IDs are random 8-character strings
- No personal information transmitted
- All collected data stays local unless exported
Credits
This implementation replicates the exact workflow from mesh-map.pages.dev for MeshCore coverage mapping with LoRa companions.