Downlink sent with every uplink received

Hi everyone, I have an arduino sketch I’m using to send to ttn from a node (esp32, lmic library). I have a problem where with every uplink, a corresponding downlink is sent. I understand it is pretty normal for the first couple of uplinks to do this, but it’s happening every time. I have an earlier version of the sketch, but fail to find the difference. Can anyone point me in the right direction?

Here is the code…

#include <lmic.h>
#include <hal/hal.h>
#include <SPI.h>
#include <MedianFilter.h>
#include <esp_system.h>


/* define keys */
#define _DEVEUI   {  }
#define _APPEUI   {  }
#define _APPKEY   {  }


/* define macros */
#define uS_TO_S_FACTOR  1000000   /* conversion factor for micro seconds to seconds */
#define TX_INTERVAL     1          /* time ESP32 will go to sleep (in minutes) */
#define MAX_LIMIT       3
float Vbat_Vsol = 3.3;
float STEP_Cnt = 4096;
float V_Trim = 10;
float R_01 = 6800;    //Battery Resistor 1
float R_02 = 8200;    //Battery Resistor 2
float R_03 = 15000;    //Solar Resistor 1
float R_04 = 8200;    //Solar Resistor 2


/* pin mapping */
#define VBAT_PIN    36
#define SWITCH_PIN  23
#define ANALOG_IN_PIN 34
#define DIAG_LED 0

/* LMIC session information */
RTC_DATA_ATTR u4_t netid;
RTC_DATA_ATTR devaddr_t devaddr;
RTC_DATA_ATTR u1_t nwkKey[16];
RTC_DATA_ATTR u1_t artKey[16];

/* LMIC state structure */
RTC_DATA_ATTR uint32_t seqnoUp;
RTC_DATA_ATTR uint32_t seqnoDn;
RTC_DATA_ATTR uint32_t globalDutyRate;
RTC_DATA_ATTR uint32_t globalDutyAvail;
RTC_DATA_ATTR uint32_t opmode;
RTC_DATA_ATTR uint32_t upRepeat;
RTC_DATA_ATTR uint32_t adrTxPow;
RTC_DATA_ATTR uint32_t datarate;
RTC_DATA_ATTR uint32_t devNonce;
RTC_DATA_ATTR uint32_t dnConf;
RTC_DATA_ATTR uint32_t adrAckReq;
RTC_DATA_ATTR uint32_t rxDelay;

/* global varialbles */
RTC_DATA_ATTR bool isOTAA = true;
RTC_DATA_ATTR uint32_t txInterval = TX_INTERVAL;

/* global variables */
static osjob_t sendJob;

int samples = 11;
int ptTotal = 0;
MedianFilter levels(samples, 0);


/* time tick */
int startSend;
int finishSend;
int timeToSend;


/* reset board */
void IRAM_ATTR resetModule() {
  esp_restart();
}


/*
   user-defined functions (essential)
   the LMIC library requires the application to implement a few callback functions.
   these functions will be called by the state engine to query application-specific
   information and to deliver state events to the application.
*/

static const u1_t DEVEUI[8] = _DEVEUI;
static const u1_t APPEUI[8] = _APPEUI;
static const u1_t APPKEY[16] = _APPKEY;

void os_getDevEui(u1_t* buf) {
  memcpy_P(buf, DEVEUI, 8);
}

void os_getArtEui(u1_t* buf) {
  memcpy_P(buf, APPEUI, 8);
}

void os_getDevKey(u1_t* buf) {
  memcpy_P(buf, APPKEY, 16);
}

/* LMIC pin mapping */
const lmic_pinmap lmic_pins = {
  .nss = 18,
  .rxtx = LMIC_UNUSED_PIN,
  .rst = LMIC_UNUSED_PIN,

  /* if DIO2 is not connected, dio2 as unused, otherwise 32 */
  .dio = {
    /*dio0*/ 26,
    /*dio1*/ 33,
    /*dio2*/ LMIC_UNUSED_PIN
  }
};


/* event-based programming model where all protocol events are
   dispatched to the application’s onEvent() callback function */
