ESP32 LoRaWAN and The Things Network: OTAA Join, Payload Decoding, and Downlinks
This site's LoRa sensor node project covers point-to-point LoRa — one ESP32 talking directly to another over raw radio, with no network layer in between. That's a great fit for a single sensor reporting to a single gateway you own. LoRaWAN is a different animal entirely: a managed network protocol built on top of the same LoRa radio modulation, designed so hundreds of battery-powered devices can share a handful of public or private gateways, with encryption, deduplication, and device management handled by a network server. The Things Network (TTN) is the free, community-run LoRaWAN network server most hobbyists start with. This guide covers joining an ESP32 node to TTN over OTAA, decoding the payloads that show up, and sending commands back down to the device.
LoRaWAN vs Point-to-Point LoRa: Know Which One You're Building
Point-to-point LoRaLoRaWAN Network layerNone — you write both endsStandardized, handled by network server Gateway neededNo, direct radio linkYes — TTN, ChirpStack, or a private one Range1-5 km typical, line of sight dependent2-15 km typical via gateway, longer in rural areas Multiple devicesYou manage addressing yourselfNetwork server handles it, thousands of devices per gateway EncryptionDIY if you want itBuilt-in AES-128, end-to-end and network-layer Best forOne-off sensor-to-base links, no infrastructureScalable deployments, existing gateway coverage, low-power field sensorsIf a TTN gateway already covers your area (check the public coverage map before buying hardware), LoRaWAN gets you a maintenance-free path to the internet for a battery sensor with no Wi-Fi and no cellular data plan. If there's no coverage nearby, you'd need to run your own gateway anyway, at which point the point-to-point approach is often simpler.
Hardware
You need an ESP32 paired with a LoRa radio module — the SX1276/SX1262 chips are the common choices, and the frequency band matters: 915 MHz in the US, 868 MHz in Europe, with regional variants elsewhere. Boards like the Heltec WiFi LoRa 32 or TTGO LoRa32 integrate the ESP32 and radio on one board and are the simplest starting point. If you're reusing the discrete LoRa module from this site's point-to-point sensor node project, the wiring is the same SPI interface (SCK, MISO, MOSI, NSS, plus a DIO0 interrupt line) — only the firmware changes.
Registering a TTN Application and Device
- Create a free account at The Things Network console and select the cluster closest to you (this determines which gateways your device can reach).
- Create an Application, then register a new End Device. TTN can auto-fill LoRaWAN version and frequency plan if you pick a supported device profile, or you can enter them manually for a generic module.
- Choose OTAA (Over-The-Air Activation) rather than ABP (Activation By Personalization). OTAA negotiates fresh session keys on every join instead of hardcoding them, which is both more secure and more forgiving if a device's frame counter ever gets out of sync.
- Record the three values the console generates: DevEUI, AppEUI (sometimes labeled JoinEUI), and AppKey. These go into your firmware.
Firmware: Joining and Sending Uplinks
The MCCI LoRaWAN LMIC library (or the newer RadioLib, which supports a broader range of modules and is actively maintained) handles the LoRaWAN MAC layer so you don't have to implement join procedures and duty-cycle limits yourself. A minimal OTAA join and uplink loop looks like this with RadioLib:
#include <RadioLib.h> SX1262 radio = new Module(NSS_PIN, DIO1_PIN, RST_PIN, BUSY_PIN); LoRaWANNode node(&radio, &US915); void setup() { radio.begin(); node.beginOTAA(joinEUI, devEUI, nwkKey, appKey); node.activateOTAA(); } void loop() { uint8_t payload[4]; float tempC = readTemperature(); int16_t t = (int16_t)(tempC * 100); payload[0] = t >> 8; payload[1] = t & 0xFF; uint16_t batt = readBatteryMillivolts(); payload[2] = batt >> 8; payload[3] = batt & 0xFF; node.sendReceive(payload, sizeof(payload)); esp_sleep_enable_timer_wakeup(15 * 60 * 1000000ULL); esp_deep_sleep_start(); }That combination of deep sleep between transmissions and a compact binary payload — two bytes per value instead of a JSON string — is standard practice for LoRaWAN nodes, and it pairs directly with the deep sleep techniques covered in this site's ESP32 battery optimization guide. A node sending a 4-byte payload every 15 minutes and sleeping otherwise can run for months on a single 18650 cell.
Decoding Payloads on TTN
Raw LoRaWAN uplinks arrive at TTN as a base64-encoded byte string. Rather than parsing that by hand downstream, TTN lets you attach a JavaScript payload formatter to the device or application that runs server-side and converts bytes into a structured JSON object before anything downstream ever sees it:
function decodeUplink(input) { var temp = ((input.bytes[0] << 8) | input.bytes[1]); if (temp > 32767) temp -= 65536; var batt = (input.bytes[2] << 8) | input.bytes[3]; return { data: { temperature_c: temp / 100, battery_mv: batt } }; }Once that's in place, every uplink shows up in the TTN console already decoded, and can be forwarded via TTN's built-in MQTT broker or webhook integrations to Node-RED, Home Assistant, or InfluxDB — the same MQTT-based automation stack this site already covers for Pi-based sensor networks.
Downlinks: Sending Commands Back to the Node
LoRaWAN's duty-cycle and regional regulations mean downlinks aren't instantaneous the way an MQTT publish is — a Class A device (the default, and the lowest-power class) only opens receive windows immediately after it transmits. Queue a downlink in the TTN console or via the API, and it delivers on the node's next uplink, not before. That's a fundamental design trade for battery life, not a bug, and it shapes what LoRaWAN downlinks are good for: configuration changes, reporting interval updates, or acknowledging a threshold alert — not real-time control. Class C devices stay in constant receive mode for near-instant downlinks, at the cost of the battery life that made LoRaWAN attractive in the first place.
Fair Use and Duty Cycle
US915 and EU868 both impose regulatory limits on transmit time (duty cycle in EU868, dwell time and channel restrictions in US915), and TTN additionally enforces a fair-use policy limiting uplink airtime per device per day on its free community network. A sensor reporting every 15 minutes with a small payload comfortably fits within both; a node transmitting every few seconds will get throttled or blocked. Keep payloads small and intervals sane and TTN's free tier is genuinely sufficient for most hobby deployments.
Between this and the site's existing point-to-point LoRa sensor node project, you now have both ends of the LoRa spectrum covered: direct radio links when you own both sides, and a proper managed network when you need scale, encryption, and someone else's gateway infrastructure doing the heavy lifting.