Firmware¶
Four Arduino sketches under firmware/, all sharing the same conventions:
a config block at the top of the file, an OUTPUT_SERIAL switch between
WiFi/MQTT and serial→LoRa backhaul, and a DEBUG_PRINT calibration readout
(automatically compiled out in serial mode so debug lines can never leak onto
the mesh).
Flashing¶
- Arduino IDE with the ESP32 board package (or PlatformIO with
platform = espressif32), plus the PubSubClient library. BLE mode uses the core's built-inBLEDevice; no extra install. - Edit the sketch's config block: WiFi credentials,
MQTT_HOST/MQTT_PORT, a uniqueNODE_ID, and the sensor's calibration knobs. - On an ESP32-C3 SuperMini: hold BOOT, tap RESET, release BOOT to
enter download mode; power over USB-C only while flashing. Build with the
CDCOnBoot=cdcboard option soSerialgoes over the native USB port. - Open Serial Monitor at 115200 to watch the debug readout; confirm arrivals
with
docker compose logs -f monitor.
WiFi/BLE sniffer (esp32_sniffer)¶
One board sniffs one radio: set SCAN_MODE to SCAN_WIFI or SCAN_BLE and
deploy a mix. BLE coexists cleanly with the WiFi/MQTT uplink; WiFi promiscuous
mode has to time-slice against the uplink, so serial→LoRa backhaul is smoother
there.
Key knobs: RSSI_MIN (ignore weak frames), COOLDOWN_MS (per-MAC re-publish
suppression), on-device WHITELIST[] (known MACs are never transmitted; they
would waste LoRa airtime).
RF attacks (DETECT_ATTACKS 1, WiFi mode): deauth-flood counting
(DEAUTH_THRESHOLD per window), rogue-AP detection for PROTECT_SSID against
KNOWN_BSSIDS[], and RF-silence reporting (SILENCE_SECONDS).
BLE trackers (DETECT_TRACKERS 1, BLE mode): Apple Find My
offline-finding, Tile, and SmartTag advertisement signatures.
Tracker and drone service data rides the BLE scan response, so these
features switch the node to active scanning with a 50/50 window/interval
split (the split matters: the radio needs the idle gap to send the scan
request and catch the response). Bench-validated against a live Samsung
SmartTag (service UUID fd5a) end to end.
Drone Remote ID (DETECT_DRONEID 1): the same sniffer also reports drone
Remote ID broadcasts (ASTM F3411 / Open Drone ID), the public identification
signal most drones must transmit. WiFi mode matches the beacon vendor IE
(ASD-STAN OUI FA:0B:BC); BLE mode matches service data UUID 0xFFFA.
Passive reception only; RSSI stands in for proximity.
Vehicle sensor (qmc5883l_vehicle)¶
Wiring (GY-271 module, I2C): VCC→3V3, GND→GND, SDA→GPIO8, SCL→GPIO9 (the
C3 core's default Wire pins). Mount rigidly within ~2–5 m of the drive lane;
a swaying post reads as field change and false-alarms.
Calibration: every site's field differs and sensors drift. Flash with
DEBUG_PRINT on and watch the once-per-second mag/baseline/delta line
during a few drive-bys, then set TRIGGER_LSB between ambient noise and your
smallest vehicle. BASELINE_ALPHA controls drift absorption; a shift
sustained past RESEED_MS (a car that parked) becomes the new baseline
automatically.
Bench-validated on an ESP32-C3 + GY-271: mag idles near 5960 (Earth's
field, ~0.5 gauss at ~12000 LSB/gauss), with a delta noise floor around 50.
A ferrous object waved by hand a few cm away produces delta ~200; a magnet
held close pegs it into the thousands. The TRIGGER_LSB = 300 default is sized
for a real vehicle at 2-5 m, not a handheld object, so a bench check either
needs a strong magnet up close or a temporarily lowered threshold (~120).
Vibration sensor (piezo_vibration)¶
Wiring: piezo disc between PIEZO_PIN (default GPIO3) and GND with a
1 MΩ resistor in parallel to bleed charge. The ESP32's ESD diodes clip
knock-energy spikes safely; add a 3.3 V zener across large discs on
hard-struck surfaces. Mount the disc rigidly (epoxy/screw clamp); a loose
disc reads as noise. On a fence, one disc per panel-run carries several meters
of mesh.
The bleed resistor is required, not optional: a piezo is a capacitor with
no DC path, so without a resistor to ground the input floats to mid-scale and
reads only mains hum. Any value from ~100 kΩ to 2 MΩ works. The chip's
internal pulldown is not a substitute on the ESP32-C3, because analogRead()
disables the internal pull on each call (bench-confirmed: the pin floated and
taps were indistinguishable from idle until a physical resistor was fitted).
Calibration: flash with DEBUG_PRINT on and watch the once-per-second
env_max/baseline/hits_in_window line. Knock, shake, and let the wind blow;
then set SPIKE_THRESHOLD above the loudest wind reading and below your
softest real knock. SHAKE_HITS/WINDOW_MS set how much repetition counts
as climbing. GLASS_MIN_SAMPLES separates a shatter (dense ringing, the
ring= debug figure) from a knock; calibrate by tapping vs. breaking a jar.
Lightning sensor (as3935_lightning)¶
Wiring (AS3935 module, I2C): VCC→3V3, GND→GND, SDA→GPIO8, SCL→GPIO9,
IRQ→IRQ_PIN (default GPIO4). Keep it away from switching supplies and LED
drivers; the AS3935 hears electrical noise as disturbers.
Calibration: flash with DEBUG_PRINT on during a quiet day. Frequent
"noise"/"disturber" prints mean raise NOISE_FLOOR/WATCHDOG or move the
module. TUNING_CAP trims the antenna (0–15, module-specific); OUTDOOR
switches AFE gain. Each strike reports with an estimated distance, and the
monitor uses them to thunder-label vibration alerts
(Configuration: LightningLabelSeconds).
Backhaul modes¶
- WiFi → MQTT (
OUTPUT_SERIAL 0, default): the sensor must be in WiFi range of the broker. Simplest. - Serial → LoRa mesh (
OUTPUT_SERIAL 1): the sketch prints one compact line per event to its UART for a mesh node or relay host to carry. See Off-Grid Backhaul.
Conserving LoRa bandwidth¶
LoRa is a few kbps with legal duty-cycle limits. Three measures, on by default, together cut traffic roughly 10–20×:
- Compact wire format:
AABBCC112233,-64(~16 bytes) instead of JSON (~56); sensor events are smaller still (V,123,K,812,S,9). - On-device whitelist: known MACs never transmit.
- Sensor id dropped on the wire: the relaying mesh node's own address
identifies the sensor; map node id → friendly name at the base with
--sensor-map.
If you still saturate the channel: raise COOLDOWN_MS, tighten RSSI_MIN,
or use a faster Meshtastic modem preset (ShortFast) if range allows.