DEV Community

manthink
manthink

Posted on

The Five-Layer Funnel for LoRaWAN Troubleshooting: From "No Data in the Cloud" to "The Numbers Are Wrong"

Currency note: figures in this article are converted at the October 2026 reference rate of 1 USD ≈ 6.71 CNY and 1 EUR ≈ 7.88 CNY. Original quote currencies are preserved; CNY amounts are conversions.

The most expensive thing in a troubleshooting session is not a missing tool. It is guessing.

The symptom is one sentence: "The device is offline." That sentence can mean five completely different things. The gateway lost power. The gateway is online but hears no radio frames. Frames arrive but decryption fails. Decryption succeeds but the parser errors out. Parsing succeeds but the data never reaches storage or the outbound channel.

These five causes live in five different layers. They require different tools, different screens, and different evidence. Without an order, troubleshooting degenerates into refreshing every page once and then changing whichever setting looks most suspicious — then finding it did not help, then trying the next one.

The funnel replaces guessing with elimination. Each layer answers exactly one question. Yes moves you down; no stops you there. Five layers in, the fault is pinned to a single cell. No luck required.


1. The funnel at a glance

Layer Question to answer Where to look Key criterion
1 Is the gateway online Operations → Gateway Management Whether online status, heartbeat time, and total online time agree
2 Did the gateway hand frames to the NS Network Data → NS Data Whether records can be found by DevEUI or DevAddr
3 Did the NS decrypt successfully Network Data → AS Data Whether an application-layer packet exists at the same moment
4 Did the thing model parse it Application Data → Realtime Data / Device → Telemetry Whether values and timestamps are advancing
5 Where did the parsed data go Forward whitelist, raw retention switch, subscriber Dropped, filtered, or never subscribed

The third column of each row is more important than it looks: what a false conclusion at this layer costs you. Most troubleshooting disasters are not caused by missing a symptom. They are caused by seeing a symptom, attributing it to the wrong layer, and breaking a configuration that was fine. The classic version: turning off ADR to "make data come up faster," and blowing the power budget of a battery device in the process.


2. Layer 1: Is the gateway online

Open Operations → Gateway Management. Each row shows location, type, online status, enabled status, hardware version, firmware version, LoRa standard, total online time, heartbeat time, and update time.

Most people read the online status column only. That is the most deceptive column on the page.

Read the three time fields together

  • Heartbeat time: when the gateway last reported a heartbeat
  • Total online time: cumulative time online
  • Update time: when this record was last modified

A gateway can show "online" while its heartbeat time stopped forty minutes ago. The default heartbeat period is 60 seconds. Forty minutes without a heartbeat means it has done nothing for forty minutes — the platform simply has not met the condition to mark it offline yet. Being marked offline requires a time condition to be satisfied; it is not a "last message received" flag.

Conversely, a very short total online time with an advancing heartbeat time means the gateway is repeatedly dropping and reconnecting. That is the signature of a power or network problem, not a radio problem.

Criterion: the gap between heartbeat time and now should be smaller than twice the heartbeat period. If the gap is larger while the status still reads online, stop trusting that column and read the heartbeat time.

Three definitions of "online," not interchangeable

This has to be settled before anything else. ThinkLink exposes at least three different online signals with different algorithms and different boundaries:

Source Algorithm and boundary
API online field Treated as online when heartbeat is empty; when non-empty, computed from activity time and twice the heartbeat, missing activity time counts as offline, and an exactly-equal deadline no longer counts as online
server_attrs._online Numeric 1 online, 0 offline; with an empty heartbeat and the feature enabled it stays 1 with no timeout task; with a non-empty heartbeat it only goes offline when the current time strictly exceeds active_time + 2 × heart_period
MQTT data.online A boolean state-change notification; asynchronous propagation has latency; it is neither a full state snapshot nor an alarm event

Each path gets used somewhere: dashboard display, statistics, automation. Mixing them produces a very concrete failure. Your dashboard reads server_attrs._online, your alarm rule reads API online, the two disagree, and you conclude the data is wrong — when in fact only the definitions differ.

