Skip to content

Unconfirmed Downlink Following Every Uplink on ChripStack Network Server (AU915 V1.0.3 with ADR Enabled) #1012

@ElectronicallyE

Description

@ElectronicallyE

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:

Image

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:
Image

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):

Image

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.

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions