ESP32 Node not sending

Hi All,

I’m testing an ESP32 node using the following code:

  Single-Channel TTN ABP Device
  By: Jim Lindblom
  SparkFun Electronics
  Date: July 31, 2018
  License: This code is public domain but you buy me a beer if you use this and we meet someday (Beerware license).
  Feel like supporting our work? Buy a board from SparkFun!

  https://sparkle.sparkfun.com/sparkle/storefront_products/14893

  This example demonstrates how to use the SparkX ESP32 LoRa 1-CH Gateway as a LoRaWAN device.
  It is configured to transmit on a single channel -- making it compatible with the same board
  set up as a gateway.

  Library Dependencies:
  arduino-lmic: https://github.com/mcci-catena/arduino-lmic

  To use the example, ensure that you've first configured the arduino-lmic library as required.
  Then create an application and device on the Things Network. Configure it for ABP activation.
  Then copy the NETWORK SESSION KEY, APP SESSION KEY, and DEVICE ADDRESS into the global variables
  towards the top of this sketch.

  Once uploaded, press the "0" button to trigger a LoRa send. The device will send "Hello, World"
  on the set frequency, hoping your gateway hears it.

  This example is based on ttn-abp by: Thomas Telkamp and Matthijs Kooijman
*/

// Include the arduino-lmic library:
#include <lmic.h>
#include <hal/hal.h>
#include <SPI.h>

// Below sets the trigger for sending a new message to a gateway.
// Either or both can be enabled (in which case pressing the button will restart the timer)
#define SEND_BY_BUTTON 1  // Send a message when button "0" is pressed
#define SEND_BY_TIMER 1 // Send a message every TX_INTERVAL seconds

// LoRaWAN NwkSKey, network session key
// This is the default Semtech key, which is used by the early prototype TTN
// network.
static const PROGMEM u1_t NWKSKEY[16] = { XXX };

// LoRaWAN AppSKey, application session key
// This is the default Semtech key, which is used by the early prototype TTN
// network.
static const u1_t PROGMEM APPSKEY[16] = { XXX };

// LoRaWAN end-device address (DevAddr)
static const u4_t DEVADDR = 0xXXX;

// These callbacks are only used in over-the-air activation, so they are
// left empty here (we cannot leave them out completely unless
// DISABLE_JOIN is set in config.h, otherwise the linker will complain).
void os_getArtEui (u1_t* buf) { }
void os_getDevEui (u1_t* buf) { }
void os_getDevKey (u1_t* buf) { }

static uint8_t mydata[] = "Hello, world!";
static osjob_t sendjob;

// Pin mapping for the SparkX ESP32 LoRa 1-CH Gateway
const lmic_pinmap lmic_pins = {
  .nss = 18,
  .rxtx = LMIC_UNUSED_PIN,
  .rst = 14,
  .dio = {26, 34, 35},
};

// If send-by-timer is enabled, define a tx interval
#ifdef SEND_BY_TIMER
#define TX_INTERVAL 5 // Message send interval in seconds
#endif

// State machine event handler
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"));
      break;
    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:
      digitalWrite(LED_BUILTIN, LOW); // Turn off LED after send is complete
      Serial.println(F("EV_TXCOMPLETE (includes waiting for RX windows)"));
      if (LMIC.txrxFlags & TXRX_ACK)
        Serial.println(F("Received ack"));
      if (LMIC.dataLen) {
        Serial.println(F("Received "));
        Serial.println(LMIC.dataLen);
        Serial.println(F(" bytes of payload"));
      }
#ifdef SEND_BY_TIMER
      // Schedule the next transmission
      os_setTimedCallback(&sendjob, os_getTime() + sec2osticks(TX_INTERVAL), do_send);
#endif
      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;
    default:
      Serial.println(F("Unknown event"));
      break;
  }
}