One more trap: after offline notification is disabled, _online retains its last value. A leftover 0 or 1 is not evidence of the current state. Reading 1 and concluding the device is alive is wrong.

If the gateway is offline, separate "no power" from "no network"

Three platform-side checks:

  • Ping: one of the dedicated operations for ManThink-type gateways, confirming reachability of the platform-to-gateway path
  • Edit → IP address: the gateway reports its current IP when it comes online; this field lets you confirm its real network address and detect a changed IP or a move to another subnet
  • Status trend: shows the online/offline curve over a chosen range so you can locate when the drop started. Its value is separating "always dropping" from "started dropping at time X" — the latter lines up directly with construction work, power cuts, lightning, or hardware swaps

If Ping fails and the IP is stale or empty, the problem is on the gateway side or on the path to the server, not in the platform. Adjusting platform settings at this point is wasted effort.


3. Layer 2: Did the gateway hand frames to the NS

This is Network Data → NS Data. You are looking at raw LoRaWAN frames together with frequency, RSSI, SNR, and data rate.

The decisive detail is which identifier you search with. Get this wrong and you will conclude "the NS received nothing" when it received plenty:

Situation Search field Why
Device has never joined, or was just reset DevEUI Join Request / Join Accept frames are identified by DevEUI; no DevAddr exists yet
Device has joined and reports normally DevAddr After joining, all data frames use a dynamically assigned DevAddr, never the DevEUI

A factory-fresh device returns nothing for its DevEUI and nothing for its DevAddr — it has no DevAddr yet. A healthy running device returns nothing for its DevEUI on ordinary uplinks, because those frames carry the DevAddr. Neither empty result is a fault. Both are a search on the wrong key.

Criterion: NS has data → the problem is above the NS (start at layer 3). NS has no data → the problem is radio or backhaul, not the platform.

When NS has no data, the directions are:

  • Is the device even transmitting (device-side log via serial or vendor tool)
  • Are the frequencies right (device channel plan versus gateway regional standard)
  • Distance and obstruction (RSSI and SNR — but only meaningful once at least one frame arrives; with zero frames there is no signal to read)
  • Is the gateway backhaul working (whether UDP 1700 is open — covered separately below, it is the single most common trap)

4. Layer 3: Did the NS decrypt successfully

This is Network Data → AS Data. AS data is what the NS emits after decrypting and deframing the LoRaWAN frame: LoRaWAN encryption is gone, but userdata.payload is still the Base64-encoded application payload, not yet parsed by the thing model.

This layer carries the most diagnostic information of the five. An AS uplink looks like this:

{ "if": "loraWAN", "type": "data", "moteeui": "6353012af1090498", "version": "3.0", "gwrx": [ { "eui": "5a53012501030056", "chan": 0, "lsnr": 14.2, "rfch": 0, "rssi": -21, "time": "2025-09-24T07:07:03.807677Z", "tmst": 1152383989 } ], "moteTx": { "codr": "4/5", "datr": "SF7BW125", "freq": 470.9, "modu": "LORA" }, "userdata": { "port": 11, "class": "ClassA", "seqno": 27464, "payload": "IQcDDG4BAADaBB0C538J", "confirmed": false } }

Every field has a specific diagnostic job:

Field Diagnostic use
moteeui Confirms which device sent it, ruling out a mistyped EUI
gwrx[] Every gateway that heard the frame, with per-gateway RSSI / LSNR. With multiple receivers, take the best RSSI as the link-quality reference
gwrx[].chan / rfch Channel number and RF chain. Misconfigured frequencies surface here
gwrx[].lsnr / rssi SNR and signal strength. SNR is the real LoRa margin metric; RSSI is only a reference
moteTx.datr Data rate, e.g. SF7BW125. If ADR is broken, this sticks at the lowest rate
moteTx.freq Uplink frequency. A mismatch with the gateway channel plan is a frequency problem
userdata.port FPort, distinguishing business channels
userdata.seqno Device frame counter. This is how you diagnose retransmission and loss — contiguous counters with uneven spacing mean retries; gaps mean real loss
userdata.payload Base64 application payload; exactly what the thing-model script consumes

NS has data, AS is empty: only three possibilities

