[solved]Adafruit Feather M0 to connect to TTN over OTAA - Unknown Event 20

Hello, I’m new to TTN and am trying to get my Adafruit Feather M0 to take information from a DHT22 temperature and humidity sensor, and then communicate that information via OTAA, but all I get is this in the serial monitor that keeps looping:

173374588: EV_TXSTART
173776540: Unknown event: 20

and eventually this loop stops with:

126183732: EV_JOIN_FAILED

then carries on with the EV_TXSTART again

 * The Things Network - Sensor Data Example
 * Example of sending a valid LoRaWAN packet with DHT22 temperature and
 * humidity data to The Things Networ using a Feather M0 LoRa.
 * Learn Guide: https://learn.adafruit.com/the-things-network-for-feather
 * Copyright (c) 2015 Thomas Telkamp and Matthijs Kooijman
 * Copyright (c) 2018 Terry Moore, MCCI
 * Copyright (c) 2018 Brent Rubell, Adafruit Industries
 * 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.
#include <lmic.h>
#include <hal/hal.h>
#include <SPI.h>

// include the DHT22 Sensor Library
#include "DHT.h"

// DHT digital pin and sensor type
#define DHTPIN 10
#define DHTTYPE DHT22

// 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] = { MYAPPEUI }; // My key has been removed for security reasons
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] = { MYDEVEUI }; // My key has been removed for security reasons
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 the TTN console can be copied as-is.
static const u1_t PROGMEM APPKEY[16] = { MYAPPKEY }; // My key has been removed for security reasons
void os_getDevKey (u1_t* buf) {  memcpy_P(buf, APPKEY, 16);}

// payload to send to TTN gateway
static uint8_t payload[5];
static osjob_t sendjob;

// Schedule TX every this many seconds (might become longer due to duty
// cycle limitations).
const unsigned TX_INTERVAL = 30;