// Transmit data from mydata
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 {
    digitalWrite(LED_BUILTIN, HIGH); // Turn on LED while sending
    // Prepare upstream data transmission at the next possible time.
    LMIC_setTxData2(1, mydata, sizeof(mydata) - 1, 0);
    Serial.println(F("Packet queued"));
  }
}

void setup() {
  Serial.begin(115200);

  // Set button pin as input
#ifdef SEND_BY_BUTTON
  pinMode(0, INPUT);
#endif
  // Configure built-in LED -- will illuminate when sending
  pinMode(LED_BUILTIN, OUTPUT);
  digitalWrite(LED_BUILTIN, LOW);

  // LMIC init
  os_init();
  // Reset the MAC state. Session and pending data transfers will be discarded.
  LMIC_reset();

 // On AVR, these values are stored in flash and only copied to RAM
 // once. Copy them to a temporary buffer here, LMIC_setSession will
  // copy them into a buffer of its own again.
  uint8_t appskey[sizeof(APPSKEY)];
  uint8_t nwkskey[sizeof(NWKSKEY)];
  (void)memcpy_P(appskey, APPSKEY, sizeof(APPSKEY));
  (void)memcpy_P(nwkskey, NWKSKEY, sizeof(NWKSKEY));
  LMIC_setSession(0x1, DEVADDR, nwkskey, appskey);

  // Set up the channels used by the Things Network, which corresponds
  // to the defaults of most gateways. Without this, only three base
  // channels from the LoRaWAN specification are used, which certainly
  // works, so it is good for debugging, but can overload those
  // frequencies, so be sure to configure the full frequency range of
  // your network here (unless your network autoconfigures them).
  // Setting up channels should happen after LMIC_setSession, as that
  // configures the minimal channel set.
  // NA-US channels 0-71 are configured automatically
  LMIC_setupChannel(0, 868100000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI);      // g-band
  LMIC_setupChannel(1, 868300000, DR_RANGE_MAP(DR_SF12, DR_SF7B), BAND_CENTI);      // g-band
  LMIC_setupChannel(2, 868500000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI);      // g-band
  LMIC_setupChannel(3, 867100000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI);      // g-band
  LMIC_setupChannel(4, 867300000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI);      // g-band
  LMIC_setupChannel(5, 867500000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI);      // g-band
  LMIC_setupChannel(6, 867700000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI);      // g-band
  LMIC_setupChannel(7, 867900000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI);      // g-band
  LMIC_setupChannel(8, 868800000, DR_RANGE_MAP(DR_FSK, DR_FSK), BAND_MILLI);      // g2-band
                                          // TTN defines an additional channel at 869.525Mhz using SF9 for class B
                                          // devices' ping slots. LMIC does not have an easy way to define set this
                                          // frequency and support for class B is spotty and untested, so this
                                          // frequency is not configured here.
  // Disable link check validation
  LMIC_setLinkCheckMode(0);

  // TTN uses SF9 for its RX2 window.
  LMIC.dn2Dr = DR_SF9;

  // Set data rate and transmit power for uplink (note: txpow seems to be ignored by the library)
  LMIC_setDrTxpow(DR_SF7, 14);

//disable all channels except channel 0 (868.1 MHz) for communication with single channel gateway
    for (int i=1;i<=8;i++)LMIC_disableChannel(i);

  // Start job -- Transmit a message on begin
  do_send(&sendjob);
}

void loop() {
  os_runloop_once();
#ifdef SEND_BY_BUTTON
  if (digitalRead(0) == LOW) {
    while (digitalRead(0) == LOW) ;
    do_send(&sendjob);
  }
#endif
}

With a Heltec ESP32 LoRa board, I’m getting “Packet queued” and then nothing happens, as shown in the following screenshot:

image

With an ESP32 Devkit V1 and an RF95 attached to it, I’m getting “FAILURE”, as shown in the screenshot below:
image

The failure seems to have something to do with the Arduino lmic library.

