ADR never sends LinkADRReq despite good SNR margin (stuck at DR0/SF12 for months) - ChirpStack v4.16.2

Viewed 58

Hi all,

I have a self-hosted ChirpStack v4.16.2 setup (Docker), EU868 region, with 4 Dragino LHT65-E3 sensors (OTAA, MAC version LoRaWAN 1.0.4, Regional parameters revision RP002-1.0.3). Device profile has "Default ADR algorithm (LoRa only)" assigned.

Symptom: all 4 devices have been stuck at DR0 (SF12) for months, despite:

  • Uplinks carrying adr=true
  • Comfortable SNR margin (observed SNR consistently between +6 and +12 dB across all 4 devices, well above what SF7-SF9 would require)
  • ADR algorithm correctly assigned in the device profile

What I've checked so far:

  • Device profile "ADR algorithm" field is set to "Default ADR algorithm (LoRa only)" (not disabled).
  • Raised log level to "debug" in chirpstack.toml and restarted the container.
  • Captured multiple full uplink/downlink cycles in the logs, including one where a downlink was actually sent (carrying a DevStatusReq MAC command). In every case, I see uplink processing, metrics being saved, and (when applicable) the DevStatusReq downlink — but never any log line referencing ADR or LinkADRReq, at any log level.
  • Devices do occasionally rejoin (OTAA), roughly every 1-2 weeks based on the "Errors" panel in the UI, but even between rejoins there are many days (and thousands of uplinks) of continuous operation without any DR change.

Given the SNR margin, I'd expect the default ADR algorithm to compute several possible steps up (per the (link_budget - installation_margin)/3 formula I found referenced in the docs/forum), yet nothing seems to happen at all - not even a decision not to act that gets logged.

Questions:

  1. Is there a known minimum number of consecutive/qualifying uplinks (with no gaps in fCnt) required before the default ADR algorithm acts? Does a rejoin reset that history?
  2. Is "installation margin" configurable anywhere in v4 (I don't see it in the Device Profile UI), or is it a fixed internal constant?
  3. Is there a way to get more verbose (debug-level) logging specifically for the ADR decision process? The "debug" log level in chirpstack.toml didn't seem to add any ADR-related output.
  4. Any known issues in v4.16.2 that would prevent LinkADRReq from ever being queued even when the algorithm should trigger it?

Happy to share full log excerpts, device profile config, or Redis key dumps if useful. Thanks in advance!

2 Answers

Please note there is a setting in v4.19 where you can configure the max. mac-command error count. By default ChirpStack will stop sending mac-commands for a device when it reports errors. This was implemented because some do have the ADR flag set to true, but will constantly nACK the LinkADRReq, causing ChirpStack to keep retrying the LinkADRReq. It could be that this is the case with you device.

You could try upgrading to v4.19.0 and set this config to a higher value than 1. Before v4.19.0 this value was not configurable and had a default of 1. See https://github.com/chirpstack/chirpstack/pull/955.

Hi Orne, thank you for the pointer — that was it. We were still on 4.16.2, where max_mac_command_error_count wasn't configurable yet (fixed at 1), so it's very likely our device NACKed a LinkADRReq at some point in the past and ChirpStack silently stopped trying ADR for it ever since.

We've upgraded to 4.19.2 and set max_mac_command_error_count=3. We'll monitor the device over the next few days and report back whether it finally moves off SF12. Thanks again for the quick and precise help!

Hi Orne,

Following up on your diagnosis (thank you again) — here's what we did and what we're seeing now.

We upgraded ChirpStack from 4.16.2 to 4.19.2 and set max_mac_command_error_count=3 in the [network] section, as you suggested.

Before the fix: over the ~5 days since the device got stuck (its downlink frame counter was at 17, all sent early in that same session), ChirpStack sent zero further downlinks to it, despite the device requesting ADR (adr bit set) and consistently good SNR on every uplink (ranging 6.5–13.2 dB).

We then forced a fresh OTAA rejoin on the device (physical reset button) to start a clean session. Immediately after — 1 second after the first uplink of the new session — ChirpStack did send a downlink: a bundle with LinkADRReq (dr:0, tx_power:5), DevStatusReq and RxParamSetupReq. The device acknowledged all of it correctly (LinkADRAns with ch_mask_ack/dr_ack/tx_power_ack all true, DevStatusAns battery 254/margin 6, RxParamSetupAns with rx2_dr_ack/rx1_dr_offset_ack true).

However, since that single exchange, over the following ~16.5 hours and 66 further uplinks — all with SNR consistently between 6 and 13 dB, well above what's needed to move off SF12/DR0 — ChirpStack hasn't sent any further downlink or ADR mac command at all.

Is that initial post-join exchange a one-time session-initialization step, separate from the ongoing per-uplink ADR evaluation? If so, why would the "Default ADR algorithm (LoRa only)" remain completely inactive afterward despite this much SNR margin on every subsequent uplink? Happy to share raw frame logs if useful.

Thanks again for your help with this.