LoRaWAN 网络排障的五层漏斗:从"上不到云"到"数值不对",每一层该用什么工具证明

本文金额按 2026 年 10 月汇率基准换算:1 USD ≈ 6.71 元人民币,1 EUR ≈ 7.88 元人民币。文中人民币为折算值,原始报价币种保留。

排障现场最常见的浪费不是缺工具,而是猜。

现象只有一句话:"设备掉线了。"但这句话可以对应五种完全不同的故障:网关断电了、网关在线但没收到射频帧、帧收到了但解密失败了、解密成功但解析脚本报错了、解析成功了但数据没进库也没转发。这五种原因分布在不同层,用的工具、看的页面、判断依据全都不一样。

如果没有顺序,排查就会变成把每个页面都刷一遍,然后凭感觉挑一个最像的改。改完发现没用,再换一个。一天就这么过去了。

五层漏斗的意义是把"猜"换成"排除":每一层只需要回答一个问题,答"是"就往下走,答"否"就停在这一层。走完五层,故障位置必然被锁定在某一个格子里------不需要运气。


一、先看漏斗:五层对应五个不同的问题

层 要回答的问题 看哪里 关键判据
第一层 网关在不在线 运维管理 → 网关管理 在线状态、心跳时间、总在线时间三者是否自洽
第二层 网关有没有把帧交给 NS 网络数据 → NS 数据 能不能用 DevEUI / DevAddr 搜到记录
第三层 NS 有没有解密成功 网络数据 → AS 数据 同一时间点有没有对应的应用层报文
第四层 物模型有没有解析出来 应用数据 → 实时数据 / 设备详情 → 遥测数据 字段值和时间戳是否在推进
第五层 解析出来的数据去哪了 设备转发字段、原始报文留存开关、订阅端 是丢弃、是白名单过滤、还是订阅端自己没接

这张表是整篇文章的骨架。下面逐层展开,每层给三样东西:​看什么、怎么判、判错了会误伤什么​。

那个"判错了会误伤什么"是重点。绝大多数排障悲剧不是因为没看到现象,而是因为看到了现象、归错了因、然后把一个本来正常的配置改坏了------最典型的是为了"让数据快点上来"去动 ADR,结果把电池设备的功耗预算打穿。


二、第一层:网关在不在线

打开​运维管理 → 网关管理 ​,列表里每个网关会显示位置、类型、在线状态、启用状态、硬件版本、固件版本、LoRa 标准、​总在线时间、心跳时间和更新时间​。

多数人只看"在线状态"这一列。这一列恰恰是最容易骗人的。

三个时间字段必须一起读

  • 心跳时间:网关最后一次上报心跳的时刻
  • 总在线时间:累计在线的时长
  • 更新时间:这条记录被更新的时刻

一个网关可能显示"在线",但心跳时间停在四十分钟前。心跳周期默认 60 秒,四十分钟没心跳意味着这四十分钟里它其实什么都没在干,只是平台还没来得及把它标成离线------判离线是要满足时间条件的,不是收到消息才标在线。

反过来,总在线时间很短、心跳时间却在推进,说明这个网关在反复掉线重连。这是电源或网络抖动的典型特征,而不是信号问题。

判据:心跳时间与当前时间的差,应该小于两倍心跳周期。超过这个差值还在显示在线,就不要相信这一列,直接看心跳时间。

在线有三个口径,不能互换

这是 ThinkLink 上一个必须提前搞清楚的事。平台里"在线"至少有三种来源,各自的算法和适用边界都不一样:

状态来源 算法与边界
API 的 online 字段 心跳为空时视为在线;心跳非空时按活动时间和两倍心跳计算,缺活动时间判离线,截止时刻恰好相等已不算在线
server_attrs._online 数字 1 表示在线、0 表示离线;心跳为空且启用时为 1 且不建超时任务;心跳非空时要严格超过 active_time + 2 × heart_period 才判离线
MQTT 的 data.online 布尔值的​状态变化通知​,异步传播存在时差,不是完整状态快照,也不是报警事件

三条路径各自用在不同地方:页面展示、统计报表、自动化联动。混用的后果很具体------你在仪表盘上看的是 server_attrs._online,告警规则读的是 API 的 online,两个数不一致时,你会以为是数据错了,其实只是口径不同。

还有一个容易踩的坑:​关闭在离线通知后,_online 会保留最后一次的值​。那个残留的 0 或 1 不是当前状态的证据。看到 1 就认为设备活着,是错的。