Interestingly, though maybe not relevant, I’m getting the same “FAILURE” message with the Heltec board if I select the “DOIT ESP32 DEVKIT V1” board under “Tools->Boards” in the Arduino IDE instead of the correct “Heltec WiFi LoRa 32”. The other way around, i.e. if I select the “Heltec WiFi LoRa 32” board in the Arduino IDE when actually programming the ESP32 DEVKIT V1 board, the failure persists.

Any help with this issue would be greatly appreciated!

1 Like

First, please don’t use that sketch! Limiting a node to a single channel is not how LoRaWAN or by extension TTN are supposed to work.

If you are going to use the MCCI LMiC repo, then you should probably use MCCI’s example sketches. Worth noting too that ESP32 hardware has seemed to be one of the harder cases to get working right with an LMiC codebase.

Your specific “FAILURE” is probably an assert based on unexpected radio chip register readbacks, but you’ll have to check the mentioned line of source to be sure. The most likely culprit would be wrong wiring of the radio chip to the processor, or at least wiring which mismatches the software expectation. Traditional the SPI pins are not mentioned in Arduino library configurations as there isn’t normally a choice of them, on the ESP32 that is less the case and you should check the latest library versions for any evolutions in how the pin assignments are supposed to be handled.

1 Like

Thank you very much for your feedback.

I have amended the sketch such that the node should not be limited to a single channel:

