* Added NodeDB fixtures and refactored to use std maps for better efficiency * Defer NodeDB save during xmodem transfer to prevent mid-transfer fsFormat
Fake NodeDB Fixtures
Deterministic JSONL seed files for the v25 meshtastic_NodeDatabase format
plus tooling that compiles them to binary .proto files and pushes them
onto a device for testing.
Centered on Truth or Consequences, NM (33.1284°N, 107.2528°W) with a 60 km
spread — change via --centroid and --spread-km if you want a different
geography.
Pipeline
bin/gen-fake-nodedb-seed.py
↓ (single Random(seed); no wall-clock dependence)
test/fixtures/nodedb/seed_v25_<N>.jsonl ← committed, hand-editable
↓
bin/seed-json-to-proto.py
↓ (resolves *_offset_sec → now-relative epochs at compile time)
build/fixtures/nodedb/nodes_v25_<N>.proto ← .gitignored, fresh timestamps
↓
- Portduino: cp to ~/.portduino/<config>/prefs/nodes.proto
- Hardware: XModem upload via mcp-server's push_fake_nodedb tool
What's committed
| File | Size | Purpose |
|---|---|---|
seed_v25_0250.jsonl |
~200 KB | Matches ESP32-S3 high-flash MAX_NUM_NODES cap |
seed_v25_0500.jsonl |
~400 KB | Stress between caps |
seed_v25_1000.jsonl |
~800 KB | Large mesh stress |
seed_v25_2000.jsonl |
~1.6 MB | Truncation/eviction stress (exceeds every platform cap) |
Determinism contract
Structural fields are deterministic given a fixed --seed: NodeNum,
long_name, short_name, hw_model, role, public_key, snr, channel, hops_away,
next_hop, bitfield flags, latitude/longitude/altitude, all
DeviceMetrics/EnvironmentMetrics/StatusMessage values.
Timestamps are intentionally non-deterministic at compile time. The JSONL
stores *_offset_sec (seconds before "now"); the compile step subtracts these
from current wall clock so the loaded NodeDB shows fresh "recently heard"
peers regardless of when the fixture was generated. Pass --now-epoch T to
the compile step to pin it for byte-identical CI artifacts.
Active-board allow-list
hw_model values are restricted to the intersection of:
- Variants with
custom_meshtastic_support_level = 1invariants/*/*/platformio.ini - Values present in the
HardwareModelenum inmesh.proto
This excludes legacy/deprecated boards (Heltec V1–V2, TLORA V1–V2, classic TBEAM (4) and TBEAM_V0P7 (6), Nano G1, Station G1/G2, etc.) and fuzzer-only sentinels (PORTDUINO, ANDROID_SIM, DIY_V1, LORA_RELAY_V1, etc.).
Refresh the allow-list in bin/gen-fake-nodedb-seed.py:HW_MODEL_WEIGHTS when
boards graduate to tier-1 (or retire). One-liner to print the current
intersection:
for f in $(find variants -name 'platformio.ini' | xargs grep -lE 'custom_meshtastic_support_level = 1'); do
grep custom_meshtastic_hw_model_slug "$f" | awk -F= '{print $2}' | tr -d ' '
done | sort -u | comm -12 - <(
bin/_generated/meshtastic_v25/__init__.py >/dev/null 2>&1 || ./bin/regen-py-protos.sh >&2
python3 -c "import sys; sys.path.insert(0,'bin/_generated'); \
from meshtastic_v25.mesh_pb2 import HardwareModel; \
print('\n'.join(HardwareModel.keys()))" | sort
)
Role allow-list
role is drawn from non-deprecated Config.DeviceConfig.Role values:
- Excluded:
ROUTER_CLIENT(deprecated v2.3.15),REPEATER(deprecated v2.7.11) - Active: CLIENT, CLIENT_MUTE, ROUTER, TRACKER, SENSOR, TAK, CLIENT_HIDDEN, LOST_AND_FOUND, TAK_TRACKER, ROUTER_LATE, CLIENT_BASE
Quickstart
Regenerate fixtures with fresh timestamps
./bin/regen-fake-nodedbs.sh
This recompiles all four .proto outputs into build/fixtures/nodedb/ from
the committed JSONL seeds, using current wall clock for timestamps. Re-run
whenever you want "recent-looking" cached state on a freshly-booted device.
Bump the seed (regenerate JSONL structure)
REGEN_SEEDS=yes ./bin/regen-fake-nodedbs.sh
This overwrites the committed JSONL files. Commit the result.
Hand-edit a specific scenario
# Find the node you want to tweak, edit the line in place.
$EDITOR test/fixtures/nodedb/seed_v25_0250.jsonl
# Recompile and push.
./bin/regen-fake-nodedbs.sh
Each line of the JSONL is one node + metadata as the first line. Field schema
documented inline in bin/gen-fake-nodedb-seed.py. To override a specific
timestamp, replace the last_heard_offset_sec field with last_heard (an
absolute epoch); the compile step honors absolute values.
Load onto Portduino (native macOS / linux)
cp build/fixtures/nodedb/nodes_v25_1000.proto ~/.portduino/default/prefs/nodes.proto
# Run the native binary; loadFromDisk picks it up at boot.
Push to USB-attached hardware via mcp-server
# From within the mcp-server tool surface:
push_fake_nodedb(
size=500,
target="hardware",
port="/dev/cu.usbmodem21301", # discover via list_devices
confirm=True, # gates the destructive write + reboot
)
Streams the proto over XModem to /prefs/nodes.proto, then issues a 1-second
reboot so loadFromDisk picks it up on next boot. CRC16-CCITT-validated
chunks; retries each chunk up to 5× on NAK before aborting with CAN.
Schema reference
See bin/gen-fake-nodedb-seed.py for the JSONL field reference. Key points:
numis a hex string ("0xa1b2c3d4")public_key_hexis 64 hex chars (32 bytes), empty for keyless nodeshw_modelandroleare enum names; the compile step resolves them viaHardwareModel.Value(name)/Config.DeviceConfig.Role.Value(name)bitfieldis a struct of named booleans; the compile step packs them per the bit positions insrc/mesh/NodeDB.h:467-484position/telemetry/environment/statusare nullable; coverage ratios at seed time decide which nodes get whichlatitude/longitudeare floats in degrees (compiled tolatitude_i = int(lat * 1e7)matchingmeshtastic_PositionLite)