Summary
In the last few days, I tried getting a project for Uni working. Im trying to build a smart bird feeder that sends environmental data to TTN. I tried using OTAA at first, but I did not manage to get it to work at all. I then switched to ABP. All using LoRaWAN 1.1.0 with RP002 Regional Parameters 1.0.3. I’m using RadioLib in its newest version. I also activated the option “Resets frame counters”, as stated in RadioLibs example. The ESPs logs tell me the data is transmitted properly, but I rarely get a message to be visible in the TTN Console (approximately every ten to twenty transmissions - no matter how near or far I’m away from the gateway). I set the delay between transmissions to 1800 seconds.
Can someone provide me with a working example for a Heltec V3.4? Ideally for OTAA in LoRa Specification 1.1.0. Or can someone tell me, what I’m doing wrong? I’ll provide the transmission logic and configuration file in the attachment.
Hardware
Heltec V3.4 863 MHz sold by Binghe
Code
config.h
#ifndef CONFIG_H
#define CONFIG_H
#include <Arduino.h>
#include "credentials.h" // Securely load LoRaWAN credentials
// ========================================================
// 1. HARDWARE PINS
// ========================================================
// LoRa SPI & Control Pins (Heltec WiFi LoRa 32 V3 Default)
#define LORA_NSS 8
#define LORA_DIO1 14
#define LORA_NRST 12
#define LORA_BUSY 13
// ========================================================
// 2. PROTOCOL & PAYLOAD CONFIGURATION
// ========================================================
// Reduced maximum LoRa payload size based on standard LoRaWAN Data Rate limits
#define MAX_BUFFER_SIZE 51
#define SCHEDULED_UPDATE_INTERVAL 1800
#endif // CONFIG_H
.ino
#include <Arduino.h>
#include "lora_node.h"
#include "config.h"
void setup() {
Serial.begin(115200);
delay(1000);
Serial.println("\n--- LORA NODE ---");
Serial.println("[MAIN] Fresh Boot. Initializing...");
// Simulate buffering data for the payload
uint8_t dummy_data[2] = {0x00, 0x00};
appendToBuffer(0x10, dummy_data, 2);
Serial.println("[MAIN] Attempting TTN transmission...");
if (transmitBuffer()) {
Serial.println("[MAIN] TX Success! Buffer cleared.");
} else {
Serial.println("[MAIN] TX Failed.");
}
executeDeepSleep(SCHEDULED_UPDATE_INTERVAL);
}
void loop() {
// Left empty by design
}
lora_node.h
#ifndef LORA_NODE_H
#define LORA_NODE_H
#include <Arduino.h>
#include <RadioLib.h>
#include "config.h"
#include "credentials.h"
// --------------------------------------------------------
// FUNCTION DECLARATIONS
// --------------------------------------------------------
bool appendToBuffer(uint8_t tag, uint8_t* data, size_t length);
bool transmitBuffer();
uint16_t getBufferLength();
#endif // LORA_NODE_H
lora_node.cpp
#include "lora_node.h"
// Initialize the SX1262
SX1262 radio = new Module(LORA_NSS, LORA_DIO1, LORA_NRST, LORA_BUSY);
// Initialize the LoRaWAN Node for Europe
LoRaWANNode node(&radio, &EU868);
// RTC Variables for Data Buffer
RTC_DATA_ATTR uint8_t rtc_payload_buffer[MAX_BUFFER_SIZE];
RTC_DATA_ATTR uint16_t rtc_buffer_index = 0;
// RTC Variables to save Frame Counters across deep sleep for ABP
RTC_DATA_ATTR uint8_t rtc_lorawan_session[RADIOLIB_LORAWAN_SESSION_BUF_SIZE];
RTC_DATA_ATTR bool rtc_session_saved = false;
uint16_t getBufferLength() {
return rtc_buffer_index;
}
bool appendToBuffer(uint8_t tag, uint8_t* data, size_t length) {
if (rtc_buffer_index + 1 + length > MAX_BUFFER_SIZE) {
return false;
}
rtc_payload_buffer[rtc_buffer_index++] = tag;
memcpy(&rtc_payload_buffer[rtc_buffer_index], data, length);
rtc_buffer_index += length;
return true;
}
bool transmitBuffer() {
if (rtc_buffer_index == 0) return false;
int state = radio.begin();
if (state != RADIOLIB_ERR_NONE) return false;
// 1. Initialize ABP for LoRaWAN 1.1.0
state = node.beginABP(devAddr, fNwkSIntKey, sNwkSIntKey, nwkSEncKey, appSKey);
if (state != RADIOLIB_ERR_NONE) {
radio.sleep();
return false;
}
// 2. ACTIVATE THE ABP SESSION FIRST (This resets the internal counters to 0)
state = node.activateABP();
if (state != RADIOLIB_ERR_NONE &&
state != RADIOLIB_LORAWAN_NEW_SESSION &&
state != RADIOLIB_LORAWAN_SESSION_RESTORED) {
Serial.printf("[LORA] ABP Activation Failed! Error: %d\n", state);
radio.sleep();
return false;
}
node.setADR(false);
// 3. RESTORE SESSION AFTER ACTIVATION
if (rtc_session_saved) {
node.setBufferSession(rtc_lorawan_session);
}
Serial.println("[LORA] Transmitting 1.1.0 ABP Unconfirmed Uplink...");
// 4. Transmit immediately without any Join Handshake
state = node.sendReceive(rtc_payload_buffer, rtc_buffer_index, 1, false);
if (state != RADIOLIB_ERR_NONE && state != RADIOLIB_ERR_RX_TIMEOUT) {
Serial.printf("[LORA] TX Failed! RadioLib Error Code: %d\n", state);
}
// 5. Save the updated frame counters to RTC before sleeping
uint8_t* session = node.getBufferSession();
if (session) {
memcpy(rtc_lorawan_session, session, RADIOLIB_LORAWAN_SESSION_BUF_SIZE);
rtc_session_saved = true;
}
radio.sleep();
// RADIOLIB_ERR_RX_TIMEOUT is a success state for unconfirmed uplinks
if (state == RADIOLIB_ERR_NONE || state == RADIOLIB_ERR_RX_TIMEOUT) {
rtc_buffer_index = 0;
return true;
}
return false;
}