/*
      Single-Channel TTN ABP Device
      By: Jim Lindblom
      SparkFun Electronics
      Date: July 31, 2018
      License: This code is public domain but you buy me a beer if you use this and we meet someday (Beerware license).
      Feel like supporting our work? Buy a board from SparkFun!

      https://sparkle.sparkfun.com/sparkle/storefront_products/14893

      This example demonstrates how to use the SparkX ESP32 LoRa 1-CH Gateway as a LoRaWAN device.
      It is configured to transmit on a single channel -- making it compatible with the same board
      set up as a gateway.

      Library Dependencies:
      arduino-lmic: https://github.com/mcci-catena/arduino-lmic

      To use the example, ensure that you've first configured the arduino-lmic library as required.
      Then create an application and device on the Things Network. Configure it for ABP activation.
      Then copy the NETWORK SESSION KEY, APP SESSION KEY, and DEVICE ADDRESS into the global variables
      towards the top of this sketch.

      Once uploaded, press the "0" button to trigger a LoRa send. The device will send "Hello, World"
      on the set frequency, hoping your gateway hears it.

      This example is based on ttn-abp by: Thomas Telkamp and Matthijs Kooijman
    */

    // Include the arduino-lmic library:
    #include <lmic.h>
    #include <hal/hal.h>
    #include <SPI.h>

    // Below sets the trigger for sending a new message to a gateway.
    // Either or both can be enabled (in which case pressing the button will restart the timer)
    #define SEND_BY_BUTTON 1  // Send a message when button "0" is pressed
    #define SEND_BY_TIMER 1 // Send a message every TX_INTERVAL seconds

    // LoRaWAN NwkSKey, network session key
    // This is the default Semtech key, which is used by the early prototype TTN
    // network.
    static const PROGMEM u1_t NWKSKEY[16] = { 0x65, 0xEE, 0xF0, 0xB4, 0x9B, 0x79, 0x4B, 0x01, 0xF4, 0xEA, 0x6C, 0x79, 0x0F, 0x3E, 0x78, 0xA6 };

    // LoRaWAN AppSKey, application session key
    // This is the default Semtech key, which is used by the early prototype TTN
    // network.
    static const u1_t PROGMEM APPSKEY[16] = { 0xA3, 0x0F, 0x73, 0x9E, 0x4B, 0x57, 0xEB, 0x43, 0x73, 0x7A, 0x22, 0x24, 0x1E, 0x12, 0xBB, 0xAD };

    // LoRaWAN end-device address (DevAddr)
    static const u4_t DEVADDR = 0x26011B1D;

    // These callbacks are only used in over-the-air activation, so they are
    // left empty here (we cannot leave them out completely unless
    // DISABLE_JOIN is set in config.h, otherwise the linker will complain).
    void os_getArtEui (u1_t* buf) { }
    void os_getDevEui (u1_t* buf) { }
    void os_getDevKey (u1_t* buf) { }

    static uint8_t mydata[] = "Hello, world!";
    static osjob_t sendjob;

    // Pin mapping for the SparkX ESP32 LoRa 1-CH Gateway
    const lmic_pinmap lmic_pins = {
      .nss = 18,
      .rxtx = LMIC_UNUSED_PIN,
      .rst = 14,
      .dio = {26, 32, 33},
    };

    // If send-by-timer is enabled, define a tx interval
    #ifdef SEND_BY_TIMER
    #define TX_INTERVAL 60 // Message send interval in seconds
    #endif

    // State machine event handler
    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"));
          break;
        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:
          digitalWrite(LED_BUILTIN, LOW); // Turn off LED after send is complete
          Serial.println(F("EV_TXCOMPLETE (includes waiting for RX windows)"));
          if (LMIC.txrxFlags & TXRX_ACK)
            Serial.println(F("Received ack"));
          if (LMIC.dataLen) {
            Serial.println(F("Received "));
            Serial.println(LMIC.dataLen);
            Serial.println(F(" bytes of payload"));
          }
    #ifdef SEND_BY_TIMER
          // Schedule the next transmission
          os_setTimedCallback(&sendjob, os_getTime() + sec2osticks(TX_INTERVAL), do_send);
    #endif
          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;
        default:
          Serial.println(F("Unknown event"));
          break;
      }
    }

    // Transmit data from mydata
    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 {
        digitalWrite(LED_BUILTIN, HIGH); // Turn on LED while sending
        // Prepare upstream data transmission at the next possible time.
        LMIC_setTxData2(1, mydata, sizeof(mydata) - 1, 0);
        Serial.println(F("Packet queued"));
      }
    }

    void setup() {
      Serial.begin(115200);

      // Set button pin as input
    #ifdef SEND_BY_BUTTON
      pinMode(0, INPUT);
    #endif
      // Configure built-in LED -- will illuminate when sending
      pinMode(LED_BUILTIN, OUTPUT);
      digitalWrite(LED_BUILTIN, LOW);

      // LMIC init
      os_init();
      // Reset the MAC state. Session and pending data transfers will be discarded.
      LMIC_reset();

      // Set static session parameters. Instead of dynamically establishing a session
      // by joining the network, precomputed session parameters are be provided.
      uint8_t appskey[sizeof(APPSKEY)];
      uint8_t nwkskey[sizeof(NWKSKEY)];
      memcpy_P(appskey, APPSKEY, sizeof(APPSKEY));
      memcpy_P(nwkskey, NWKSKEY, sizeof(NWKSKEY));
      LMIC_setSession (0x1, DEVADDR, nwkskey, appskey);

    #if defined(CFG_eu868) // EU channel setup
      // Set up the channel used by the Things Network and compatible with
      // our gateway.
      // Setting up channels should happen after LMIC_setSession, as that
      // configures the minimal channel set.
      LMIC_setupChannel(0, 868100000, DR_RANGE_MAP(DR_SF12, DR_SF7),  BAND_CENTI);      // g-band
      LMIC_setupChannel(1, 868300000, DR_RANGE_MAP(DR_SF12, DR_SF7B), BAND_CENTI);      // g-band
      LMIC_setupChannel(2, 868500000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI);      // g-band
      LMIC_setupChannel(3, 867100000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI);      // g-band
      LMIC_setupChannel(4, 867300000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI);      // g-band
      LMIC_setupChannel(5, 867500000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI);      // g-band
      LMIC_setupChannel(6, 867700000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI);      // g-band
      LMIC_setupChannel(7, 867900000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI);      // g-band
      LMIC_setupChannel(8, 868800000, DR_RANGE_MAP(DR_FSK, DR_FSK), BAND_MILLI);      // g2-band

    /*
    #elif defined(CFG_us915) // US channel setup
      // Instead of using selectSubBand, which will cycle through a sub-band of 8
      // channels. We'll configure the device to only use one frequency.
      // First disable all sub-bands
      
      for (int b = 0; b < 8; ++b) {
        LMIC_disableSubBand(b);
      }
      // Then enable the channel(s) you want to use
      //LMIC_enableChannel(8); // 903.9 MHz
      LMIC_enableChannel(17);
      */
      
    #endif

      // Disable link check validation
      LMIC_setLinkCheckMode(0);

      // TTN uses SF9 for its RX2 window.
      LMIC.dn2Dr = DR_SF9;

      // Set data rate and transmit power for uplink (note: txpow seems to be ignored by the library)
      LMIC_setDrTxpow(DR_SF7, 14);

      // Start job -- Transmit a message on begin
      do_send(&sendjob);
    }

    void loop() {
      os_runloop_once();
    #ifdef SEND_BY_BUTTON
      if (digitalRead(0) == LOW) {
        while (digitalRead(0) == LOW) ;
        do_send(&sendjob);
      }
    #endif
    }