void onEvent(ev_t ev) {
  Serial.print("[INFO] ");
  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("[INFO] EV_JOINED\n");
      {
        netid = 0;
        devaddr = 0;
        LMIC_getSessionKeys(&netid, &devaddr, nwkKey, artKey);

        Serial.print("[INFO] netid: ");
        Serial.println(netid, DEC);
        Serial.print("[INFO] devaddr: ");
        Serial.println(devaddr, HEX);

        Serial.print("[INFO] artKey: ");
        for (int i = 0; i < sizeof(artKey); ++i) {
          Serial.print(artKey[i], HEX);
        }
        Serial.println("");

        Serial.print("[INFO] nwkKey: ");
        for (int i = 0; i < sizeof(nwkKey); ++i) {
          Serial.print(nwkKey[i], HEX);
        }
        Serial.println("");
        LMIC_setSeqnoUp(1);
      }

      /* disable link check validation (automatically enabled */
      /* during join, but not supported by TTN at this time) */
      LMIC_setLinkCheckMode(0);
      break;
    case EV_JOIN_FAILED:
      break;
    case EV_TXCOMPLETE:
      Serial.println("[INFO] EV_TXCOMPLETE (includes waiting for RX windows)\n");

      if (LMIC.txrxFlags & TXRX_ACK) {
        Serial.println("[INFO] Received ack");
      }

      if (LMIC.dataLen) {
        Serial.printf("[INFO] Received %d bytes of payload\n", LMIC.dataLen);

        /* receive downlink */
        if (LMIC.dataLen == 5) {
          Serial.println("[INFO] Downlink is available");

          /* get packet information */
          uint8_t header = LMIC.frame[LMIC.dataBeg + 0];
          uint8_t cmd = LMIC.frame[LMIC.dataBeg + 1];
          uint16_t dat = 256 * LMIC.frame[LMIC.dataBeg + 2] + LMIC.frame[LMIC.dataBeg + 3];
          uint8_t tail = LMIC.frame[LMIC.dataBeg + 4];

          /*
             analyze downlink {0x55, cmd, dat0, dat1, 0xFF}
             @cmd: {set interval - 01, reboot - 02}
             @dat: 2 byte data
          */
          if (header == 0x55 && tail == 0xFF) {
            if (cmd == 0x01) {
              Serial.println("[INFO] Received CHANGE_TX_INTERVAL request");
              txInterval = dat;
            }
            else {
              Serial.println("[INFO] Received REBOOT request");
              resetModule();
            }
          }
        }
      }

      /* turn off PCB power */
      digitalWrite(SWITCH_PIN, LOW);
      Serial.println("***");
      Serial.println("[INFO] Turning off PCB power");

      /* Use ABP next time */
      isOTAA = false;

      Serial.println("[INFO] Saving LMIC State to memory");
      seqnoUp = LMIC.seqnoUp;
      seqnoDn = LMIC.seqnoDn;
      globalDutyRate = LMIC.globalDutyRate;
      globalDutyAvail = LMIC.globalDutyAvail;
      opmode = LMIC.opmode;
      upRepeat = LMIC.upRepeat;
      adrTxPow = LMIC.adrTxPow;
      datarate = LMIC.datarate;
      devNonce = LMIC.devNonce;
      dnConf = LMIC.dnConf;
      adrAckReq = LMIC.adrAckReq;
      rxDelay = LMIC.rxDelay;

      finishSend = millis();
      timeToSend = (finishSend - startSend) / 1000;

      Serial.printf("[INFO] The board was awake for %d seconds this time\n", timeToSend);
      Serial.printf("[INFO] Going to sleep now for %d minute\n", txInterval);
      Serial.println("***\n\n");
      Serial.flush();

      esp_sleep_enable_timer_wakeup(txInterval * 60 * uS_TO_S_FACTOR);
      esp_deep_sleep_start();
      break;
    default:
      Serial.println("[INFO] Unhandled event");
      break;
  }
}


void doSend(osjob_t* j) {
  /* check if there is not a current TX/RX job running */
  if (LMIC.opmode & OP_TXRXPEND) {
    Serial.println("[INFO] TX/RX was Busy, last msg not yet sent");
  }
  else {

    /* prepare upstream data transmission at the next possible time. */
    Serial.println("[INFO] Reading sensors and building payload\n");

    digitalWrite(SWITCH_PIN, LOW);   /* select Battery channel */
    delay(500);                       /* small delay for settling */
    float vbat_a = analogRead(VBAT_PIN)/STEP_Cnt*Vbat_Vsol;
    int vbat = (vbat_a /(R_02/(R_02+R_01))*100)+V_Trim;
    Serial.println(analogRead(VBAT_PIN));
    Serial.println(vbat_a);
    Serial.println(vbat);

    digitalWrite(SWITCH_PIN, HIGH);          /* select Solar channel */
    delay(500);

    float vsol_a = analogRead(VBAT_PIN)/STEP_Cnt*Vbat_Vsol;
    int vsol = (vsol_a /(R_04/(R_03+R_04))*100)+V_Trim;
    Serial.println(analogRead(VBAT_PIN));
    Serial.println(vsol_a);
    Serial.println(vsol);

    for (int p = 1; p <= samples; p++) {
      int ptLevel = analogRead(ANALOG_IN_PIN);
      levels.in(ptLevel);
      ptTotal = ptTotal + ptLevel;
      Serial.printf("[INFO] %d: %d\n", p, ptLevel);
      delay(50);
    }
    int level = levels.out();

    Serial.printf("[INFO] Median Reading: %d\n", level);

    byte payload[6];
    payload[0] = highByte(vbat);
    payload[1] = lowByte(vbat);
    payload[2] = highByte(level);
    payload[3] = lowByte(level);
    payload[4] = highByte(vsol);
    payload[5] = lowByte(vsol);

    /* prepare upstream data transmission at the next possible time */
    LMIC_setTxData2(1, payload, sizeof(payload), 0);
    Serial.println("[INFO] Packet queued");
  }

  /* next TX is scheduled after TX_COMPLETE event */
}