网关离线了,先分清是"没电"还是"没网"

平台侧能做的验证有三件:

  • Ping:网关列表里 ManThink 类型网关的专用操作之一,用于确认平台到网关这条链路的可达性
  • 编辑 → 看 IP 地址:网关上线时会自动上报当前 IP,通过这个字段能确认它真实的网络地址,判断是不是 IP 变了、是不是跑到另一个网段去了
  • 状态趋势:点开能看指定时间范围内的在线/离线状态变化曲线,用来定位掉线的具体时段。这一步的价值在于把"一直掉"和"某个时刻开始掉"区分开------后者能直接对上施工、断电、雷击、换设备这些事件

如果 Ping 不通、IP 也是旧的或空的,问题就在网关侧或者它到服务器之间的网络,不在平台。这时候再去改平台配置是白费力气。


三、第二层:网关有没有把帧交给 NS

平台侧对应的是​网络数据 → NS 数据​。这一层看的是原始 LoRaWAN 帧,以及接收时的频率、RSSI、SNR、数据速率。

关键在于​用哪个标识去搜​,这一步选错,就会得出"NS 没收到"的错误结论:

情形 搜索字段 说明
设备从未入网,或刚复位 DevEUI Join Request / Join Accept 帧用 DevEUI 标识,此时还没有 DevAddr
设备已入网,正常上报 DevAddr 入网后所有数据帧使用动态分配的 DevAddr,不再用 DevEUI

一个刚出厂的设备搜 DevEUI 搜不到东西,但搜它的 DevAddr 也搜不到------因为它根本没有 DevAddr。反过来,一台正常运行的设备用 DevEUI 去搜普通上行,同样搜不到,因为数据帧里带的是 DevAddr。这两种"搜不到"都跟故障无关,纯粹是搜错了字段。

判据:NS 有数据 → 问题在 NS 以上(第三层开始)。NS 无数据 → 问题在射频或回传,不在平台。

NS 无数据时,往下查的方向是:

  • 设备到底在不在发(用串口或厂商工具看设备侧有没有发包日志)
  • 频点对不对(设备的信道计划和网关的区域标准是否一致)
  • 距离和遮挡(RSSI 与 SNR,但前提是至少有帧进来,没帧就没有信号可读)
  • 网关的回传通不通(Semtech UDP 的 1700 端口有没有放行------这条后面单独说,它是最高频的坑)

四、第三层:NS 有没有解密成功

平台侧对应的是​网络数据 → AS 数据 ​。AS 数据是 NS 把 LoRaWAN 帧解密、解帧之后 输出的结构化应用层报文:LoRaWAN 加密已经去掉,但 userdata.payload 仍然是 Base64 编码的业务负载,还没经过物模型解析。

这是排障最有信息量的一层。一条 AS 上行报文长这样:

