Tune¶
Out of the box, DAPPS picks defaults that are safe on a low-traffic VHF FM port. If you're running on something faster (full-time IP backbone, fast Ethernet between two co-located nodes) you can probably leave most knobs alone. If you're on something slower, smaller, or shared with other operators (1200-baud VHF in a busy area, HF NVIS, satellite), the knobs in this section let you back off.
Your node's radio port¶
On a radio link, how quickly mail moves depends more on your packet node's radio-port settings than on anything in DAPPS. These are the BPQ settings that matter, measured with two DAPPS nodes on a simulated 2 m FM channel (net-sim):
| BPQ setting | AFSK 1200 | QPSK 3600 (pdn-soundmodem) | Why |
|---|---|---|---|
FRACK |
7000 | 4000 | Longer than your longest burst plus the answer (below). BPQ starts this timer when it hands a burst to the TNC, not when the burst has gone, so if it's too short BPQ polls the far end while it is still answering, and both are lost. |
RESPTIME |
1000 | 1000 | BPQ's default. BPQ asks for an answer at the end of every burst anyway. A longer one saves the odd RR on a clean link, but BPQ also waits this long (3 s at least) before asking for a lost frame again. |
MAXFRAME |
4 | 7 | Frames per burst. |
PACLEN |
120 | 236 | Bytes per frame. On a marginal link, shorter frames are lost less often. |
PERSIST, SLOTTIME |
64, 100 | 64, 100 | The usual values, even on a link only your two nodes use. With 255, two nodes whose calls start together collide, and BPQ repeats both a FRACK apart, still together, until they give up. |
TXDELAY |
As short as your radios allow; 300 or more on a weak link | As short as your radios allow | Every transmission pays it. Near the edge of range, a radio that has just stopped transmitting misses the start of what comes straight back: in our tests a third of those frames were lost with 150 ms, and a quarter with 300 ms. |
With these settings, a test exchange of 8 posts and their acks between two nodes took 42 to 46 s at 1200 baud and 22 to 27 s at QPSK 3600, on one connection.
The rule behind FRACK: a burst of MAXFRAME frames of PACLEN bytes takes about MAXFRAME x (PACLEN + 20) x 8 / bit rate seconds on air. At 1200 baud, 4 frames of 120 bytes take about 4 s; at 7200 bps (QPSK 3600), 7 frames of 236 bytes about 2 s. Set FRACK 2 to 3 s above that, and raise it if you raise MAXFRAME or PACLEN.
If your TNC supports it, KISSOPTIONS=ACKMODE has the TNC tell BPQ when each frame has actually gone, so FRACK runs from then. pdn-soundmodem supports it; Dire Wolf doesn't. With FRACK set as above it made no difference.
Airtime budget¶
The single biggest knob. By default DAPPS imposes no cap on discovery transmissions - beacons go out on their per-channel cadence, probes fire on their interval, solicit replies happen whenever a solicit arrives. If you turn discovery features on without thinking about airtime, this can be a lot.
This caps all discovery-class transmissions (beacons + solicits + probes) at a trailing-hour total. When the cap would be exceeded, beacons reschedule a quarter-cadence later, probe sweeps stop early and resume next sweep, solicit replies are deferred. The dashboard's Discovery channels heading shows current consumption against the cap with a warning at ≥ 90 %.
There's also a per-channel cap, set on the discovery channel row itself (not via env var). When both global and per-channel caps are set, a transmission must fit under both. Use this when a single shared HF channel needs to be capped tighter than your overall budget.
Operator-triggered probes from the REST surface bypass the budget - that's an explicit human action on a single callsign and rate-limiting it would be hostile to the "I'm debugging a peer right now" path.
Probe strategies¶
Probing is off by default. Once you enable it (DAPPS_PROBING_ENABLED=true), a strategy decides when to actually run a sweep:
| Strategy | When it fires |
|---|---|
FixedInterval |
Every DAPPS_PROBE_INTERVAL_HOURS hours. The simplest. Same cadence regardless of context. |
Overnight |
Once per local-time day inside the [probe-overnight-start-hour, probe-overnight-end-hour) window. Default 02:00 to 06:00. Wraps midnight if end < start. Good for shared bands where you want to be quiet during peak hours. |
WhenQuiet |
Fixed cadence, but defers each tick if the forwarder has seen activity in the last probe-quiet-window-seconds (default 5 minutes). Good for nodes where probe traffic shouldn't compete with real traffic. |
Switch strategy via DAPPS_PROBE_STRATEGY=Overnight (case-insensitive) or the /Config form.
Fragment threshold¶
Payloads strictly larger than DAPPS_FRAGMENT_THRESHOLD_BYTES get split into N fragments at submit, each delivered independently and reassembled at the receiver. Default is 4096 bytes - sized for VHF. Bump it higher (or set 0 to disable) on faster, more reliable bearers; drop it lower if your bearer's MTU is smaller than 4 KiB.
The dashboard's outbound queue shows fragment status; the master ID groups the fragments back together for the sender.
Reassembly buffers age out after 7 days by default (DAPPS_FRAGMENT_REASSEMBLY_TIMEOUT_SECONDS). Drop this if you're storage-constrained; raise it if you have peers that go offline for long stretches.
Routing algorithm¶
Two choices, switched with DAPPS_ROUTING_ALGORITHM (restart required):
passive-flood(default) - AODV-style: routes are learned from observed forwards. Cheap, no extra airtime, works well on small meshes.meshcore- DSR-style source routing inspired by MeshCore's algorithm: the sender stamps the path on the message, intermediate nodes follow it. Adds a few bytes per message; works better when the topology is volatile or you want explicit per-message path control.
For most setups, leave passive-flood on. Switch to meshcore if you have a reason - for instance, if you're emulating MeshCore-on-AX.25 to test the bearer integration shape, or if you're seeing forwarding loops on a complex topology that the passive learner is mis-handling.
Polling¶
Every session with a neighbour carries traffic both ways, whoever dialled, so mail usually rides a call one end made for its own traffic. Two knobs:
- Link hold (
DAPPS_SESSION_TAIL_SECONDS, default 120) - keep the link to a neighbour open until it has been quiet this long, so a follow-up in either direction goes at once instead of after a new connect. The link is held for the lower of both ends' values. Worth raising on a quiet point-to-point link where messages come in conversations; lower it, or set 0 for that neighbour on the Topology page, where a node is short of circuits or drops idle ones sooner. - Scheduled poll (
DAPPS_SCHEDULED_POLL_ENABLED, default false) - periodically call every neighbour, just so it can send what it holds for us. Use this where peers can't reach you reliably (asymmetric link, peer behind NAT, peer that doesn't know our bearer hint), or on a node that rarely sends anything itself.
Heartbeat cadence¶
DAPPS_HEARTBEAT_INTERVAL_SECONDS (≥ 10 s, default 60 s) controls how often the heartbeat is published to MQTT. On a quiet node, 60 s is fine. If you're scraping the heartbeat into a high-resolution monitoring system, drop to 10 to 30 s; if you're storage-constrained on the MQTT broker side and only want trends, raise to 300 s.
The MQTT message is retained so a late subscriber gets the most recent snapshot immediately - cadence is about freshness for active subscribers, not "did I miss it."
What not to tune blindly¶
- Don't lower
fragment-reassembly-timeout-secondsbelow a day unless you're really sure no peer of yours will ever go off the air for that long. The cost of a longer timeout is a few KB of buffer; the cost of a too-short timeout is silent message loss. - Don't raise
probe-interval-hoursvery high while keeping probing on - long intervals plus stale routing data is a recipe for confidently sending to peers that have moved on. - Don't disable
update-check-enabledunless your node really has no internet - the cost is one HTTPS request per hour and the win is knowing when there's a fix. - Don't disable the heartbeat publisher if you're using it for monitoring - there's no "just-once" knob, and disabling the loop means losing the entire scrape source.