The official decision table is refreshingly blunt:

Symptom Likely cause Action
NS has data, AS empty LoRaWAN key mismatch (AppKey / AppSKey / NwkSKey) Verify the keys in the profile against what is burned into the device
NS has data, AS empty No device profile, or enable is false Create the profile and confirm enable is on
AS has data Decryption succeeded; go to layer 4 —

The "enable is false" case is especially sneaky. It raises no error. The NS simply does not respond, which from the outside is indistinguishable from a wrong key. And it is a silent switch: the profile exists, the keys exist, nothing takes effect.

One more easily missed constraint: a given device profile should be enabled on exactly one platform (Cloud / TKG / TKE) at a time, otherwise join conflicts follow. A profile left enabled in the cloud while the device is also commissioned into an edge server means two NS instances competing for the same DevAddr. The symptom is "works, then does not, then works" — far harder to chase than a clean failure.

FPort is more than a business category

Several FPorts are conventional on the platform:

FPort Purpose
51 Pass-through: raw bytes forwarded as-is
201 Firmware OTA
209 Heartbeat, used to confirm device liveness
214 Parameter configuration and readback

A packet on port 209 is a heartbeat, not business data — its payload contains none of your measurement points. A thing-model script without a per-port branch will happily parse a heartbeat as sensor data and emit nonsense. This is a common source of "one field occasionally turns into a weird number."


5. Layer 4: Did the thing model parse it

Two entry points: Application Data → Realtime Data, and Device detail → Telemetry tab.

Telemetry in the device detail is grouped by thing model and shows every field from the most recent parse together with a timestamp. It updates by MQTT push, so no manual refresh is needed. That makes it a good liveness probe: stare at it for three minutes. If neither values nor timestamps move, parsing has stopped. If the timestamp advances but a specific field is empty, that field is simply never assigned by the script.

AS has data, realtime data is empty: only two causes

Symptom Likely cause Action
AS has data, realtime empty No thing model bound to the device Bind one in model management
AS has data, realtime empty Parser script error Open the editor and feed the AS payload in manually to debug
AS has data, some fields empty Script does not cover that FPort or field Inspect the branch for that port

That middle row — manually feeding the payload — is the highest-leverage action in the entire troubleshooting system. Script errors, a null return, a wrongly shaped object: in the overwhelming majority of cases you can reproduce them by pasting a Base64 string. No site visit, no restart.

Three fixed traps in the script layer

Three things reliably produce "it will not parse":

First, the only output channel is the return value. Scripts run in a restricted sandbox: no console.log, no async, and a 1000 ms execution ceiling. Every result, every diagnostic, must come out through return. Anyone expecting print-based debugging sees "nothing was output" and wrongly concludes "the script never ran."

Second, the payload comes from msg.userdata.payload and it is Base64. Treat it as a string and you get mojibake instead of bytes. Decode it first, then read by byte offset.

Third, a script existing does not mean the mapping is done. Written, saved, no error — none of that proves the field reached telemetry. The only proof is a value visible in the Telemetry tab. These are separate states on the platform, and conflating them produces endless loops between "but I wrote it" and "but there is no data."


6. Layer 5: Where did the data go

At this point AS has packets and telemetry has values, yet downstream still says it received nothing. The problem is no longer acquisition. It is the data egress.

Egress 1: the raw retention switch

One easily misread symptom: the AS Data page is empty while telemetry is perfectly fine.

This is not a contradiction. Raw packet retention must be enabled separately via the device's real_time_storage switch. When it is off, the NS still receives, decrypts, runs the thing model, and writes telemetry — the whole chain works — it simply keeps no raw copy at that layer.

So "nothing on the AS page" has two entirely different meanings: no signal came in, or signal came in but was not archived. The discriminator is trivial — check whether telemetry has values. Values present means retention is off. No values means a link problem.

Egress 2: the forward whitelist

Since 2.00.066, server attributes and shared attributes are not forwarded by default; only fields explicitly checked for forwarding are sent. The intent is to keep irrelevant fields from flooding MQTT, but the side effect is: you add a server attribute, see a value in the UI, and the MQTT side never receives it — because it was never checked.