You were right, the culprit was indeed a wrong wiring (or rather a wrong pin mapping in the sketch).

The node now seems to be sending packages. At least it looks like that to me from what I get on the serial monitor:
image

Unfortunately however, I don’t get any data on the TTN application side:
image

Would anyone have an idea what the problem is?

The node’s log looks fine.

As nothing shows in the application/device’s Data page of TTN Console (make sure to have the browser open before sending any data), you need to look at the gateway’s Traffic page to see if anything is being received. It seems you do have a gateway, but it’s been inactive since January 4th?

{
  "eui-b4e62dffff8e351d": {
    ...
    "attributes": {
      "brand": "ESP based",
      "frequency_plan": "EU_863_870",
      "model": "ESP32",
      "placement": "indoor"
    },
    "last_seen": "2020-01-04T21:12:54Z"
  }
}

Also, the description of the gateway above seems to suggest it’s ESP32 based, so probably is not a real gateway but a single-channel forwarder? Those are important details when asking for help… For such forwarder you would indeed need to limit your node to use the very same channel (and maybe even the same SF). But, again, such single-channel forwarder is not a proper gateway. If there are other TTN gateways in your neighborhood, then leave that ESP32 forwarder powered off and use those other gateway(s).

If you see that the node’s transmission has been received by one or more gateways, then maybe you’re running into the frame counter security. You could disable that for testing. Also check the keys and DevAddr, again.

(Please don’t post images for what is really just text. Hard to read on small screens, and hard to copy any text from an image.)

Thanks a lot for your input.

I have actually discontinued using the single channel packet forwarder, as this was discouraged on this forum. I am using the Heltec ESP32 board as a node now.

I know from previous tests with a Dragino board that I have a couple of gateways within reach. So I would expect my ESP32 node to reach at least one of those (I placed the ESP32 node at the same location as the Dragino node).

I’ll switch position of the node around the house to see whether that makes a difference… Any further suggestions as to what the problem could be?

Thanks for the hint re images of text.

If you’re sure the frame counter security is not an issue, and if you’ve triple-checked the keys and DevAddr, then all I can suggest is trying with a larger SF to increase the reach, and How to contact a gateway owner? if that doesn’t help.

With luck things will work.

Unfortunately, if they don’t, lack of access to the gateway can make debugging a lot harder, since essentially everything has to work correctly before you see any sign of progress.

That you’ve edited removed functionality back into a “broken” sketch is worrisome as its hard to know what else could be wrong - probably you should be starting from a maintained LMiC repository and not the one that the board vendor hacked on.