Skip to content

External ADR (mt-adr)

Model catalog

Search for these models in ThinkLink by name or ID.

Model typeNameid_namePlatform model ID
RPCExternal ADR Configmt_adr_config118745136434671616
RPCExternal ADR Enginemt_adr_engine118745135679696896
TriggerExternal ADR Trigger (generic)mt_adr_trigger118745137390972928

Operations Skill

Use mt-adr-operations to configure ADR, analyze trigger gates and SNR/DR decisions, or diagnose LinkADRReq downlinks. Confirm the regional band and ADR owner first, then verify the actual DR on later uplinks.

Supported platform version: ThinkLink ≥ 2.00.031 | Last updated: 2026-06-14


What it is

The ThinkLink platform's Network Server (NS) does not perform server-side ADR (Adaptive Data Rate). This capability externalizes an ADR engine at the application layer: each uplink fires a trigger → margin algorithm → MAC downlink (LinkADRReq) feedback loop that automatically adjusts the data rate (DR/SF) for LoRaWAN devices.

  • Only DR/SF is adjusted. TX power and NbTrans are left unchanged.
  • Multi-band support: CN470 / EU868 / US915 (see Notes for US915 caveats).
  • Engine state is stored in server_attrs.adr_ext.state on the device — no persistent schema changes are required on the platform side.

Components

ArtifactIdentifierRole
Triggermt_adr_triggerUplink hook + gate: filters invalid frames and invokes the engine RPC
Engine RPCmt_adr_engineMargin algorithm + sliding-window state + generates the MAC downlink
Config RPCmt_adr_configWrites server_attrs.adr_ext (enable flag + parameters)

Enabling steps

  1. Verify prerequisites: the thing model bound to the target device must expose snr, adr, and datr in its telemetry (see next section).

  2. Configure parameters: on the device detail page, execute the mt_adr_config RPC and fill in the following parameters:

    ParameterDescriptionDefault / Recommended
    enabledEnable the ADR enginefalse (set to true to activate)
    margin_dbSafety margin (dB) — conservative buffer before stepping up the DR10
    window_sizeSliding window size (frames); max SNR is taken over this many frames20
    min_samplesMinimum frames to collect before making any decision8
    min_interval_framesMinimum frames between consecutive DR adjustments (debounce)5
    bandFrequency band: CN470 / EU868 / US915Match deployment band (default CN470)
    chmaskChannel mask (16-bit; all-on = 65535)65535
    chmask_cntlChMaskCntl (EU868/CN470=6 all-on; US915 125 kHz=6)6
  3. Bind thing models (deferred to owner): add the target thing model IDs to the trigger's thing_model_ids. Until bound, the trigger remains completely silent for that thing model.


Prerequisite: thing model must expose three fields

The payload_parser of the device's bound thing model must write snr, adr, and datr into the telemetry object.

  • Setting frameInfo.rssi = true already injects snr (and rssi) automatically.
  • adr and datr must be explicitly extracted from the uplink userdata.motetx.

Add the following snippet to the payload_parser function body of the relevant thing model:

js
if (this.msg?.userdata?.motetx) {
  this.tdata.adr  = this.msg.userdata.motetx.adr === true;  // uplink ADR bit
  this.tdata.datr = this.msg.userdata.motetx.datr;          // e.g. "SF7BW125"
}

After updating the thing model, push it to the platform and confirm that adr and datr appear in live telemetry before proceeding to step 2.


Gating (when it does nothing)

If any of the following conditions is true, the trigger returns null immediately and the engine does not run:

ConditionReason
server_attrs.adr_ext.enabled !== trueADR not enabled via the config RPC
Uplink adr !== trueThe device has not set its ADR bit — the NS requires device consent before sending ADR commands
Telemetry missing snr or datrThe thing model prerequisite is not met; the algorithm cannot proceed

Algorithm

