-
Notifications
You must be signed in to change notification settings - Fork 225
Description
Hello,
As part of my testing as part of #1010, I've found that for every "Unconfirmed uplink", there is an "Unconfirmed downlink" sent by the ChirpStack network server:
Reviewing the LoRaWAN Specification V1.0.3, I understand that LinkADRReq
sent by the network shall be acknowledged by the end device with a LinkADRAns
in the following uplink, sent in the FOpts (f_opts)
field. This does not occur, with the FOpts
field being empty (see "f_opts": []
below).
My code is ttn_otaa.ino
example with LMIC_selectSubBand(1);
added just before do_send(&sendjob);
. There are no other changes:
/*******************************************************************************
* Copyright (c) 2015 Thomas Telkamp and Matthijs Kooijman
* Copyright (c) 2018 Terry Moore, MCCI
*
* Permission is hereby granted, free of charge, to anyone
* obtaining a copy of this document and accompanying files,
* to do whatever they want with them without any restriction,
* including, but not limited to, copying, modification and redistribution.
* NO WARRANTY OF ANY KIND IS PROVIDED.
*
* This example sends a valid LoRaWAN packet with payload "Hello,
* world!", using frequency and encryption settings matching those of
* the The Things Network.
*
* This uses OTAA (Over-the-air activation), where where a DevEUI and
* application key is configured, which are used in an over-the-air
* activation procedure where a DevAddr and session keys are
* assigned/generated for use with all further communication.
*
* Note: LoRaWAN per sub-band duty-cycle limitation is enforced (1% in
* g1, 0.1% in g2), but not the TTN fair usage policy (which is probably
* violated by this sketch when left running for longer)!
* To use this sketch, first register your application and device with
* the things network, to set or generate an AppEUI, DevEUI and AppKey.
* Multiple devices can use the same AppEUI, but each device has its own
* DevEUI and AppKey.
*
* Do not forget to define the radio type correctly in
* arduino-lmic/project_config/lmic_project_config.h or from your BOARDS.txt.
*
*******************************************************************************/
#include <lmic.h>
#include <hal/hal.h>
#include <SPI.h>
//
// For normal use, we require that you edit the sketch to replace FILLMEIN
// with values assigned by the TTN console. However, for regression tests,
// we want to be able to compile these scripts. The regression tests define
// COMPILE_REGRESSION_TEST, and in that case we define FILLMEIN to a non-
// working but innocuous value.
//
#ifdef COMPILE_REGRESSION_TEST
# define FILLMEIN 0
#else
# warning "You must replace the values marked FILLMEIN with real values from the TTN control panel!"
# define FILLMEIN (#dont edit this, edit the lines that use FILLMEIN)
#endif
// This EUI must be in little-endian format, so least-significant-byte
// first. When copying an EUI from ttnctl output, this means to reverse
// the bytes. For TTN issued EUIs the last bytes should be 0xD5, 0xB3,
// 0x70.
static const u1_t PROGMEM APPEUI[8]={ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
void os_getArtEui (u1_t* buf) { memcpy_P(buf, APPEUI, 8);}
// This should also be in little endian format, see above.
static const u1_t PROGMEM DEVEUI[8]={ 0xE7, 0x0B, 0x07, 0xD0, 0x7E, 0xD5, 0xB3, 0x70 };
void os_getDevEui (u1_t* buf) { memcpy_P(buf, DEVEUI, 8);}
// This key should be in big endian format (or, since it is not really a
// number but a block of memory, endianness does not really apply). In
// practice, a key taken from ttnctl can be copied as-is.
static const u1_t PROGMEM APPKEY[16] = { 0x13, 0x4A, 0xAA, 0xC4, 0x6F, 0xCF, 0x1A, 0x8B, 0x58, 0x92, 0xD9, 0x13, 0x32, 0xB6, 0x37, 0xC7 };
void os_getDevKey (u1_t* buf) { memcpy_P(buf, APPKEY, 16);}
static uint8_t mydata[] = "Hello, world!";
static osjob_t sendjob;
// Schedule TX every this many seconds (might become longer due to duty
// cycle limitations).
const unsigned TX_INTERVAL = 10;
// Pin mapping
const lmic_pinmap lmic_pins = {
.nss = 18,
.rxtx = LMIC_UNUSED_PIN,
.rst = 14,
.dio = {26, 33, 32}
};
void printHex2(unsigned v) {
v &= 0xff;
if (v < 16)
Serial.print('0');
Serial.print(v, HEX);
}
void onEvent (ev_t ev) {
Serial.print(os_getTime());
Serial.print(": ");
switch(ev) {
case EV_SCAN_TIMEOUT:
Serial.println(F("EV_SCAN_TIMEOUT"));
break;
case EV_BEACON_FOUND:
Serial.println(F("EV_BEACON_FOUND"));
break;
case EV_BEACON_MISSED:
Serial.println(F("EV_BEACON_MISSED"));
break;
case EV_BEACON_TRACKED:
Serial.println(F("EV_BEACON_TRACKED"));
break;
case EV_JOINING:
Serial.println(F("EV_JOINING"));
break;
case EV_JOINED:
Serial.println(F("EV_JOINED"));
{
u4_t netid = 0;
devaddr_t devaddr = 0;
u1_t nwkKey[16];
u1_t artKey[16];
LMIC_getSessionKeys(&netid, &devaddr, nwkKey, artKey);
Serial.print("netid: ");
Serial.println(netid, DEC);
Serial.print("devaddr: ");
Serial.println(devaddr, HEX);
Serial.print("AppSKey: ");
for (size_t i=0; i<sizeof(artKey); ++i) {
if (i != 0)
Serial.print("-");
printHex2(artKey[i]);
}
Serial.println("");
Serial.print("NwkSKey: ");
for (size_t i=0; i<sizeof(nwkKey); ++i) {
if (i != 0)
Serial.print("-");
printHex2(nwkKey[i]);
}
Serial.println();
}
// Disable link check validation (automatically enabled
// during join, but because slow data rates change max TX
// size, we don't use it in this example.
LMIC_setLinkCheckMode(0);
break;
/*
|| This event is defined but not used in the code. No
|| point in wasting codespace on it.
||
|| case EV_RFU1:
|| Serial.println(F("EV_RFU1"));
|| break;
*/
case EV_JOIN_FAILED:
Serial.println(F("EV_JOIN_FAILED"));
break;
case EV_REJOIN_FAILED:
Serial.println(F("EV_REJOIN_FAILED"));
break;
case EV_TXCOMPLETE:
Serial.println(F("EV_TXCOMPLETE (includes waiting for RX windows)"));
if (LMIC.txrxFlags & TXRX_ACK)
Serial.println(F("Received ack"));
if (LMIC.dataLen) {
Serial.print(F("Received "));
Serial.print(LMIC.dataLen);
Serial.println(F(" bytes of payload"));
}
// Schedule next transmission
os_setTimedCallback(&sendjob, os_getTime()+sec2osticks(TX_INTERVAL), do_send);
break;
case EV_LOST_TSYNC:
Serial.println(F("EV_LOST_TSYNC"));
break;
case EV_RESET:
Serial.println(F("EV_RESET"));
break;
case EV_RXCOMPLETE:
// data received in ping slot
Serial.println(F("EV_RXCOMPLETE"));
break;
case EV_LINK_DEAD:
Serial.println(F("EV_LINK_DEAD"));
break;
case EV_LINK_ALIVE:
Serial.println(F("EV_LINK_ALIVE"));
break;
/*
|| This event is defined but not used in the code. No
|| point in wasting codespace on it.
||
|| case EV_SCAN_FOUND:
|| Serial.println(F("EV_SCAN_FOUND"));
|| break;
*/
case EV_TXSTART:
Serial.println(F("EV_TXSTART"));
break;
case EV_TXCANCELED:
Serial.println(F("EV_TXCANCELED"));
break;
case EV_RXSTART:
/* do not print anything -- it wrecks timing */
break;
case EV_JOIN_TXCOMPLETE:
Serial.println(F("EV_JOIN_TXCOMPLETE: no JoinAccept"));
break;
default:
Serial.print(F("Unknown event: "));
Serial.println((unsigned) ev);
break;
}
}
void do_send(osjob_t* j){
// Check if there is not a current TX/RX job running
if (LMIC.opmode & OP_TXRXPEND) {
Serial.println(F("OP_TXRXPEND, not sending"));
} else {
// Prepare upstream data transmission at the next possible time.
LMIC_setTxData2(1, mydata, sizeof(mydata)-1, 0);
Serial.println(F("Packet queued"));
}
// Next TX is scheduled after TX_COMPLETE event.
}
void setup() {
Serial.begin(9600);
Serial.println(F("Starting"));
#ifdef VCC_ENABLE
// For Pinoccio Scout boards
pinMode(VCC_ENABLE, OUTPUT);
digitalWrite(VCC_ENABLE, HIGH);
delay(1000);
#endif
// LMIC init
os_init();
// Reset the MAC state. Session and pending data transfers will be discarded.
LMIC_reset();
LMIC_selectSubBand(1);
// Start job (sending automatically starts OTAA too)
do_send(&sendjob);
}
void loop() {
os_runloop_once();
}
I have added the LMIC_DEBUG_PRINTF
function for functions relating to LinkADRAns
in lmic.c
to identify what functions are being used (section modified below). Please note that the only change I made was adding { }
for the if statement if (olen == 0) { }
which is required to include LMIC_DEBUG_PRINTF
.
static bit_t
applyAdrRequests(
const uint8_t *opts,
int olen,
u1_t adrAns
) {
LMIC_DEBUG_PRINTF("1!!!");
lmic_saved_adr_state_t initialState;
int const kAdrReqSize = 5;
int oidx;
u1_t p1 = 0;
u1_t p4 = 0;
bit_t response_fit = 1;
bit_t map_ok = 1;
LMICbandplan_saveAdrState(&initialState);
// compute the changes
if (adrAns == (MCMD_LinkADRAns_PowerACK | MCMD_LinkADRAns_DataRateACK | MCMD_LinkADRAns_ChannelACK)) {
LMIC_DEBUG_PRINTF("2!!!");
for (oidx = 0; oidx < olen; oidx += kAdrReqSize) {
// can we advance?
LMIC_DEBUG_PRINTF("3!!!");
if (olen - oidx < kAdrReqSize) {
LMIC_DEBUG_PRINTF("4!!!");
// ignore the malformed one at the end
break;
}
u2_t chmap = os_rlsbf2(&opts[oidx+2]);// list of enabled channels
p1 = opts[oidx+1]; // txpow + DR, in case last
p4 = opts[oidx+4]; // ChMaskCtl, NbTrans
u1_t chpage = p4 & MCMD_LinkADRReq_Redundancy_ChMaskCntl_MASK; // channel page
// notice that we ignore map_ok except on the last setting.
// so LMICbandplan_mapChannels should report failure status, but do
// the work; if it fails, we'll back it out.
map_ok = LMICbandplan_mapChannels(chpage, chmap);
LMICOS_logEventUint32("applyAdrRequests: mapChannels", ((u4_t)chpage << 16)|(chmap << 0));
LMIC_DEBUG_PRINTF("5!!!");
}
}
if (! map_ok) {
adrAns &= ~MCMD_LinkADRAns_ChannelACK;
LMIC_DEBUG_PRINTF("6!!!");
}
// p1 now has txpow + DR. DR must be feasible.
dr_t dr = (dr_t)(p1>>MCMD_LinkADRReq_DR_SHIFT);
if (adrAns == (MCMD_LinkADRAns_PowerACK | MCMD_LinkADRAns_DataRateACK | MCMD_LinkADRAns_ChannelACK) && ! LMICbandplan_isDataRateFeasible(dr)) {
adrAns &= ~MCMD_LinkADRAns_DataRateACK;
LMICOS_logEventUint32("applyAdrRequests: final DR not feasible", dr);
LMIC_DEBUG_PRINTF("7!!!");
}
if (adrAns != (MCMD_LinkADRAns_PowerACK | MCMD_LinkADRAns_DataRateACK | MCMD_LinkADRAns_ChannelACK)) {
LMICbandplan_restoreAdrState(&initialState);
LMIC_DEBUG_PRINTF("8!!!");
}
// now put all the options
for (oidx = 0; oidx < olen && response_fit; oidx += kAdrReqSize) {
// can we advance?
LMIC_DEBUG_PRINTF("9!!!");
if (olen - oidx < kAdrReqSize) {
// ignore the malformed one at the end
LMIC_DEBUG_PRINTF("10!!!");
break;
}
response_fit = put_mac_uplink_byte2(MCMD_LinkADRAns, adrAns);
LMIC_DEBUG_PRINTF("11!!!");
}
// all done scanning options
bit_t changes = LMICbandplan_compareAdrState(&initialState);
// handle the final options
if (adrAns == (MCMD_LinkADRAns_PowerACK | MCMD_LinkADRAns_DataRateACK | MCMD_LinkADRAns_ChannelACK)) {
// handle uplink repeat count
LMIC_DEBUG_PRINTF("12!!!");
u1_t uprpt = p4 & MCMD_LinkADRReq_Redundancy_NbTrans_MASK; // up repeat count
if (LMIC.upRepeat != uprpt) {
LMIC.upRepeat = uprpt;
changes = 1;
LMIC_DEBUG_PRINTF("13!!!");
}
LMICOS_logEventUint32("applyAdrRequests: setDrTxPow", ((u4_t)adrAns << 16)|(dr << 8)|(p1 << 0));
// handle power changes here, too.
changes |= setDrTxpow(DRCHG_NWKCMD, dr, pow2dBm(p1));
LMIC_DEBUG_PRINTF("14!!!");
}
// Certification doesn't like this, but it makes the device happier with TTN.
// LMIC.adrChanged = changes; // move the ADR FSM up to "time to request"
LMIC_DEBUG_PRINTF("15!!!");
LMIC_DEBUG_PRINTF("response_fit = %d\n", response_fit);
LMIC_DEBUG_PRINTF("16!!!");
return response_fit;
}
static int
scan_mac_cmds_link_adr(
const uint8_t *opts,
int olen,
bit_t *presponse_fit
)
{
LMICOS_logEventUint32("scan_mac_cmds_link_adr", olen);
LMIC_DEBUG_PRINTF("17!!!");
if (olen == 0) {
LMIC_DEBUG_PRINTF("18!!!");
return 0;
}
int oidx = 0;
int const kAdrReqSize = 5;
int lastOidx;
u1_t adrAns = MCMD_LinkADRAns_PowerACK | MCMD_LinkADRAns_DataRateACK | MCMD_LinkADRAns_ChannelACK;
// process the contiguous slots
for (;;) {
lastOidx = oidx;
LMIC_DEBUG_PRINTF("19!!!");
// can we advance?
if (olen - oidx < kAdrReqSize) {
// ignore the malformed one at the end; but fail it.
LMIC_DEBUG_PRINTF("20!!!");
adrAns = 0;
break;
}
u1_t p1 = opts[oidx+1]; // txpow + DR
u2_t chmap = os_rlsbf2(&opts[oidx+2]);// list of enabled channels
u1_t chpage = opts[oidx+4] & MCMD_LinkADRReq_Redundancy_ChMaskCntl_MASK; // channel page
// u1_t uprpt = opts[oidx+4] & MCMD_LinkADRReq_Redundancy_NbTrans_MASK; // up repeat count
dr_t dr = (dr_t)(p1>>MCMD_LinkADRReq_DR_SHIFT);
if( !LMICbandplan_canMapChannels(chpage, chmap) ) {
adrAns &= ~MCMD_LinkADRAns_ChannelACK;
LMIC_DEBUG_PRINTF("21!!!");
LMICOS_logEventUint32("scan_mac_cmds_link_adr: failed canMapChannels", ((u4_t)chpage << 16)|((u4_t)chmap << 0));
}
if( !validDR(dr) ) {
LMIC_DEBUG_PRINTF("22!!!");
adrAns &= ~MCMD_LinkADRAns_DataRateACK;
}
if (pow2dBm(p1) == -128) {
LMIC_DEBUG_PRINTF("23!!!");
adrAns &= ~MCMD_LinkADRAns_PowerACK;
}
oidx += kAdrReqSize;
if (opts[oidx] != MCMD_LinkADRReq)
LMIC_DEBUG_PRINTF("24!!!");
break;
}
LMIC_DEBUG_PRINTF("25!!!");
// go back and apply the ADR changes, if any -- use the effective length,
// and process.
*presponse_fit = applyAdrRequests(opts, lastOidx + kAdrReqSize, adrAns);
return lastOidx;
}
// scan mac commands starting at opts[] for olen, return count of bytes consumed.
// build response in pendMacData[], but limit length as needed; simply chop at last
// response that fits.
static int
scan_mac_cmds(
const uint8_t *opts,
int olen,
int port
) {
int oidx = 0;
uint8_t cmd;
LMIC.pendMacLen = 0;
if (port == 0) {
// port zero: mac data is in the normal payload, and there can't be
// piggyback mac data.
LMIC.pendMacPiggyback = 0;
} else {
// port is either -1 (no port) or non-zero (piggyback): treat as piggyback.
LMIC.pendMacPiggyback = 1;
}
while( oidx < olen ) {
bit_t response_fit;
response_fit = 1;
cmd = opts[oidx];
/* compute length, and exit for illegal commands */
// cmdlen == 0 for error, or > 0 length of command.
int const cmdlen = getMacCmdSize(cmd);
if (cmdlen <= 0 || cmdlen > olen - oidx) {
// "the first unknown command terminates processing"
olen = oidx;
break;
}
switch( cmd ) {
case MCMD_LinkCheckAns: {
// TODO([email protected]) capture these, reliably..
//int gwmargin = opts[oidx+1];
//int ngws = opts[oidx+2];
break;
}
// from 1.0.3 spec section 5.2:
// For the purpose of configuring the end-device channel mask, the end-device will
// process all contiguous LinkAdrReq messages, in the order present in the downlink message,
// as a single atomic block command. The end-device will accept or reject all Channel Mask
// controls in the contiguous block, and provide consistent Channel Mask ACK status
// indications for each command in the contiguous block in each LinkAdrAns message,
// reflecting the acceptance or rejection of this atomic channel mask setting.
//
// So we need to process all the contigious commands
case MCMD_LinkADRReq: {
// skip over all but the last command.
LMIC_DEBUG_PRINTF("26!!!");
oidx += scan_mac_cmds_link_adr(opts + oidx, olen - oidx, &response_fit);
break;
}
case MCMD_DevStatusReq: {
// LMIC.snr is SNR times 4, convert to real SNR; rounding towards zero.
const int snr = (LMIC.snr + 2) / 4;
// per [1.02] 5.5. the margin is the SNR.
LMIC.devAnsMargin = (u1_t)(0b00111111 & (snr <= -32 ? -32 : snr >= 31 ? 31 : snr));
response_fit = put_mac_uplink_byte3(MCMD_DevStatusAns, os_getBattLevel(), LMIC.devAnsMargin);
break;
}
Here is the serial output with the changes applied:
19:28:17.755 -> ��Starting
19:28:17.902 -> RXMODE_RSSI
19:28:17.933 -> 2757: engineUpdate, opmode=0x8
19:28:17.965 -> Packet queued
19:28:17.965 -> 2784: EV_JOINING
19:28:17.998 -> 2792: engineUpdate, opmode=0xc
19:28:18.029 -> 2812: EV_TXSTART
19:28:18.029 -> 2895: TXMODE, freq=917800000, len=23, SF=10, BW=125, CR=4/5, IH=0
19:28:23.268 -> 337823: setupRx1 txrxFlags 00 --> 01
19:28:23.333 -> start single rx: now-rxtime: 5
19:28:23.365 -> 338456: RXMODE_SINGLE, freq=926300000, SF=10, BW=500, CR=4/5, IH=0
19:28:23.428 -> 345807: Setup channel mask, group=0, mask=ff00
19:28:23.461 -> 345813: Setup channel mask, group=16, mask=0000
19:28:23.524 -> 345818: Setup channel mask, group=32, mask=0000
19:28:23.589 -> 347955: Setup channel mask, group=48, mask=0000
19:28:23.620 -> 351145: Setup channel mask, group=64, mask=0002
19:28:23.685 -> 354344: EV_JOINED
19:28:23.685 -> netid: 0
19:28:23.717 -> devaddr: 1E66E9C
19:28:23.717 -> AppSKey: 02-C6-64-66-56-60-2D-39-58-04-E1-FB-43-DF-D5-BF
19:28:23.782 -> NwkSKey: 94-EA-4B-CE-98-04-E0-A8-7A-B9-F7-2F-65-7D-FA-0C
19:28:23.845 -> 369734: engineUpdate, opmode=0x808
19:28:23.877 -> 369765: EV_TXSTART
19:28:23.910 -> 369846: TXMODE, freq=918200000, len=26, SF=10, BW=125, CR=4/5, IH=0
19:28:25.200 -> 457332: setupRx1 txrxFlags 0x1 --> 01
19:28:25.232 -> start single rx: now-rxtime: 5
19:28:25.265 -> 457966: RXMODE_SINGLE, freq=927500000, SF=10, BW=500, CR=4/5, IH=0
19:28:25.328 -> rxtimeout: entry: 458865 rxtime: 457957 entry-rxtime: 908 now-entry: 5 rxtime-txend: 62375
19:28:26.204 -> 519832: setupRx2 txrxFlags 0x1 --> 02
19:28:26.237 -> start single rx: now-rxtime: 4
19:28:26.269 -> 520465: RXMODE_SINGLE, freq=923300000, SF=12, BW=500, CR=4/5, IH=0
19:28:26.333 -> rxtimeout: entry: 524052 rxtime: 520457 entry-rxtime: 3595 now-entry: 5 rxtime-txend: 124875
19:28:26.429 -> 526650: processRx2DnData txrxFlags 0x2 --> 00
19:28:26.498 -> 529702: processDnData_norx txrxFlags 00 --> 20
19:28:26.529 -> 532827: EV_TXCOMPLETE (includes waiting for RX windows)
19:28:26.594 -> 540515: engineUpdate, opmode=0x900
19:28:36.539 -> 1165514: engineUpdate, opmode=0x908
19:28:36.577 -> 1165541: EV_TXSTART
19:28:36.577 -> 1165622: TXMODE, freq=917000000, len=26, SF=10, BW=125, CR=4/5, IH=0
19:28:36.670 -> Packet queued
19:28:37.918 -> 1253110: setupRx1 txrxFlags 0x20 --> 01
19:28:37.982 -> start single rx: now-rxtime: 4
19:28:38.015 -> 1253742: RXMODE_SINGLE, freq=923900000, SF=10, BW=500, CR=4/5, IH=0
19:28:38.078 -> 26!!!17!!!19!!!24!!!25!!!1!!!2!!!3!!!5!!!9!!!11!!!12!!!13!!!14!!!15!!!response_fit = 1
19:28:38.174 -> 16!!!1259727: decodeFrame txrxFlags 0x1 --> 21
19:28:38.207 -> 1262850: Received downlink, window=RX1, port=-1, ack=0, txrxFlags=0x21
19:28:38.302 -> 1267538: EV_TXCOMPLETE (includes waiting for RX windows)
19:28:38.335 -> 1275227: engineUpdate, opmode=0x910
19:28:38.634 -> 1298162: engineUpdate, opmode=0x110
19:28:38.698 -> 1298180: EV_TXSTART
19:28:38.698 -> 1298253: TXMODE, freq=917400000, len=14, SF=7, BW=125, CR=4/5, IH=0
19:28:39.679 -> 1362908: setupRx1 txrxFlags 0x21 --> 01
19:28:39.710 -> start single rx: now-rxtime: 5
19:28:39.742 -> 1363542: RXMODE_SINGLE, freq=925100000, SF=7, BW=500, CR=4/5, IH=0
19:28:39.838 -> 26!!!17!!!19!!!24!!!25!!!1!!!2!!!3!!!5!!!9!!!11!!!12!!!14!!!15!!!response_fit = 1
19:28:39.903 -> 16!!!1369133: decodeFrame txrxFlags 0x1 --> 21
19:28:39.966 -> 1372257: Received downlink, window=RX1, port=-1, ack=0, txrxFlags=0x21
19:28:40.031 -> 1376945: EV_TXCOMPLETE (includes waiting for RX windows)
19:28:40.094 -> 1384634: engineUpdate, opmode=0x910
19:28:40.396 -> 1407570: engineUpdate, opmode=0x110
19:28:40.428 -> 1407588: EV_TXSTART
19:28:40.460 -> 1407661: TXMODE, freq=916800000, len=14, SF=7, BW=125, CR=4/5, IH=0
19:28:41.443 -> 1472316: setupRx1 txrxFlags 0x21 --> 01
19:28:41.475 -> start single rx: now-rxtime: 5
19:28:41.507 -> 1472949: RXMODE_SINGLE, freq=923300000, SF=7, BW=500, CR=4/5, IH=0
19:28:41.572 -> rxtimeout: entry: 1473177 rxtime: 1472941 entry-rxtime: 236 now-entry: 5 rxtime-txend: 62375
19:28:42.444 -> 1534816: setupRx2 txrxFlags 0x1 --> 02
19:28:42.476 -> start single rx: now-rxtime: 4
19:28:42.510 -> 1535449: RXMODE_SINGLE, freq=923300000, SF=12, BW=500, CR=4/5, IH=0
19:28:42.572 -> rxtimeout: entry: 1539036 rxtime: 1535441 entry-rxtime: 3595 now-entry: 5 rxtime-txend: 124875
19:28:42.669 -> 1541894: processRx2DnData txrxFlags 0x2 --> 00
19:28:42.732 -> 1545010: processDnData_norx txrxFlags 00 --> 20
19:28:42.765 -> 1548200: EV_TXCOMPLETE (includes waiting for RX windows)
19:28:42.828 -> 1555888: engineUpdate, opmode=0x900
Here is the output from the following packets, exported from ChirpStack:
2025-06-22 19:28:37 UnconfirmedDataUp
{
"phy_payload": {
"mhdr": {
"f_type": "UnconfirmedDataUp",
"major": "LoRaWANR1"
},
"mic": [
146,
203,
195,
232
],
"payload": {
"f_port": 1,
"fhdr": {
"devaddr": "01e66e9c",
"f_cnt": 1,
"f_ctrl": {
"ack": false,
"adr": true,
"adr_ack_req": false,
"class_b": false,
"f_opts_len": 0,
"f_pending": false
},
"f_opts": []
},
"frm_payload": "48656c6c6f2c20776f726c6421"
}
},
"rx_info": [
{
"channel": 1,
"context": "sBbHLA==",
"crcStatus": "CRC_OK",
"gatewayId": "c0ee40ffff29630b",
"location": {},
"nsTime": "2025-06-22T09:58:37.563851252+00:00",
"rssi": -71,
"snr": 7.199999809265137,
"uplinkId": 31233
}
],
"tx_info": {
"frequency": 917000000,
"modulation": {
"lora": {
"bandwidth": 125000,
"codeRate": "CR_4_5",
"spreadingFactor": 10
}
}
}
}
2025-06-22 19:28:25 UnconfirmedDataDown
{
"phy_payload": {
"mhdr": {
"f_type": "UnconfirmedDataDown",
"major": "LoRaWANR1"
},
"mic": [
208,
143,
95,
202
],
"payload": {
"f_port": null,
"fhdr": {
"devaddr": "01e66e9c",
"f_cnt": 0,
"f_ctrl": {
"ack": false,
"adr": true,
"adr_ack_req": false,
"class_b": false,
"f_opts_len": 6,
"f_pending": false
},
"f_opts": [
{
"LinkADRReq": {
"ch_mask": [
false,
false,
false,
false,
false,
false,
false,
false,
true,
true,
true,
true,
true,
true,
true,
true
],
"dr": 5,
"redundancy": {
"ch_mask_cntl": 0,
"nb_rep": 1
},
"tx_power": 0
}
},
"DevStatusReq"
]
},
"frm_payload": null
}
},
"tx_info": {
"context": "r1R/JA==",
"frequency": 927500000,
"modulation": {
"lora": {
"bandwidth": 500000,
"codeRate": "CR_4_5",
"polarizationInversion": true,
"spreadingFactor": 10
}
},
"power": 30,
"timing": {
"delay": {
"delay": "1s"
}
}
}
}
2025-06-22 19:28:25 UnconfirmedDataUp
{
"phy_payload": {
"mhdr": {
"f_type": "UnconfirmedDataUp",
"major": "LoRaWANR1"
},
"mic": [
44,
211,
189,
59
],
"payload": {
"f_port": 1,
"fhdr": {
"devaddr": "01e66e9c",
"f_cnt": 0,
"f_ctrl": {
"ack": false,
"adr": true,
"adr_ack_req": false,
"class_b": false,
"f_opts_len": 0,
"f_pending": false
},
"f_opts": []
},
"frm_payload": "48656c6c6f2c20776f726c6421"
}
},
"rx_info": [
{
"channel": 7,
"context": "r1R/JA==",
"crcStatus": "CRC_OK",
"gatewayId": "c0ee40ffff29630b",
"location": {},
"nsTime": "2025-06-22T09:58:24.831631621+00:00",
"rfChain": 1,
"rssi": -91,
"snr": 6.800000190734863,
"uplinkId": 58461
}
],
"tx_info": {
"frequency": 918200000,
"modulation": {
"lora": {
"bandwidth": 125000,
"codeRate": "CR_4_5",
"spreadingFactor": 10
}
}
}
}
I have found #912 (comment) which appears to be in referring to a similar issue. @terrillmoore has identified this as an issue with ChirpStack.
I've also found this reply on the ChirpStack forum by @terrillmoore in December 2019, however in October 2022, @terrillmoore says it has been resolved in v4 the same thread.
To verify this, I tested the same code on TTN V3 where the downlink issue does not occur (excluding a few initial downlinks when the device connects):

Do you have any insight into why this issue is occurring and why the end device does not reply with a LinkADRAns
in the FOpts
field in the subsequent uplink? If this issue is with ChirpStack, I would appreciate a link to where this is discussed/called out.