// Pin mapping for Adafruit Feather M0 LoRa
const lmic_pinmap lmic_pins = {
    .nss = 8,
    .rxtx = LMIC_UNUSED_PIN,
    .rst = 4,
    .dio = {3, 6, LMIC_UNUSED_PIN},
    .rxtx_rx_active = 0,
    .rssi_cal = 8,              // LBT cal for the Adafruit Feather M0 LoRa, in dB
    .spi_freq = 8000000,

// init. DHT

void onEvent (ev_t ev) {
    Serial.print(": ");
    switch(ev) {
        case EV_SCAN_TIMEOUT:
        case EV_BEACON_FOUND:
        case EV_BEACON_MISSED:
        case EV_BEACON_TRACKED:
        case EV_JOINING:
        case 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("artKey: ");
              for (size_t i=0; i<sizeof(artKey); ++i) {
                if (i != 0)
                Serial.print(artKey[i], HEX);
              Serial.print("nwkKey: ");
              for (size_t i=0; i<sizeof(nwkKey); ++i) {
                      if (i != 0)
                      Serial.print(nwkKey[i], HEX);
            // 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.
        || 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:
        case EV_REJOIN_FAILED:
        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.println(F("Received "));
              Serial.println(F(" bytes of payload"));
            // Schedule next transmission
            os_setTimedCallback(&sendjob, os_getTime()+sec2osticks(TX_INTERVAL), do_send);
        case EV_LOST_TSYNC:
        case EV_RESET:
        case EV_RXCOMPLETE:
            // data received in ping slot
        case EV_LINK_DEAD:
        case EV_LINK_ALIVE:
        || 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.print(F("Unknown event: "));
            Serial.println((unsigned) ev);

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 {
        // read the temperature from the DHT22
        float temperature = dht.readTemperature();
        //float temperature = 1.1;
        Serial.print("Temperature: "); Serial.print(temperature);
        Serial.println(" *C");
        // adjust for the f2sflt16 range (-1 to 1)
        temperature = temperature / 100;

        // read the humidity from the DHT22
        float rHumidity = dht.readHumidity();
        //float rHumidity = 2.2;
        Serial.print("%RH ");
        // adjust for the f2sflt16 range (-1 to 1)
        rHumidity = rHumidity / 100;

        // float -> int
        // note: this uses the sflt16 datum (https://github.com/mcci-catena/arduino-lmic#sflt16)
        uint16_t payloadTemp = LMIC_f2sflt16(temperature);
        // int -> bytes
        byte tempLow = lowByte(payloadTemp);
        byte tempHigh = highByte(payloadTemp);
        // place the bytes into the payload
        payload[0] = tempLow;
        payload[1] = tempHigh;

        // float -> int
        uint16_t payloadHumid = LMIC_f2sflt16(rHumidity);
        // int -> bytes
        byte humidLow = lowByte(payloadHumid);
        byte humidHigh = highByte(payloadHumid);
        payload[2] = humidLow;
        payload[3] = humidHigh;

        // prepare upstream data transmission at the next possible time.
        // transmit on port 1 (the first parameter); you can use any value from 1 to 223 (others are reserved).
        // don't request an ack (the last parameter, if not zero, requests an ack from the network).
        // Remember, acks consume a lot of network resources; don't ask for an ack unless you really need it.
        LMIC_setTxData2(1, payload, sizeof(payload)-1, 0);
    // Next TX is scheduled after TX_COMPLETE event.

void setup() {
    while (! Serial);


    // LMIC init
    // Reset the MAC state. Session and pending data transfers will be discarded.
    // Disable link-check mode and ADR, because ADR tends to complicate testing.
    // Set the data rate to Spreading Factor 7.  This is the fastest supported rate for 125 kHz channels, and it
    // minimizes air time and battery power. Set the transmission power to 14 dBi (25 mW).
    // in the US, with TTN, it saves join time if we start on subband 1 (channels 8-15). This will
    // get overridden after the join by parameters from the network. If working with other
    // networks or in other regions, this will need to be changed.

    // Start job (sending automatically starts OTAA too)

void loop() {
  // we call the LMIC's runloop processor. This will cause things to happen based on events and time. One
  // of the things that will happen is callbacks for transmission complete or received messages. We also
  // use this loop to queue periodic data transmissions.  You can put other things here in the `loop()` routine,
  // but beware that LoRaWAN timing is pretty tight, so if you do more than a few milliseconds of work, you
  // will want to call `os_runloop_once()` every so often, to keep the radio running.

I’m using the guide provided by adafruit:

I have my DEVEUI, APPEUI and APPKEY already generated and I’m fairly confident I have got it in the correct format, so I’m thinking there’s another problem.

If anyone could shed some light on this that would be great, thank you!

can you show some screencaps from your console gateway traffic page … can you see your node trying to join

Nothing’s coming through at all

What sort of gateway are you using? Do you have any evidence that it is functioning and connected to TTN?

What software are you using? Presumably from the messages LMiC, but from which repo?

Are you certain that both LMiC and the gateway are configured for the correct regional band plan?

I’ve just recompiled some older code and I am seeing the same thing in the logs, I assume something has changed in the MCCI lmic library…

Check your lmic_project_config.h setting, wrong country selected with give the same error.

The code was changed recently in https://github.com/mcci-catena/arduino-lmic/commit/1b2db4e8064ffe545c42b7dbf8bc409f525334cc to add extra events EV_RXSTART and EV_JOIN_TXCOMPLETE, in the renumbering caused by this EV_JOIN_TXCOMPLETE would be event number 20 mentioned above.

But like all but a few events, these are purely advisory - not having code to handle them is in no way an “error” it just means the users sketch does not have code to take any particular action based on these notifications, nor is there any action it would seem appropriate to take beyond optionally printing a more interpretive debug message.

1 Like

Ignore my first comment, I got the same error due to the wrong country frequencies selected (worth checking), updated MCCI library looks clean now too, no compile errors and works as expected.

1 Like

I double checked that, and low and behold it worked! It was just a case of not having the config file set up properly, for some reason my file was blank! Thanks to all who replied

Ok, it was intermittently working, now it’s coming up in the gateway traffic, but I’m still getting the “Unknown Event: 20”. I’ve provided the screencap below. I can see entries on the join tab, but not under uplink or down link. I can also see them in the data tab for the application as well, is there anything I can do to fix this?

This was already explained. Including the fact that it is not an error.

Right, thank you :slight_smile:


Did you ever figure this out? I have the same problem: on starting the sketch I get the ‘unknown error 20’ for a few minutes and then it just starts working. Is this scanning through the channel list?

2951705: EV_TXSTART
3353635: Unknown event: 20
3371572: EV_TXSTART
3752096: Unknown event: 20
4505561: EV_TXSTART
4907496: Unknown event: 20
4934344: EV_TXSTART
5314869: Unknown event: 20
5427249: EV_TXSTART
5768420: EV_JOINED
netid: 19
devaddr: 260229AE
artKey: 23-C0-9A-F-20-54-4F-F3-46-A7-A9-3D-6B-C9-19-91
nwkKey: 12-59-1D-B2-50-ED-21-1D-6A-46-5-BB-C5-30-24-71
5768784: EV_TXSTART
5918140: EV_TXCOMPLETE (includes waiting for RX windows)

I’m also using the code on Adafruit’s exampe but adapted to MetroM0 and RTF95 breakout board.

This was already explained. Including the fact that it is not an error .

Right, no error. Just trying to figure out what’s going on. Is this standard behavior for the LMIC code to connect to TTN? Is there any way to reduce the time for the TXSTART (and JOIN) event to succeed? Any helpful pointers will be much appreciated.

Just trying to figure out what’s going on. Is this standard behavior for the LMIC code to connect to TTN?

There are many reasons a join could fail:

  • re-using an already used join nonce, causing the server to silently reject the join attempt. LMiC in particular likes to grab a single random number (which in many people’s situations comes from a bad algorithm) and if it fails, then count linearly rather than re-randomize. So for example, if your RNG always returns the same thing (oops) it will take longer and longer on each run before you get a join success. LoRaWAN nodes are really supposed to save the join nonce (or session details including frame count) across boots, trivial firmware changes, etc - but most people’s installs don’t.
  • poor RF situation between the node and gateway
  • on-air contention with other users or other radio services
  • node firmware not turning on the radio at exactly the right time to receive replies
  • mismatched channel configuration or other regional settings (note TTN EU868 has a non-standard RX2, but sometimes concludes you didn’t implement that and starts using the standard one…)

It’s a lot easier to debug this kind of thing if you control the only gateway that could potentially be used. Then you can see if there’s gateway traffic, and you can see if the server sends a reply.

For timing issues, while you can blip a GPIO at the end of transmit and start of receive and measure with a scope, it can be even more effective if you trigger a scope on a GPIO blipped at the start of receive, and put the other probe on the gateway card’s transmit LED.

If you don’t have a scope a cheapie USB logic analyzer works too (at least for cleanly digital signals, might not work for tapping out LED drive), and actually makes it easier to untangle what happened in long experiments.

Is there any way to reduce the time for the TXSTART (and JOIN) event to succeed?

If you do LoRaWAN the way it was intended, then joins are a very rare thing so that’s not supposed to be as much of an issue as it is being for you. But that would require saving the result across power cycles and even trivial firmware changes.


I also had issues running the adafruit example in Europe using the adafruit tutorial. For one putting a lmic config file into a special folder of my sketch folder did not make it pick up the EU network parameters (868 MHz). So i went into the library and changed the config file there. Secondly the line //LMIC_selectSubBand(1); had to be commented out in the sketch. I will report this to Lady ada.

1 Like

I’ve uploaded the same firmware many times - the total number is definitely not less than 40 times (the changes were in the TX interval to test, and portions of void do_send block whenever there are changes in my electric circuit). Out of those attempts, it got joined in some cases, but it’s not joining in the recent attempts. I think this could be a potential reason in my case, for no-join. What do I do now, is there a way out?

Another thing I’ve done is I regenerated new random keys (devEUI, appEUI) and incorporated that in the firmware and uploaded the code (again multiple times, my bad).

Unknown event 20 is probably EV_JOIN_TXCOMPLETE.

Thanks a lot Marcus, had the exact same issue and that fixed it :slight_smile: