
去年底接手了一个市政相关的物联网项目要把辖区内两千多个井盖纳入远程监测。传统人工巡检在这种体量面前基本失效井盖移位、被盗、井内水位上涨、污水井可燃气体超标个个都是实打实的安全隐患。我们最终定的方案组合是合宙 AIR780E Cat1-4G 模组做数据采集和传输走腾讯云 MQTT 通道接入平台再用微信小程序做管理端应用。这篇是连载的第一篇会把整体架构、硬件选型逻辑、AIR780E 开发环境搭建、腾讯云物联网平台的产品配置以及第一次把真实数据从模组上报到云端的全过程讲清楚。适合正准备做类似 Cat1 物联网项目的开发者、做毕业设计的学生以及想了解智能井盖系统设计细节的同行参考。1. 井盖监测的真实痛点为什么这个系统值钱1.1 雨天内涝、井盖偷盗、燃气超标需求从哪来很多人第一次听到智能井盖这四个字第一反应是井盖有什么好监测的。但真正下到现场待几天你会发现这个看似不起眼的基础设施牵扯的安全问题远比想象中复杂。第一个场景是城市内涝。暴雨天排水不及时井内压力增大井盖会被水流顶起甚至冲走。这时候路面出现一个敞口井行人和车辆根本来不及反应每年都有类似事故见诸报道。第二个场景是井盖偷盗和异动。电缆井、通讯井里的线缆有回收价值某些路段井盖丢失率常年下不来靠人工巡检发现往往已经过了几天。第三个场景是井下环境风险。污水管网在雨天的水位会暴涨化粪池和污水井里会积聚硫化氢、甲烷这类气体巡检人员下井前完全不知道里面的情况贸然开盖作业有中毒和爆炸风险。我们项目的核心诉求就是把这几个场景变成可远程感知的状态。井盖是否倾斜、是否被移动、井内水位到了什么高度、可燃气体浓度是否超标这些数据过去只能靠人跑现场确认现在要让设备在十几秒内主动上报。和甲方开需求会的时候对方说了一句很直白的话我们要的不是科技感是出事前能给我们留出反应时间。这句话基本定了这个系统的设计基调——稳定、低功耗、可运维比花哨功能重要得多。1.2 把需求翻译成可上报的指标清单需求会开完接下来要做的是把业务语言翻译成技术指标。这一步很关键因为传感器选型、上报频率、告警阈值、电池容量核算全部依赖这张指标清单。我们最终锁定了四类监测项。第一是井盖倾斜角度用倾角传感器采集正常状态是 0 度附近大于等于 10 度判定为偏移异常第二是开盖/移位状态井盖与井座之间安装干簧管和磁铁井盖被拿起时磁力消失触发信号第三是井内水位根据井深设置两个告警档位高水位和淹没水位第四是可燃气体和硫化氢浓度这个主要针对污水井和化粪池井用气体传感器配合 ADC 读取浓度值。除了监测项本身还有一组系统级指标要定下来。平时设备每天上报一次心跳包含电量、信号强度和基础状态一旦触发告警设备要能在 15 秒内完成唤醒、联网、上报全流程。电池在标准 1 小时/天的上报频率下目标续航不低于 18 个月。这些指标直接决定了后面的硬件选型和代码逻辑怎么设计也决定了成本上限。1.3 小程序端要解决的最后一公里问题设备能上报数据只是前半段数据最终要能被人看到和处理才算闭环。在这个项目里使用方不是专业 IT 人员而是市政巡查队员和中控室值班员他们对打开电脑进后台这件事有天然的抵触。微信小程序在这里的优势就很明显扫码即用、不需要安装、告警能通过订阅消息推送到手机分享位置也方便。小程序端我们规划了三个核心页面地图总览页、设备详情页、告警处理页。地图上按颜色区分井盖状态绿色正常、黄色预警、红色告警点击设备图标进入详情页能看到最近一次上报的各项数据和信号强度告警处理页则记录了每条告警的状态流转从触发、确认到销警。这套页面交互在连载后面几篇会展开讲连载01阶段只做需求拆解和界面原型确认先把上层应用要消费的数据结构定下来。2. 技术选型复盘Cat1、腾讯云 MQTT、小程序是怎么凑到一起的2.1 Cat1 与 NB-IoT、普通 4G 的取舍核心通信模组选型是项目一开始就卡住的问题。当时在 NB-IoT、Cat.1、普通 4G 三者之间对比了很久最终选了 Cat.1也就是 AIR780E 所属的类别。先看 NB-IoT。它的优势是低功耗、深覆盖单点成本也很低特别适合水表、气表这类固定位置、极小流量的场景。但放在井盖上有个尴尬的地方NB-IoT 网络在部分区域的覆盖和基站容量并不理想尤其是地下井内这种遮挡环境一旦信号弱上行速率和数据量都受限。而且 NB-IoT 对移动性支持较差如果井盖被偷走运输很难实时追踪位置。普通 4G 模组倒是没有速率和覆盖问题但成本和功耗都偏高。井盖项目的上行数据量很小一次上报撑死几百字节用全速 4G 属于杀鸡用牛刀而且持续连接时的待机电流对电池方案很不友好。Cat.1 正好卡在中间。它复用 4G 基站覆盖成熟下行速率 10Mbps、上行 5Mbps 左右对传感数据上报绰绰有余成本已经打到和 NB-IoT 接近的区间更重要的是Cat.1 支持 LTE 网络上的语音和移动性功耗比普通 4G 低不少还支持 PSM 省电模式。对井盖这种低速、小数据、需要一定实时性和移动性的场景Cat.1 是目前最均衡的选择。2.2 为什么不上自建 MQTT 服务器通信协议层面MQTT 没什么争议本身就是为物联网设备接入设计的轻量消息协议基于 TCP支持 QoS 分级、遗嘱消息、订阅发布模型非常适合网络不稳定、数据量小的设备端。真正需要决策的是自己部署一套 MQTT Broker还是直接用云厂商的物联网平台。自建方案我们其实认真评估过。用 EMQX 或者 Mosquitto 搭一个 Broker 并不复杂甚至跑在一台 2C4G 的小服务器上就能支撑不少设备。但问题在于井盖项目不只需要一个消息通道还需要设备管理、数据解析、日志查询、告警规则、后续的推送服务。这些东西如果全部自己开发周期会拉得很长而甲方那边给的时间并不充裕。腾讯云物联网开发平台在这一点上帮我们省了很多事。产品、设备、Topic、数据模板都是可视化操作设备上报的数据能在控制台直接看到日志还带规则引擎可以把数据转发到其他服务。开发阶段用的免费额度对两千设备量的验证完全够用。另外项目应用端已经确定做微信小程序腾讯云的生态衔接也更顺少了一套跨厂商的对接成本。2.3 小程序作为应用端的三点理由为什么不做 App这个问题甲方自己就回答了——让市政巡查人员为了看井盖专门装一个 App这个推行的隐性成本太高。微信小程序天然免安装扫码即可进入对非技术背景的用户非常友好。第一分发成本低。小程序不需要上各应用商店审核也不用考虑 Android 和 iOS 两套包体微信内搜索、扫码、分享都能进入。第二告警推送链路顺。微信订阅消息能力允许服务端主动给用户推送告警通知用户不打开小程序也能收到提醒这对发现井盖异动这类时效性强的场景非常重要。第三定位和扫码能力可复用。巡查人员到现场后可以直接在小程序里调起地图定位扫描井盖上的 NFC 标签或二维码进行设备绑定和确认操作整个现场作业流程都能在微信里闭环。当然小程序也有它的限制比如包体大小、部分 API 需要企业认证、域名需要 HTTPS 备案但这些在项目初期规划好都能接受。而且腾讯云 IoT 平台本身有配套的小程序端 SDK 和服务端 API后续对接省了很多弯路。3. 连载01硬件部分AIR780E 组装、供电与开发环境3.1 AIR780E 核心板和传感器选型清单AIR780E 是合宙推出的 Cat.1 模组基于紫光展锐的芯片方案封装尺寸小引脚覆盖了 UART、GPIO、I2C、SPI、ADC 等常用外设。它支持两种开发方式一种是传统的 AT 指令适合快速验证另一种是合宙自研的 LuatOS 方案直接用 Lua 脚本跑在模组内部省掉外部 MCU。井盖项目资源紧凑我们用 LuatOS 方案一块模组同时承担采集和上报不额外挂单片机。传感器选型上倾角检测我们最初用滚珠倾斜开关做验证后来换成六轴姿态传感器 MPU6050通过 I2C 读取三轴加速度数据在软件里换算倾斜角。这样阈值可以灵活配置比物理开关更可靠。开盖检测用干簧管加磁铁井盖盖上时磁铁靠近干簧管触点闭合井盖被抬起或移位触点断开对应 GPIO 产生中断唤醒设备。水位检测根据井深选了浮球开关和投入式压力传感器两版浮球版便宜适合浅井压力版精度高适合需要看连续水位的检查井。气体传感器只在对口污水井部署用 MQ-4 甲烷传感器和 MQ-136 硫化氢传感器输出模拟量接模组 ADC 读取。注意气体传感器加热电流比较大百毫安级别不能直接从模组引脚供电一定要用 MOS 管做电源开关只在采样时通电采样完立刻断电否则电池续航会非常难看。3.2 电池与低功耗设计井盖没有插座的现实井盖分布在马路中间不可能拉市电供电方案是整个硬件设计里最需要抠细节的地方。我们选了锂亚硫酰氯电池型号 ER34615标称电压 3.6V单节容量大约 19000mAh。这种电池的自放电率低、工作温度范围宽非常适合长时间小电流放电的场景。但它有个特性大电流持续放电能力弱而 Cat.1 模组在联网发送时瞬态电流可能冲到 1-2A 的峰值。所以必须在电池输出端并联大容量电容或者小容量锂电池做缓冲否则电压会被瞬间拉垮模组直接重启。功耗预算大致是这样的AIR780E 在 PSM 模式下静态电流能压到微安级别基本可以忽略设备被事件唤醒后初始化加网络注册大约需要 5 到 10 秒期间平均电流 30mA 左右MQTT 连接和上报数据只有一两秒峰值电流 150mA 以上一次完整唤醒上报流程大概消耗 0.3mAh。按每天一次心跳加上偶尔的告警上报来算一节 ER34615 支撑一年左右两节并联可以做到 18 到 24 个月达到了甲方的续航要求。低功耗的另外一个关键是能睡就睡。AIR780E 的 LuatOS 支持进入休眠状态由 GPIO 中断或者定时器唤醒。我们的逻辑是平时设备大部分时间处于 PSM 休眠心跳由定时器触发告警由干簧管、倾角传感器的中断引脚触发。这样既保证了实时性又把平均功耗压到最低。3.3 LuatOS 开发环境搭建与烧录AIR780E 的开发工具链很轻量。我用的组合是 VSCode 加合宙的 LuatOS 插件配合官方烧录工具把固件和脚本烧进模组。第一步是拿到两个文件一个是固件包AIR780E 的 LuatOS 固件是AirM2M_LuatOS系列的 soc 文件另一个是用户脚本工程官方 GitHub 和文档中心都有基础模板。脚本工程的核心入口是main.lua里面通过sys.taskInit启动各个任务运行时依赖的库文件像sys.lua、mqtt.lua都会一并打包。第二步是接线。开发阶段用 USB 转 TTL 串口模块连接 AIR780E 核心板的调试串口注意电平匹配AIR780E 的串口是 1.8V 电平不能用普通 5V 的 USB-TTL 直接怼否则大概率烧坏模组 IO。这点后面踩坑记录里会详细说。第三步是烧录。把固件和脚本通过官方烧录工具下载到模组工具会识别串口并写入。烧录完成后在串口终端里应该能看到 LuatOS 的启动日志版本信息、内存大小、基站注册状态都会打印出来。看到日志正常滚动说明开发环境已经打通。3.4 首次上电自检AT 指令验证模组与网络虽然 LuatOS 下主要用 Lua 脚本开发但首次上电我建议先用 AT 模式或者串口交互验证一下硬件和网络这一步能帮你把问题范围缩小。AIR780E 支持切到 AT 模式用串口工具发送指令几个关键检查项如下AT 指令含义期望结果AT基本通信测试OKATCCID读取 SIM 卡 ICCID20 位卡号ATCSQ查询信号强度返回 99 表示无信号10-31 之间可用ATCGATT?查询网络附着状态返回 1 表示已附着ATCEREG?查询 LTE 注册状态返回 0,1 表示已注册我当时拿到板子第一件事就是插卡、接天线、上电。信号强度这个数值尤其重要井盖项目设备埋在地面以下井盖本身如果是铸铁的对 4G 信号有很明显的屏蔽作用。实测在空旷路面信号能到ATCSQ返回 20 以上但一旦放进井里、盖上铁盖直接掉到个位数。这个问题不解决后面所有链路都是白搭。天线选型上我们最终用了外置的棒状天线或者带有磁性底座的吸盘天线尽量让天线贴近非金属井盖位置或者通过井盖上的预留孔引出。如果井盖材质是复合材料信号问题会好很多铸铁井盖就必须提前设计天线引出方案。4. 腾讯云物联网平台配置产品、设备和 Topic 规划4.1 创建产品与设备拿到三元组云平台侧的第一步是在腾讯云物联网开发平台创建产品和设备。登录控制台后进入物联网开发平台新建产品。需要选几个关键属性产品名称我们填了智能井盖;节点类型选直连设备;认证方式选密钥认证;数据格式按设备端和平台交互的方式选自定义或物模型井盖项目由于既有标准物模型能力又需要一些自定义字段我们最终选了 JSON 格式配合物模型定义。产品创建完成后在产品下添加设备。每个设备会分配一组唯一的三元组信息ProductID产品 ID、DeviceName设备名称、DeviceSecret设备密钥。这三个值在设备端连接平台时用于 MQTT 鉴权要保存好。提示开发阶段建议把设备的DeviceName编成有业务含义的编号比如井盖的片区加井号例如HD-001、XM-023。后面维护设备列表、排查问题时能少死很多脑细胞。平台的免费额度对开发来说够用。创建设备后控制台会生成该设备的接入指引包含 MQTT broker 地址、端口号和 Topic 示例信息。即使不细读文档照着控制台给的接入参数都能连上这点对新手非常友好。4.2 数据模板与 Topic 设计腾讯云物联网平台在设备通信上有一套标准的 Topic 语义。设备上报数据走的是事件 Topic格式类似$thing/up/event/{ProductID}/{DeviceName}平台下发指令走的是$thing/down/command/{ProductID}/{DeviceName}。我们自己一般不另起自定义 Topic尽量用平台标准语义因为后续数据解析、规则引擎转发、云日志查询都能直接复用不需要额外写代码。数据模板的设计其实就是物模型的设计。井盖项目的物模型我们定义了这样几个属性tilt_angle倾斜角度整数型单位 0.1 度、water_level水位档位枚举型0 正常、1 高水位、2 淹没、gas_concentration气体浓度浮点型单位 ppm、battery_voltage电池电压浮点型单位 V、signal_strength信号强度整数型单位 dBm、cover_status井盖开合状态布尔型或整数型。物模型定义好之后设备上报的 JSON 数据会和这些属性一一对应。这样一个小程序端拿到数据后不需要关心底层 Topic直接读属性值即可展示非常干净。4.3 MQTT 鉴权方式与连接参数说明设备和腾讯云物联网平台建立 MQTT 连接时用的是设备密钥鉴权即通过DeviceSecret计算签名。连接参数大致如下Broker 地址控制台接入信息里给出的 MQTT 域名根据平台实例所属地域而定非加密端口1883加密端口8883TLS或443WebSocketClient ID{ProductID}{DeviceName}Username{ProductID}{DeviceName};hmacsha256;{timestamp}Password对一段规范化字符串做 HMAC-SHA256 计算密钥为DeviceSecret签名字符串的格式大致是把clientId、deviceName、productId、timestamp拼接成一个查询串然后计算摘要。需要注意拼接顺序和大小写很多鉴权失败的问题都出在这两个细节上。这个部分连载02我会专门写一版完整可用的工具代码先把坑点放在这里。按我的经验云平台接入最容易出的问题有三个时间戳和服务端不同步导致签名验证失败三元组复制时带了不可见空格设备端用的 TLS 证书过期或者没加载根证书。开发阶段建议先用 1883 明文端口把链路跑通确认代码逻辑没问题后再切 8883 加密端口。5. 第一次打通数据链路从模组发一条真实数据到云端5.1 LuatOS 里的 MQTT 连接骨架代码AIR780E 的 LuatOS 固件里已经集成了 MQTT 库连接流程不算复杂。核心逻辑是先确认网络就绪然后创建 MQTT 客户端配置鉴权参数连接后订阅下行 Topic。下面这个代码骨架是连载01阶段验证用的签名计算部分做了简化实际使用时需要按平台文档严格实现-- LuatOS MQTT 连接骨架连载01验证版 local mqtt require(mqtt) sys.taskInit(function() -- 等待网络就绪 while not socket.isReady() do sys.wait(1000) end log.info(net, network ready) local productId YOUR_PRODUCT_ID local deviceName YOUR_DEVICE_NAME local deviceSecret YOUR_DEVICE_SECRET -- 计算鉴权参数这里只表示流程实际签名格式以平台接入文档为准 local timestamp string.format(%d, os.time()) local clientId productId .. deviceName local content string.format( clientId%sdeviceName%sproductId%stimestamp%s, clientId, deviceName, productId, timestamp ) local password crypto.hmac_sha256(deviceSecret, content) local username string.format(%s;hmacsha256;%s, clientId, timestamp) -- 创建连接 local mqttClient mqtt.create(net, clientId, 1883) mqttClient:auth(username, password) mqttClient:keepalive(120) mqttClient:on(function(mqtt_c, event, data, payload) if event mqtt.EVENT_CONNECTED then log.info(mqtt, connect ok) local topic $thing/down/command/ .. productId .. / .. deviceName mqtt_c:subscribe(topic) elseif event mqtt.EVENT_DATA then log.info(mqtt, receive, data, payload) end end) mqttClient:connect() end)这段代码不是完整可上线的版本但骨架是对的。它把等待网络、计算鉴权、建立连接、订阅指令这条主线串起来了。开发阶段建议先用控制台创建设备时的接入参数手动跑一遍再把参数替换成代码里的变量。5.2 上报 JSON 格式设计和物模型映射设备连接成功后接下来是上报数据。上报的数据格式要和物模型定义保持一致平台会按物模型做解析。我们设计的上报内容结构如下{ method: report, clientToken: clientToken_001, timestamp: 1735642800, params: { tilt_angle: 2, water_level: 0, gas_concentration: 0.0, battery_voltage: 3.58, signal_strength: -87, cover_status: 0 } }method固定为report表示这是一次属性上报params里是具体的属性键值对。设备端先用 JSON 库构造这个表再转成字符串发送到事件 Topic。clientToken是请求标识平台回复消息时会把同一个 token 带回来设备端可以据此匹配消息。采集端这边MPU6050 读到的三轴加速度要先做低通滤波和倾斜角换算直接拿原始值上报没有意义。水位档位通过 GPIO 电平判断气体浓度通过 ADC 采样值和标定曲线换算成 ppm电池电压用模组内部的 ADC 通道采集。这些采集逻辑在连载后续会拆开讲连载01阶段先保证数据结构能跑通。5.3 云端日志验证看到第一条数据就是里程碑数据上报后怎么确认平台真的收到了最直接的方法是在腾讯云物联网开发平台的设备详情页看日志。设备上报的消息、平台下发的消息、设备上下线事件都会记录在设备日志或消息日志里。我第一次在控制台看到这条 JSON 数据从井盖模组出现在云端日志里时整个系统的地基算是打好了。验证通过的标准大概是三件事日志里能看到设备上报的消息内容且字段和物模型匹配无解析异常设备在线状态显示为在线并且下线后能正常显示离线平台下发的测试指令能被设备端订阅收到并打印日志。这三件事都成立端到云的链路就闭环了。如果日志里看不到数据排查顺序是先看串口日志确认模组有没有成功发出 MQTT publish再看鉴权有没有通过最后看 Topic 路径是否写错。绝大多数连不上的情况在这三步内都能找到原因。6. 连载01踩坑记录与下一步计划6.1 这阶段踩过的三个硬坑第一个坑是串口电平不匹配。开发板自带的调试串口是 1.8V 电平我用了一根常见的 5V USB 转 TTL 线直接接上去结果串口完全乱码查了半天才发现是电平问题。后面换用支持 1.8V 电平的串口工具才正常。新手如果遇到模组串口输出乱码或者无输出先别怀疑模组坏了看看电平匹配这一项。第二个坑是铸铁井盖对信号的屏蔽。最初我们把整机装进标准铸铁井盖的井下模块上报成功率骤降信号强度掉到 -110dBm 以下经常超时。后来改成外置天线并尽量让天线贴近井盖边缘的非金属密封圈位置信号才稳下来。任何做井下设备的项目天线位置务必在第一轮样机就实测不要拿桌面测试结果当依据。第三个坑是腾讯云平台鉴权时的大小写和拼接顺序。用设备密钥生成 HMAC-SHA256 签名时参数名大小写错了、timestamp 不是 32 位整数字符串、字符串里多了换行符平台都会静默拒绝连接。这类问题最坑的地方在于控制台不给你明确错误原因只能靠对比日志。我的经验是先把签名算法在本地用 Python 或者在线工具算一遍确认和平台文档示例一致后再移植到 Lua 代码里。6.2 下期内容规则引擎、小程序地图与告警闭环连载01到目前为止硬件侧、云平台侧和第一条数据链路已经跑通但这只是整个系统的骨架。井盖数据如果只是躺在云端的日志里没有任何实际价值接下来要做的才是真正产生业务价值的环节。下一期我计划重点讲三块内容一是腾讯云规则引擎的配置怎么把设备上报的数据转发到其他服务比如写入数据库、触发告警通知二是微信小程序的完整开发流程地图打点、设备状态展示、扫码绑定以及和腾讯云 IoT 平台 API 的对接方式三是告警闭环逻辑从设备上报异常到小程序订阅消息推送到巡查人员手机的完整链路实现。做这个连载的初衷是发现很多物联网教程都停留在单个模组连上云的阶段但真正交付一个能用的系统难点往往在数据怎么用、告警怎么推、现场怎么操作这些工程细节上。我自己在项目推进过程中也走了不少弯路把这些过程记录下来希望后面做类似选型——Cat.1 模组加云平台加小程序的同行能少踩几个坑。