On each valid uplink the engine updates the sliding window and makes a DR decision as follows:

  1. Accumulate samples: collect SNR values for the last window_size frames. No decision is made until at least min_samples frames have been seen.

  2. Compute margin:

    maxSNR   = max(SNR over last window_size frames)
    margin   = maxSNR − requiredSNR(currentDR) − margin_db
    drStep   = floor(margin / 3)
  3. requiredSNR reference values (demodulation floor per SF, in dB):

    SF / DRrequiredSNR (dB)
    SF7−7.5
    SF8−10.0
    SF9−12.5
    SF10−15.0
    SF11−17.5
    SF12−20.0
  4. Debounce: if fewer than min_interval_frames frames have elapsed since the last DR change, skip the current decision.

  5. Apply: drStep > 0 → step DR up; drStep < 0 → step DR down; drStep == 0 → keep current DR.


When a decision is made the engine RPC returns a thinkOne action that causes the platform to send a LinkADRReq FOpts frame to the device:

json
{
  "type": "mac",
  "userdata": {
    "port": -1,
    "payload": "0x03..."
  }
}
  • port: -1 signals a MAC command (FOpts layer); no application port is consumed.
  • payload is the hex-encoded LinkADRReq byte string. The NS handles encryption and framing automatically — no additional handling is required at the application layer.

Notes

Channel mask overwrites the existing channel plan (CN470 / EU868 / US915)

Every LinkADRReq carries ChMask (a mandatory command field), and the engine sends all-on by default (ChMask=0xFFFF, ChMaskCntl=6). This means each DR adjustment resets the device's channel plan to all-on, overwriting any custom channel mask the operator previously pushed. If your deployment customizes channels (fixed sub-band, avoiding interfered channels), set chmask/chmask_cntl explicitly via mt_adr_config to match the live channel plan so the engine does not overwrite it with all-on. The config RPC already rejects chmask=0 (which would disable all channels and brick the device).

US915 channel mask

US915 channel-mask paging is non-trivial: ChMaskCntl values 0–5 each govern a different 125 kHz sub-band; ChMaskCntl=6 enables all 125 kHz channels; ChMaskCntl=7 enables all 500 kHz channels. The current v1 implementation treats US915 as "all 125 kHz channels enabled" (ChMaskCntl=6, ChMask=0xFFFF), which is correct when a standard 8-channel gateway covers every sub-band. Before deploying in a US915 environment, verify the channel plan with your gateway/NS operator to confirm the mask matches the actual channel configuration. An incorrect mask can cause devices to stop receiving downlinks. Note: the engine now resolves the current DR by exact SF + bandwidth match (BUG-035 fixed 2026-06-15), so US915 SF8BW500 (DR4) vs SF8BW125 (DR2) and EU868 SF7BW250 (DR6) vs SF7BW125 (DR5) are correctly distinguished — the earlier "SF-only mis-mapping" no longer occurs; still limit dr_min/dr_max to the DR range your gateway supports.

The engine does not track LinkADRAns (fire-and-forget)

After sending a LinkADRReq, the engine does not parse the device's LinkADRAns (PowerACK/DataRateACK/ChannelMaskACK status bits), so it has no explicit awareness of a device NACK. It currently relies on self-correction ("next frame datr unchanged → margin still satisfied → retry"), which is acceptable but not robust. A strict closed loop (echo the three ACK bits + NACK backoff/alert) is a future enhancement. state.last_dr records the last target DR the engine commanded and is kept for diagnostics only (it does not drive decisions — curDR is always computed fresh from the current frame's datr).

Do not hand-edit the state field

server_attrs.adr_ext.state is owned entirely by the engine (sliding window buffer, frame counter since last adjustment, etc.). Operators must not directly modify this field via the platform UI or any script. Doing so will corrupt the debounce logic or put the state machine out of sync. To reset the engine, re-execute mt_adr_config (omitting the state key); the engine will reinitialize with a clean state on the next uplink.

Binding deferred to owner

The trigger mt_adr_trigger is deployed with thing_model_ids intentionally left empty. The owner is responsible for binding the appropriate thing model IDs based on the actual deployment scope. Until a thing model is bound, the trigger is completely inert for devices using that model — no DR commands will be issued.