Skip to content

LoRaWAN Setup Guide

This guide covers connecting your Wireless Tracker to LoRaWAN networks including The Things Network (TTN), SnapEmu, and other LoRaWAN servers.

  • Heltec Wireless Tracker with LoRa antenna
  • Gateway coverage in your area (or your own gateway)
  • Account on your chosen LoRaWAN network server
  • Arduino IDE or PlatformIO with Heltec libraries

Choose the correct model for your region:

RegionFrequencyModel
EU868863-870 MHzWireless Tracker-HF
US915902-928 MHzWireless Tracker-HF
AU915915-928 MHzWireless Tracker-HF
AS923920-925 MHzWireless Tracker-HF
KR920920-923 MHzWireless Tracker-HF
CN470470-510 MHzWireless Tracker-LF
MethodProsCons
OTAA (Over-The-Air Activation)More secure, automatic session renewalRequires join process
ABP (Activation By Personalization)Immediate transmissionLess secure, manual key management
  1. Create account at thethingsnetwork.org

  2. Create a new Application

  3. Add an End Device:

    • Select “Enter end device specifics manually”
    • Frequency plan: Match your region (e.g., US915 FSB2)
    • LoRaWAN version: 1.0.3
    • Regional Parameters: RP001 Regional Parameters 1.0.3 revision A
  4. Generate credentials:

    • DevEUI: Generate or use chip’s MAC
    • AppEUI: All zeros or generate
    • AppKey: Generate (save this!)
  5. Copy credentials for firmware

function decodeUplink(input) {
return {
data: {
// Parse your payload format here
battery: input.bytes[0] / 10,
latitude: ((input.bytes[1] << 24) | (input.bytes[2] << 16) |
(input.bytes[3] << 8) | input.bytes[4]) / 1000000,
longitude: ((input.bytes[5] << 24) | (input.bytes[6] << 16) |
(input.bytes[7] << 8) | input.bytes[8]) / 1000000
},
warnings: [],
errors: []
};
}
#include "LoRaWan_APP.h"
// OTAA Credentials - Replace with your values
uint8_t devEui[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
uint8_t appEui[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
uint8_t appKey[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
// Channel mask for US915 FSB2 (channels 8-15)
uint16_t userChannelsMask[6] = { 0xFF00, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000 };
// For EU868, use:
// uint16_t userChannelsMask[6] = { 0x00FF, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000 };
LoRaMacRegion_t loraWanRegion = LORAMAC_REGION_US915; // Change for your region
DeviceClass_t loraWanClass = CLASS_A;
uint32_t appTxDutyCycle = 60000; // 60 second transmit interval
bool overTheAirActivation = true;
bool loraWanAdr = true;
bool isTxConfirmed = true;
uint8_t appPort = 1;
uint8_t confirmedNbTrials = 4;
void setup() {
Serial.begin(115200);
Mcu.begin(HELTEC_BOARD, SLOW_CLK_TPYE);
LoRaWAN.init(loraWanClass, loraWanRegion);
LoRaWAN.setDefaultDR(3); // SF7-SF12 depending on region
}
static void prepareTxFrame(uint8_t port) {
appDataSize = 4;
// Example: Send battery voltage and status
float voltage = readBatteryVoltage();
uint16_t vBat = (uint16_t)(voltage * 100);
appData[0] = (uint8_t)(vBat >> 8);
appData[1] = (uint8_t)(vBat & 0xFF);
appData[2] = 0x00; // Status byte
appData[3] = 0x00; // Reserved
}
void loop() {
switch (deviceState) {
case DEVICE_STATE_INIT:
LoRaWAN.init(loraWanClass, loraWanRegion);
deviceState = DEVICE_STATE_JOIN;
break;
case DEVICE_STATE_JOIN:
LoRaWAN.join();
break;
case DEVICE_STATE_SEND:
prepareTxFrame(appPort);
LoRaWAN.send();
deviceState = DEVICE_STATE_CYCLE;
break;
case DEVICE_STATE_CYCLE:
txDutyCycleTime = appTxDutyCycle +
randr(-APP_TX_DUTYCYCLE_RND, APP_TX_DUTYCYCLE_RND);
LoRaWAN.cycle(txDutyCycleTime);
deviceState = DEVICE_STATE_SLEEP;
break;
case DEVICE_STATE_SLEEP:
LoRaWAN.sleep(loraWanClass);
break;
default:
deviceState = DEVICE_STATE_INIT;
break;
}
}
float readBatteryVoltage() {
pinMode(2, OUTPUT);
digitalWrite(2, LOW);
int raw = analogRead(1);
digitalWrite(2, HIGH);
return (raw / 4095.0) * 3.3 * 4.9;
}
RegionLoRaMacRegion_tDefault DRNotes
EU868LORAMAC_REGION_EU868DR5 (SF7)1% duty cycle
US915LORAMAC_REGION_US915DR3 (SF7)Use FSB2 for TTN
AU915LORAMAC_REGION_AU915DR3 (SF7)Similar to US915
AS923LORAMAC_REGION_AS923DR5 (SF7)Multiple sub-bands
CN470LORAMAC_REGION_CN470DR3 (SF7)LF model only
#include "TinyGPS++.h"
TinyGPSPlus gps;
HardwareSerial GPSSerial(1);
void setup() {
// Enable GNSS power (V1.1)
pinMode(3, OUTPUT);
digitalWrite(3, HIGH);
// Initialize GNSS serial
GPSSerial.begin(115200, SERIAL_8N1, 33, 34);
}
static void prepareTxFrame(uint8_t port) {
// Read latest GPS data
while (GPSSerial.available()) {
gps.encode(GPSSerial.read());
}
if (gps.location.isValid()) {
int32_t lat = gps.location.lat() * 1000000;
int32_t lon = gps.location.lng() * 1000000;
appDataSize = 9;
// Latitude (4 bytes, big-endian)
appData[0] = (lat >> 24) & 0xFF;
appData[1] = (lat >> 16) & 0xFF;
appData[2] = (lat >> 8) & 0xFF;
appData[3] = lat & 0xFF;
// Longitude (4 bytes, big-endian)
appData[4] = (lon >> 24) & 0xFF;
appData[5] = (lon >> 16) & 0xFF;
appData[6] = (lon >> 8) & 0xFF;
appData[7] = lon & 0xFF;
// Satellites
appData[8] = gps.satellites.value();
}
}
SymptomCauseSolution
No join acceptWrong credentialsDouble-check DevEUI, AppEUI, AppKey
TimeoutNo gatewayVerify gateway coverage
Repeated joinsSession expiredCheck network server settings
  1. Verify antenna is connected
  2. Check frequency plan matches gateway
  3. Increase spreading factor (lower DR) for better range
  4. Monitor RSSI/SNR on gateway

EU868 has strict 1% duty cycle limits:

// Increase transmit interval to respect duty cycle
uint32_t appTxDutyCycle = 300000; // 5 minutes minimum for EU868