{ "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 } }

每个字段在排障时都有明确用途:

字段 排障用途
moteeui 确认是哪台设备的报文,排除 EUI 填错的可能
gwrx[] 接收该帧的所有网关及各网关的 RSSI / LSNR。多网关收到同一帧时,取 RSSI 最好的那个作为链路质量判据
gwrx[].chan/rfch 信道号与射频链路口。频点配置错时,这里会暴露出来
gwrx[].lsnr/rssi SNR 与信号强度。SNR 是 LoRa 真正的余量指标,RSSI 只是参考
moteTx.datr 数据速率,如 SF7BW125。ADR 失效时这里会一直停在最低速率
moteTx.freq 上行频率。和网关配置的信道计划对不上就是频点问题
userdata.port FPort,用于区分业务通道
userdata.seqno 设备帧序号。​排查重发和丢包靠这个字段​------序号连续但时间间隔不均,是重传;序号跳号,是真的丢了
userdata.payload Base64 编码的业务负载,物模型脚本处理的就是它

NS 有数据、AS 为空,只有三种可能

官方的判断表非常干脆:

现象 可能原因 处理
NS 有数据,AS 为空 LoRaWAN 密钥错误(AppKey / AppSKey / NwkSKey 不匹配) 核对档案里的密钥,与设备烧录的一致
NS 有数据,AS 为空 设备档案没创建,或 enable 为 false 建档案,确认 enable 已开启
AS 有数据 解密成功,进入第四层 ---

"档案 enable 为 false"这条特别隐蔽。它不会报错,NS 就是不响应该设备,从外面看跟密钥错了完全一样。而且它是个​静默开关​------档案还在,密钥也在,只是不生效。

还有一条容易忽略的:​同一设备档案在同一时间只应在一个平台(云 / TKG / TKE)处于启用状态​,否则会入网冲突。设备在云上还挂着一份没关的档案,现场又接进边缘服务器,两台 NS 抢同一个 DevAddr,表现是"一会儿好一会儿不好",比彻底坏掉更难查。

FPort 不只是业务分类

平台上有几个 FPort 是约定好的:

FPort 用途
51 透传通道,原始字节直接透传
201 固件 OTA 升级
209 心跳包,用于确认设备在线状态
214 参数配置与读取

看到 209 端口的报文,别拿它当业务数据去解析------它是心跳,payload 里没有你要的测点。物模型脚本如果没按 port 分支,就会把心跳包也当业务数据解,解出一堆垃圾值。这是"字段值时不时变成一个奇怪的数"的常见来源。


五、第四层:物模型有没有解析出来

平台侧有两个入口:​应用数据 → 实时数据 ​,以及​设备详情 → 遥测数据 Tab​。

设备详情里的遥测数据是按物模型分组的,展示每个物模型最近一次解析后的全部字段值和时间戳,并且通过 MQTT 实时推送更新------不用手动刷新。这一点对排障很有用:如果打开页面盯着看三分钟,值和时间戳都不动,那说明解析侧停了;如果时间戳在动但某个字段是空的,那是脚本里那个字段没赋值。

AS 有数据、实时数据为空,只有两类原因

现象 可能原因 处理
AS 有数据,实时数据为空 设备未绑定物模型 在模型管理里为该设备绑定
AS 有数据,实时数据为空 物模型解析脚本有错误 打开物模型编辑器,把 AS 数据中的 payload 手动喂给解析器调试
AS 有数据,实时数据部分字段为空 脚本没覆盖对应的 FPort 或字段 检查脚本里对应 port 分支的逻辑

第三条"手动喂 payload"是整个排障体系里性价比最高的动作。脚本报错、返回 null、返回了结构不对的对象,绝大多数时候不用去现场、不用重启任何东西,复制一个 Base64 字符串进来就能复现。

脚本层的三个固定陷阱

写物模型脚本时有三件事会稳定地制造"解析不出来":

第一,脚本的唯一输出通道是返回值。 脚本运行在一个受限沙箱里,console.log 不可用、async 不可用,执行时间上限是 1000 毫秒。所有调试信息、所有结果,都只能通过 return 出来。指望用打印排错的人,会看到"什么都没输出",然后误判成"脚本没执行"。

第二,payload 要从 msg.userdata.payload 取,而且是 Base64。 直接把它当字符串处理,得到的是乱码而不是字节。要先用 Base64 解开再按字节读。

第三,脚本存在不等于映射完成。 脚本写好了、保存了、没报错,不代表这个字段已经进了遥测。字段有没有真正入库,只能在遥测数据 Tab 里看到值了才算数。这两件事在平台上是分开的状态,混为一谈就会在"我明明写了"和"为什么没数据"之间反复横跳。


六、第五层:数据去哪了

到这一层,AS 有报文、遥测有值,但下游还是说"没收到"。这时候问题不在采集链路,而在​数据出口​。

出口一:原始报文留存开关

有一条特别容易误判的现象:​AS 数据页是空的,但遥测数据正常​。

这不是矛盾。原始报文的留存是要单独开的------设备的 real_time_storage 开关。不开的时候,NS 收到帧、解密、走完物模型、写进遥测,全过程正常,只是这一层不给你留一份原始报文。

所以"AS 数据页看不到东西"有两种完全不同的含义:一种是信号没进来,一种是信号进来了但没留档。判断方法很简单------看遥测有没有值。有值,就是没开留存;没值,才是链路问题。

出口二:转发白名单

从 2.00.066 起,服务端属性和共享属性​默认不转发​,只发送勾选转发的字段。这是为防止无关字段把 MQTT 流量顶爆而设计的,但它的副作用是:你新加了一个服务端属性,页面上一看有值,MQTT 那边永远收不到------因为没勾。

同理,目标 Broker 断线​不缓冲、不补发​,也不提供 exactly-once。转发器不是消息队列,它是实时通道。订阅端重启的那几分钟里产生的数据,就是没有了。

出口三:订阅端自己的问题

MQTT 这一侧最常见的一句话是"我订阅了但没数据"。三个固定原因:

  • **{tenant} 没替换成组织账号**。订阅主题是 /v32/{tenant}/as/up/data/# 这种形态,{tenant} 是占位符,不是字面量。照抄不替换,能连上、能订阅成功、就是收不到
  • 订阅到的是已解析主题,但设备没配转发 。tkl/up/telemetry/# 是解析后的,as/up/data/# 是原始的,两条主题的数据来源不同,一条有数据不代表另一条也有
  • 收到后被脚本丢弃了 。转发脚本返回 null 就是丢弃,这既是过滤的正路,也是最常见的隐性故障

还有一个版本相关的事实值得记住:​登记订阅不等于订阅成功​,只有收到 SUBACK 才算真的订上了。2.00.070 之后这个语义是明确的,但排障时很多人还是把"界面上显示已配置"当成"已经在收数据"。

分页加载带来的假空值

2.00.075 起,配置、属性、遥测是按需要分别读取的。切页签之后要先等加载成功再编辑保存,​未加载的字段不能当作空值​。MQTT 消息也可能只含白名单里的属性子集,接收端应该按字段合并,而不是整组覆盖。

这两条加在一起会造成一种很别扭的故障:保存的时候把一个"没加载出来"的字段写成了空,或者接收端拿一条不完整的消息覆盖了完整的状态。表现是"某个配置自己变回去了",而且很难复现。


七、现场三件套:不用登平台也能查

平台侧解决的是"哪一层坏了",现场侧解决的是"这一层为什么坏"。三样东西足够。

一、网关本地 Web 控制台

GD6 系列走的是 gwTool6 Web 控制台。几个关键入口:

  • Ethernet 默认 DHCP,在同一网段直接访问网关 IP 就能进
  • **Wi-Fi AP 的 SSID 是 GD6-<Gateway EUI>**,只有在没有有效 EUI 时才回退成 LGW-Config
  • **出厂热点密码与 Web 密码都是 wifi!0804**,两项独立管理,首次登录后应当改掉

现场没网的时候,手机连上这个热点就能进配置页,这是最省事的一条路。要注意的是热点必须真的出现并且能连上访问,才算这条路径可用------"界面上显示已开启"不算。

Radio 页的频点、NS 页的服务器地址和端口、以及"保存并重启生效"这个确认动作,都在这里。有一个操作细节值得写下来:​页底的"提交"才保存 Radio,NS 页面用的是"保存并重启生效",等服务恢复后要重新读取核对​。改完不重新读取,你看到的是表单里的旧值,不是设备里的当前值。

顺便说一个 GD62 的边界:​它的外部没有 REC 按键 ​,所以任何"长按 REC 约两秒"的恢复步骤对这台机器都不成立。需要恢复出厂要走技术支持流程,恢复后 Web 默认密码回到 wifi!0804,而​已写入的 Gateway EUI 会保留​。

二、一个 MQTT 客户端

不管是 mosquitto_sub、MQTTX 还是自己写的脚本,订阅一次 /v32/{tenant}/as/up/data/#,你会直接看到所有设备的原始上行。这是判断"到底有没有数据在流动"最快的方式,比翻页面快得多。

看的时候关注三件事:有没有报文、报文的 moteeui 是不是你要的那台、userdata.seqno 是不是在连续推进。第三条能立刻区分"设备没发"和"设备发了但没到"。

三、一条能算账的现场流程

排障的时间成本是可以量化的。一次现场上门的综合成本(人工加差旅)在国内一线城市大约 300--800 元,海外按公开的服务报价大约 150--400 美元(约 1,000--2,700 元)。这个数字的意义不在于精确,而在于它给"远程能不能解决"定了个价:如果一个问题能靠远程把五层走一遍定位到具体某一层,那么上门就变成了"去换那个件",而不是"去现场看看什么问题"------这两者的时间差通常是半天和三天。

更贵的是误判。为了"让数据快点上来"去关掉 ADR,电池设备的功耗会在一周内被打穿;这种损失现场看不出来,三个月后才以"电池不耐用"的形式出现,而那时已经很难回溯到当初改了哪一项。


八、七条真机联调踩出来的坑

下面这七条来自实际联调记录,不是理论推演。它们共同的特征是:​现象都在平台侧,根因都在平台之外​。

现象 根因 处理
收不到任何网关 UDP 安全组只放行了 TCP,没放 UDP 1700 加 UDP 1700 入站规则
网关发包被拒(非法包 / 单字节) 网关用的不是 Semtech UDP,或者走了私有协议 把 nsProtocol 改成 pkt_fwd
收到包但不上行 nsAddress 前面有前导空格 去掉空格并重启服务
网关连上了但帧被丢 桥接前缀(region)与设备频段不符 把主题模板改成正确的子带
第三方平台有事件,ThinkLink 无遥测 转发器 MQTT 订阅在重启后假死 重新保存该转发器,强制重订阅
设备上行抖动、频繁重传 档案误设成 Class C + ADR + DevStatusReq,给电池 Class A 设备发了多余下行 档案改 Class A,关掉 ADR 与 DevStatusReq
AS 数据页空但遥测正常 原始报文留存开关没开 按需开启设备的 real_time_storage

第一条(UDP 1700)值得单独强调。它是所有排障里出现频率最高、也最容易在"我以为我配好了"这一步翻车的一条------因为本地测试时往往没有安全组,上云之后才出现。同样的逻辑在第 24 期讲 BACnet 时也出现过:Docker 手册的入站端口清单里不含 47808 和 502,安装器不建映射也不放行,表现是"平台显示服务在跑,对面就是扫不到"。端口清单永远要单独核对一遍,不要相信安装器的默认行为。

第五条(转发器订阅假死)也是同类问题:连接是通的、进程是活的、配置是对的,就是不收数据。这类故障没有报错,只能靠"重新保存一遍看会不会恢复"来验证。它提醒我们一件事------​**"运行中"和"工作中"是两个状态**​。


九、把五层压缩进 20 分钟

真正的排障不需要把所有页面刷一遍。按时间盒走:

0--3 分钟:确认网关。 看心跳时间和当前时间的差,看总在线时间是否在长时间稳定增长。Ping 一次。如果网关就不在线,后面四层都不用看。

3--8 分钟:确认帧进来了没有。 NS 数据页,用正确的那把钥匙搜:未入网搜 DevEUI,已入网搜 DevAddr。搜到就说明射频和回传都通,问题在上面;搜不到就直接转去查频点和端口。

8--13 分钟:确认解密。 AS 数据页用同一个标识搜。有帧无报文,就是密钥或档案 enable,只有这两种。

13--17 分钟:确认解析。 实时数据页和设备详情的遥测 Tab 一起看。时间戳在动就是活的;不动就把 AS 的 payload 复制到脚本编辑器里手动跑一遍。

17--20 分钟:确认出口。 遥测有值但下游没有,查三件事:real_time_storage、转发白名单有没有勾、订阅端主题里的 {tenant} 换没换。

二十分钟走完,故障一定落在某一条判据上。剩下的是修,不是找。


十、这套方法真正解决的是什么

工具本身不复杂:五个页面、一个 MQTT 客户端、一个网关 Web 控制台。

复杂的是​顺序​。同样的五个页面,按对的顺序看是排除法,按错的顺序看是抽样调查。抽样调查的结论依赖运气,而运气在故障现场从来不够用。

还有一个附带的好处:当你清楚地知道每一层用什么证明时,你也就知道了​哪些现象不能作为结论​。"在线"不能证明在工作,"有配置"不能证明在运行,"脚本存在"不能证明映射完成,"页面上有值"不能证明数据发出去了。这四句话每一句都对应上面某一层的一个具体陷阱。

排障能力的上限,往往不在"知道多少工具",而在"知道哪些证据不算证据"。


官方手册

相关推荐
计算机魔术师1 小时前
Cursor 官方插件商城现恶意 Claude Code 扩展,约 11 小时影响最多 5000 名开发者
前端
掘金者阿豪1 小时前
MongoDB迁移到金仓数据库:商品属性继续灵活存,订单报表交给 SQL
前端·后端
栉甜1 小时前
Vue2学习内容(上)
前端·javascript·vue.js·学习
梦曦i2 小时前
v0.2.0 架构升级:class + reactive 重塑状态管理
前端·uni-app x
布丁爱干饭2 小时前
寄给午后
前端
java1234_小锋2 小时前
【技术专题】CSS3 网页技术 - CSS3 布局进阶
前端·css·css3
zhippt_10142 小时前
只剩截图没有源文件?图转PPT把图片还原成可编辑幻灯片
前端·人工智能·powerpoint
shmily麻瓜小菜鸡3 小时前
TypeScript 知识点补充(第三部分:工程化与高级类型)
前端·javascript·typescript
10岁的博客3 小时前
Chrome 扩展安全攻防实录:从权限模型到真实漏洞复盘
前端·chrome·安全