Similarly, when the target broker disconnects there is no buffering and no replay, and no exactly-once guarantee. A forwarder is not a message queue; it is a realtime channel. Data produced during those few minutes while the subscriber restarts is simply gone.

Egress 3: the subscriber's own problems

The most common sentence on the MQTT side is "I subscribed but there is no data." Three fixed causes:

  • {tenant} was never replaced with the organization account. Topics look like /v32/{tenant}/as/up/data/#, where {tenant} is a placeholder, not a literal. Copy it verbatim and you connect fine, subscribe fine, and receive nothing
  • Subscribed to the parsed topic while the device has no forwarding configured. tkl/up/telemetry/# is parsed; as/up/data/# is raw. They draw from different sources, and data on one says nothing about the other
  • Received, then dropped by a script. A forwarding script that returns null discards the message. That is the correct way to filter, and also the most common invisible failure

One version note worth remembering: registering a subscription is not the same as subscribing successfully — only a SUBACK counts. Since 2.00.070 this semantics is explicit, yet in the field many still treat "shown as configured in the UI" as "receiving data."

False empties introduced by paginated loading

Since 2.00.075, configuration, attributes, and telemetry load separately on demand. After switching tabs, wait for a successful load before editing and saving — unloaded fields must not be treated as empty values. MQTT messages may likewise carry only a subset of whitelisted attributes; receivers should merge by field rather than overwrite whole groups.

Together these two facts produce an awkward class of failure: a save writes an empty value into a field that had simply not loaded, or a receiver overwrites complete state with a partial message. The visible symptom is "a setting reverted itself," and it is hard to reproduce.


7. The on-site trio: diagnosing without the platform

The platform answers "which layer is broken." The field answers "why is that layer broken." Three things are enough.

One: the gateway's local web console

GD6 series gateways use the gwTool6 web console. Key entry points:

  • Ethernet defaults to DHCP; on the same subnet, browse straight to the gateway IP
  • The Wi-Fi AP SSID is GD6-<Gateway EUI>, falling back to LGW-Config only when no valid EUI exists
  • Factory AP password and web password are both wifi!0804, managed independently; change them on first login

With no network on site, joining that hotspot from a phone is the fastest route into the console. Note the condition: the hotspot must actually appear and accept connections to count — "shown as enabled in the UI" does not.

The Radio page frequencies, the NS page server address and ports, and the "save and restart to apply" confirmation all live here. One operational detail deserves writing down: the "submit" button at the bottom of the page is what saves Radio, whereas the NS page uses "save and restart," and after the service recovers you must reload to verify. Skip the reload and you are reading the form's old values, not the device's current ones.

While on the subject, one boundary specific to GD62: it has no external REC button. Any "hold REC for about two seconds" recovery procedure does not apply to that unit. Factory reset goes through the support procedure; afterwards the web password returns to wifi!0804 while a previously written Gateway EUI is retained.

Two: an MQTT client

Whether mosquitto_sub, MQTTX, or your own script, subscribing once to /v32/{tenant}/as/up/data/# shows you every device's raw uplink directly. It is the fastest way to answer "is anything actually flowing," much faster than paging through the UI.

Watch three things: whether packets appear at all, whether moteeui is the device you care about, and whether userdata.seqno advances contiguously. That last one instantly separates "the device never sent" from "the device sent and it never arrived."

Three: a site visit you can actually price

Troubleshooting time has a quantifiable cost. A single site visit (labour plus travel) runs roughly 300–800 CNY in a tier-one Chinese city, and roughly 150–400 USD (≈ 1,000–2,700 CNY) overseas at published service rates. The point is not precision. The point is that it puts a price on "can this be solved remotely": if a problem can be walked through all five layers remotely and pinned to one layer, the site visit becomes "go replace that part" instead of "go see what is wrong." The difference in elapsed time is typically half a day versus three days.

The more expensive failure is misattribution. Disabling ADR to "make data come up faster" will blow a battery device's power budget within a week. Nothing shows on site. Three months later it surfaces as "the batteries do not last," and by then it is very hard to trace back to which setting changed.