/* ____    __
  / __/__ / /___ _____
  _\ \/ -_) __/ // / _ \
  /___/\__/\__/\_,_/ .__/
*************** /_/ ***/
void setup() {
  /* serial initialize */
  Serial.begin(9600);
  while (!Serial);
  Serial.println("[INFO] Starting");

  /* pin configuration */
  pinMode(SWITCH_PIN, OUTPUT);
  pinMode(ANALOG_IN_PIN, INPUT);
  digitalWrite(SWITCH_PIN, HIGH);   /* Turn on PCB power */
  delay(3000);

  /* timer started */
  startSend = millis();

  /* LMIC init */
  os_init();

  /* reset the MAC state */
  /* session and pending data transfers will be discarded */
  LMIC_reset();

  /* let LMIC compensate for +/- 1% clock error */
  LMIC_setClockError(MAX_CLOCK_ERROR * 1 / 100);
  LMIC_selectSubBand(1);

  if (isOTAA) {
    /* immediately start joining the network */
    Serial.println("[INFO] Start joining using OTAA");
    LMIC_startJoining();

    /*
       init done - onEvent() callback will be invoked
       the events EV_JOINING and EV_JOINED or EV_JOIN_FAILED will be generated
    */
  }
  else {
    /*
       set static session parameters
       instead of dynamically establishing a session by joining the network, precomputed session parameters can be provided.
       to resume a session with precomputed parameters,
       the frame sequence counters (LMIC.seqnoUp and LMIC.seqnoDn) must be restored to their latest values.
    */
    Serial.println("***");
    Serial.printf("[INFO] Seqnr set to %d\n", seqnoUp) ;
    Serial.println("[INFO] Resuming Session using previous Join Session Keys");
    LMIC_setSession( netid, devaddr, nwkKey, artKey) ;

    Serial.println("[INFO] Retrieving LMIC State from memory");
    Serial.println("***\n");
    LMIC.seqnoUp = seqnoUp;
    LMIC.seqnoDn = seqnoDn;
    LMIC.globalDutyRate = globalDutyRate;
    LMIC.globalDutyAvail = globalDutyAvail;
    LMIC.opmode = opmode;
    LMIC.upRepeat = upRepeat;
    LMIC.adrTxPow = adrTxPow;
    LMIC.datarate = datarate;
    LMIC.devNonce = devNonce;
    LMIC.dnConf = dnConf;
    LMIC.adrAckReq = adrAckReq;
    LMIC.rxDelay = rxDelay;
  }

  /* start job (sending automatically starts OTAA too) */
  doSend(&sendJob);
}


/* __
  / /  ___  ___  ___
  / /__/ _ \/ _ \/ _ \
  /____/\___/\___/ .__/
************* /_/ ****/
void loop() {
  os_runloop_once();
}

image

Sorry, I meant to say I have an earlier version of the sketch which doesn’t have this issue, but fail to find the difference. It’s definitely not gateway related.

LMIC with ADR enabled is known to show this issue.

At this point “LMiC” is no longer a single thing, but a family of many different repos with many different bugs and bugfixes.

Any useful analysis of this would have to start with the exact identity of the repository and version used (as an actual link) and then an examination of the fopts bits in the uplink and downlink messages.

In effect, likely the network keeps telling the node the same thing, because the node’s next uplink indicates it ignored what the network just told it

All sorted, I was missing some lines of code in the sketch. Working perfectly now. Thanks!

Can you please tell us what you forgot, for future readers?

1 Like

Of course, here are the changes I added…Using version 2.3.2 this library
Apparently doesn’t work with the latest version (3.0.99)

Under - /* LMIC state structure */

RTC_DATA_ATTR uint32_t ladrAns;
RTC_DATA_ATTR uint32_t devsAns;

Under - case EV_TXCOMPLETE:

                ladrAns = LMIC.ladrAns;
                devsAns = LMIC.devsAns;

Under void setup ()

            LMIC.ladrAns = ladrAns;
            LMIC.devsAns = devsAns;
1 Like

I was noticing downlinks with no payload sent after every uplink. These went away after I started using the MCCI LMIC library.

I’m wondering if older versions of it were ignoring some sort of config message from TTN or the gateway so they just kept getting sent?

Probably, but as in the message a few above yours posted months ago, such reports are meaningless unless they include:

  1. The exact version of LMiC being used - repository and precise version checked out

  2. The actual contents of the downlink messages, most especially the fOpts field.