OTAA join fails for ESP32 node using RadioLib (IN865)

Hi all,

I’m trying to connect an ESP32 + SX1262 node to my ChirpStack 1.1 server in IN865 region using RadioLib (LoRaWAN 1.1 OTAA), but OTAA fails with error -1116 (RADIOLIB_ERR_NO_JOIN_ACCEPT).

Setup

  • ESP32 DevKit, SX1262
  • RadioLib version: 7.2.1
  • LoRaWAN 1.1, Class A
  • ChirpStack 1.1, IN865
  • Node keys:
    • DevEUI: 0x899F6EEE7E9D77F2
    • AppKey: 3E 41 74 2C D8 65 70 BC 65 95 1A 80 5A AB 19 7E
    • NwkKey: 28 EC CB 64 AB D0 4D 80 7F 21 86 F9 3B FF B7 B7
    • JoinEUI: 0 (not used)
  • Node sketch: minimal RadioLib OTAA example

Serial Output
[5] Initialising node (OTAA 1.1.0)…
→ Node init OK
[6] Sending JoinRequest…
→ OTAA state: -1116
→ Join failed

What I tried

  • Verified raw LoRa transmission works.
  • Confirmed gateway receives uplink.
  • Radio configured for IN865.
  • Keys match the device registered in ChirpStack (DevEUI, AppKey, NwkKey).

How do I correctly configure RadioLib/ESP32 for OTAA 1.1 to join a ChirpStack server without using JoinEUI? Am I missing something in the sketch or ChirpStack setup?

Thanks in advance!

#define RADIOLIB_DEBUG
#include "config.h"

bool joined = false;

void setup() {
  Serial.begin(115200);
  SPI.begin(6, 2, 7, 10);  // (SCK, MISO, MOSI, SS)
  while (!Serial);
  delay(2000);

  Serial.println(F("\n=== LoRaWAN Debug Start ==="));

  // 1. Init radio
  Serial.println(F("[1] Initialising radio..."));
  int16_t state = radio.begin(
  865.0625,   // IN865 join channel 1
  125.0,             // bandwidth in kHz
  7,                 // spreading factor (SF7 is standard for join)
  7,                 // coding rate (4/7 is common)
  RADIOLIB_SX126X_SYNC_WORD_PRIVATE,
  10, 8, 0, false
);
  
  if (state == RADIOLIB_ERR_NONE) {
    Serial.println(F("   -> Radio init OK"));
  } else {
    Serial.print(F("   -> Radio init FAILED: ")); 
    Serial.println(state);
    while (true);
  }

  // Test raw transmit
  Serial.println(F("[2] Test raw transmit..."));
  state = radio.transmit("Hello");
  if (state == RADIOLIB_ERR_NONE) {                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               
    Serial.println(F("   -> Transmission successful!"));
  } else {
    Serial.print(F("   -> Transmit failed, code: ")); Serial.println(state);
  }

  // 2. Print region/subBand
  Serial.print(F("[3] Region: ")); Serial.println("IN865");
  Serial.print(F("    SubBand: ")); Serial.println(subBand);

  // 3. Print credentials
  Serial.println(F("[4] Device credentials:"));
  Serial.print(F("    DevEUI: 0x")); Serial.println((uint64_t)devEUI, HEX);
  Serial.print(F("    JoinEUI: 0x")); Serial.println((uint64_t)joinEUI, HEX);

  Serial.print(F("    AppKey: "));
  for (uint8_t i = 0; i < 16; i++) { Serial.print(appKey[i], HEX); Serial.print(" "); }
  Serial.println();

  Serial.print(F("    NwkKey: "));
  for (uint8_t i = 0; i < 16; i++) { Serial.print(nwkKey[i], HEX); Serial.print(" "); }
  Serial.println();
  

  // 4. Init node (OTAA 1.1)
  Serial.println(F("[5] Initialising node (OTAA 1.1.0)..."));
  state = node.beginOTAA(joinEUI, devEUI, nwkKey, appKey);
  if (state == RADIOLIB_ERR_NONE) {
    Serial.println(F("   -> Node init OK"));
  } else {
    Serial.print(F("   -> Node init FAILED: ")); Serial.println(state);
    while (true);
  }
  delay(1000);
  // 5. Try to join
  Serial.println(F("[6] Sending JoinRequest..."));
  state = node.activateOTAA();
  Serial.print(F("   -> OTAA state: ")); Serial.println(state);
  if (state == RADIOLIB_LORAWAN_NEW_SESSION) {
    Serial.println(F("   -> Join success!"));
    joined = true;
  } else {
    Serial.println(F("   -> Join failed (RADIOLIB_ERR_NO_JOIN_ACCEPT = -1116)"));
    joined = false;
  }
  checkLoRaMode();
  Serial.println(F("=== Setup Complete ===\n"));
  
}
void checkLoRaMode() {
    // Check if the node object exists
    if (&node != nullptr) {
        Serial.println(F("[Check] LoRaWANNode object exists. Node is configured for LoRaWAN."));

        if (joined) {
            Serial.println(F("[Check] Node has joined the network. LoRaWAN session active."));
        } else {
            Serial.println(F("[Check] Node is in LoRaWAN mode but not joined yet."));
        }

        // Optional: send a dummy payload to verify LoRaWAN behavior
        uint8_t testPayload[1] = {0x00};
        int16_t state = node.sendReceive(testPayload, sizeof(testPayload));
        if (state >= RADIOLIB_ERR_NONE) {
            Serial.println(F("[Check] LoRaWAN sendReceive() successful. Node is in LoRaWAN mode."));
        } else {
            Serial.println(F("[Check] LoRaWAN sendReceive() returned error. Node may not be joined yet."));
        }

    } else {
        Serial.println(F("[Check] No LoRaWANNode object found. Node may be in raw LoRa mode."));
    }
}


void loop() {
  if (joined) {
    Serial.println(F("[Loop] Sending uplink..."));

    uint8_t value1 = radio.random(100);
    uint16_t value2 = radio.random(2000);

    uint8_t uplinkPayload[3];
    uplinkPayload[0] = value1;
    uplinkPayload[1] = highByte(value2);
    uplinkPayload[2] = lowByte(value2);

    int16_t state = node.sendReceive(uplinkPayload, sizeof(uplinkPayload));
    if (state < RADIOLIB_ERR_NONE) {
      Serial.print(F("   -> Error in sendReceive: "));
      Serial.println(state);
    } else if (state > 0) {
      Serial.println(F("   -> Downlink received!"));
    } else {
      Serial.println(F("   -> No downlink received"));
    }

    Serial.print(F("Next uplink in "));
    Serial.print(uplinkIntervalSeconds);
    Serial.println(F(" seconds\n"));
  } else {
    Serial.println(F("[Loop] Node not joined, skipping uplink."));
  }

  delay(uplinkIntervalSeconds * 1000UL);
}