ttn_fetch.py
On the server side, this Python script queries The Things Network v3 API to fetch the latest uplinks from a MKR WAN 1310: timestamp, gateway-seen RSSI and base64 payload. The building block of a dashboard or an email alert.
Hardware
An Arduino MKR WAN 1310 (SAMD21 microcontroller, LoRa modem driven by an onboard ESP32 that also exposes Wi-Fi, LoRa antenna and Wi-Fi antenna). A TTN account, an application with a joined device, an API token (ttn-lvc CLI or integration key).
Wiring
None; the board is not even needed to test — use uplinks already received.
How it works
GET /as/applications/{app}/devices/{dev}/uplinks returns frames decrypted by the server. The frm_payload field is base64: decode it with base64.b64decode to recover your bytes.
Running it
Install Arduino SAMD Boards and the MKRWAN 2.x library (MKRWAN1310), select the board, upload. The 1310 is driven through AT commands over its internal ESP32; for LoRa, plan a second LoRa board on the other end.
Variants
Switch to MQTT (mqtt.ttn.cloud) for real time without polling, or schedule the script via cron for daily archiving.
ttn_fetch.py
#!/usr/bin/env python3
"""Uplinks TTN v3 d'une application via l'API HTTP.
Usage : TTN_TOKEN=xxx python ttn_fetch.py [app] [device]
Le token doit etre une cle d'integration "Link API".
"""
import os
import sys
import requests
BASE = "https://eu1.cloud.thethings.network/api/v3"
APP = sys.argv[1] if len(sys.argv) > 1 else "mon-app"
DEV = sys.argv[2] if len(sys.argv) > 2 else "mon-device"
HEADERS = {"Authorization": f"Bearer {os.environ['TTN_TOKEN']}"}
def principal() -> None:
url = f"{BASE}/as/applications/{APP}/devices/{DEV}/uplinks"
r = requests.get(url, params={"limit": 10, "order": "DESC"},
headers=HEADERS, timeout=15)
r.raise_for_status()
for up in reversed(r.json().get("uplinks", [])):
t = up.get("received_at", "?")[:19]
meta = up.get("received_at_metadata", {})
rssi = meta.get("lorawan_metadata", {}).get(
"received_signal_strength", "?")
print(f"{t} rssi={rssi} b64={up.get('frm_payload', '')}")
if __name__ == "__main__":
principal()