8. Seven traps from real integration work

These seven come from actual integration records, not theory. Their common shape: the symptom appears in the platform, the root cause sits outside it.

Symptom Root cause Fix
No gateway UDP arrives at all Security group allows TCP only; UDP 1700 not opened Add an inbound rule for UDP 1700
Gateway packets rejected (illegal / single byte) Not using Semtech UDP, or a proprietary protocol Set nsProtocol to pkt_fwd
Packets received but nothing goes uplink Leading space in nsAddress Remove the space and restart the service
Gateway connects but frames are dropped Bridge prefix (region) does not match the device band Point the topic template at the correct sub-band
Events on a third-party platform, no telemetry in ThinkLink Forwarder MQTT subscription silently dies after restart Re-save the forwarder to force a resubscribe
Uplink jitter and frequent retransmission Profile wrongly set to Class C + ADR + DevStatusReq, sending surplus downlinks to a battery-powered Class A device Set the profile to Class A, disable ADR and DevStatusReq
AS Data page empty but telemetry normal Raw retention switch not enabled Enable real_time_storage on the device

The first one deserves its own paragraph. UDP 1700 is the highest-frequency trap in all of LoRaWAN troubleshooting, and the easiest place to fail at "I am sure I configured it" — because local testing usually has no security group at all, and the problem only appears after moving to cloud. The same logic appeared in issue 24 on BACnet: the Docker inbound port list does not include 47808 or 502, and the installer neither maps nor allows them, producing "the platform says the service is running, the other side cannot even scan it." Always audit the port list separately. Never trust installer defaults.

The fifth trap belongs to the same family: the connection is up, the process is alive, the configuration is correct, and no data arrives. There is no error to read; the only test is "re-save it and see whether it recovers." It is a reminder that running and working are two different states.


9. Compressing the funnel into 20 minutes

A real session does not require visiting every page. Work in time boxes:

0–3 min: confirm the gateway. Compare heartbeat time against now; check whether total online time has grown steadily over a long period. Ping once. If the gateway is offline, layers 2–5 are irrelevant.

3–8 min: confirm frames arrive. On NS Data, search with the right key: DevEUI before join, DevAddr after. Finding it means radio and backhaul both work and the problem is above; not finding it sends you straight to frequencies and ports.

8–13 min: confirm decryption. Search AS Data with the same identifier. Frames but no packets means the key or the profile's enable flag — those are the only two options.

13–17 min: confirm parsing. Read Realtime Data and the device Telemetry tab together. An advancing timestamp means it is alive; if not, paste the AS payload into the script editor and run it by hand.

17–20 min: confirm egress. Telemetry has values but downstream does not: check real_time_storage, whether the forward whitelist boxes are ticked, and whether {tenant} was substituted in the subscriber's topic.

Twenty minutes, and the fault lands on a specific criterion. What remains is fixing, not finding.


10. What this method actually solves

The tools are unremarkable: five screens, one MQTT client, one gateway web console.

What is not unremarkable is the order. The same five screens read in the right order are elimination. Read in the wrong order they are sampling. Sampling conclusions depend on luck, and luck is never sufficient on a fault site.

There is a secondary benefit. When you know exactly what proves each layer, you also know which observations cannot serve as conclusions. "Online" does not prove working. "Configured" does not prove running. "The script exists" does not prove the mapping is done. "There is a value on the page" does not prove the data was sent. Each of those four sentences maps to a specific trap described above.

The ceiling on troubleshooting skill is rarely about how many tools you know. It is about knowing which evidence does not count as evidence.


Further reading

  • Issue 21, ThinkLink Thing-Model Decoder Scripts in Practice: From Binary Payload to Readable Data — the deep dive for layer 4
  • Issue 22, ThinkLink Triggers and Alarm Rules in Practice — what happens after layer 5: turning data into notification
  • Issue 26, LoRaWAN Gateway Antenna Selection and Installation Guide — when layer 2 fails for lack of signal, start from the link budget
  • Issue 27, A/B Partitioning for LoRaWAN Gateway Firmware Updates — availability design on the gateway side

Official documentation

Top comments (0)