ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

STM32+ONENET农业物联网全链路实战:从传感器到小程序

STM32+ONENET农业物联网全链路实战:从传感器到小程序 简介本资源是一套面向嵌入式开发初学者与农业物联网实践者的鸡舍环境监测小程序完整源码聚焦STM32数据采集与ONENET云平台双向通信解决传统养鸡场温湿度、光照等参数依赖人工巡检、响应滞后的问题。压缩包共37个文件93KB含12个PNG界面图标、8个JSON配置文件如app.json、project.config.json、7个JS逻辑文件含设备通信与事件处理、5个WXSS样式文件及4个WXML模板文件结构清晰对应小程序标准分层架构便于理解页面渲染、数据绑定与云平台API对接机制。已有526人学习下载适合通过真实农业场景项目掌握微信小程序STM32IoT云平台协同开发的全流程。读者可直接部署运行获取完整的前端交互逻辑、ONENET数据上报/指令下发实现细节、以及适配养殖场景的UI布局与状态管理方案。1. 鸡舍环境监测不是“连上云就完事”STM32 ONENET 小程序的真实链路拆解你拿到这个upload.zip解压看到一堆.wxml、.js和 PNG 图片第一反应可能是“这是个微信小程序前端”但真正决定它能不能用的是藏在index.js里那几行 MQTT 连接逻辑和 STM32 固件中那个没写进 README 的 AT 指令序列。这不是一个纯前端项目——它是一条从鸡舍温湿度传感器出发经 STM32 数据预处理、NB-IoT/NB 模组透传、ONENET 平台规则引擎路由、再到小程序实时渲染的完整闭环。整个链路里任何一环参数错配比如 ONENET 设备鉴权用的是product_id device_name auth_info三元组而非仅 APIKey都会导致小程序首页显示“数据加载中…”卡死 30 秒后报错4001: Device not found。适合正在做农业物联网毕设的嵌入式初学者也适合需要快速复用通信模块的中小型养殖场技术员——你不需要重写 STM32 驱动但必须理解onenet_mqtt.c中MQTT_CONNECT报文 payload 的字段映射关系。2. STM32 端不是简单发数据而是按 ONENET 协议栈构造设备身份与上报帧ONENET 对接入设备有强身份约束STM32 侧不能只发原始传感器值。本项目采用 NB-IoT 模组常见为 BC95 或 EC20通过 AT 指令接入其固件逻辑核心在于onenet_mqtt.c中的onenet_connect()和onenet_post_data()函数。关键不在“连上”而在“连对”。2.1 设备注册与鉴权三元组才是 ONENET 的登录凭证ONENET 不接受裸设备直连。必须先在平台创建产品Product再为每个鸡舍设备生成唯一device_name如js_001_shidu并配置auth_info通常为设备 MAC 或 SN 的 MD5 截取前16位。product_id则是产品创建后分配的 10 位字符串如5F8A2B1C3D。这三者共同构成设备身份缺一不可。STM32 初始化时需将它们拼入 MQTT CONNECT 报文的client_id字段// onenet_mqtt.c 关键片段 char client_id[64]; sprintf(client_id, product_id%s,device_name%s,auth_info%s, PRODUCT_ID, DEVICE_NAME, AUTH_INFO); // 此 client_id 必须与 ONENET 平台设备管理页中“设备标识符”完全一致提示AUTH_INFO若填错例如误用明文 SN 而非 MD5 前16位ONENET 会返回CONNACK 0x05Connection Refused, Bad User Name or Password但串口日志可能只显示ATQMTCONN返回ERROR需用ATQMTSTAT?查看详细错误码。2.2 数据上报格式JSON 结构体必须匹配 ONENET 数据流定义ONENET 要求上报数据为标准 JSON且 key 名必须与平台创建的“数据流”名称严格一致。本项目监测项包括temperature、humidity、light_intensity、air_quality等对应 ONENET 产品下需提前创建同名数据流Datastream。STM32 构造上报 payload 时必须使用双引号包裹 key 和 string 值并确保数值类型正确// onenet_mqtt.c 数据封装示例 char post_payload[256]; sprintf(post_payload, {\temperature\:%.1f,\humidity\:%.1f,\light_intensity\:%d,\air_quality\:%d}, get_temp_value(), get_humi_value(), get_light_value(), get_aq_value()); // 注意浮点数保留1位小数整数不带小数点JSON 格式无空格2.2.1 ONENET 平台端数据流配置验证表数据流名称数据类型单位是否必填小程序绑定字段temperaturefloat℃是index.js中data.temphumidityfloat%是index.js中data.humilight_intensityintlux否index.js中data.lightair_qualityintAQI否index.js中data.aqwindow_statusint0/1否mine.js中data.window注意若 STM32 上报{temp:25.5}key 名为temp但 ONENET 平台数据流名为temperature该数据将被丢弃且无日志提示小程序永远收不到温度值。2.3 控制指令下发STM32 必须订阅/sys/{product_id}/{device_name}/thing/service/property/set小程序端下发“开窗”、“关灯”等指令本质是 ONENET 平台向设备 Topic 推送 MQTT 消息。STM32 必须主动订阅该 Topic并解析 JSON 中的params字段// 收到下行指令时的解析逻辑简化 if (strstr(topic, /thing/service/property/set)) { cJSON *root cJSON_Parse(payload); cJSON *params cJSON_GetObjectItem(root, params); if (cJSON_HasObjectItem(params, window_status)) { int win_cmd cJSON_GetObjectItem(params, window_status)-valueint; control_window(win_cmd); // 执行开窗/关窗动作 } if (cJSON_HasObjectItem(params, light_status)) { int light_cmd cJSON_GetObjectItem(params, light_status)-valueint; control_light(light_cmd); // 执行灯光开关 } cJSON_Delete(root); }2.3.1 下行指令 JSON 格式规范ONENET 物模型标准{ method: thing.service.property.set, params: { window_status: 1, light_status: 0 }, id: 123456789 }window_status和light_status必须与 ONENET 产品物模型中定义的服务属性名称完全一致STM32 解析时需忽略method和id字段只取params内容若订阅 Topic 错误如少写/sys/前缀指令将无法到达设备。3. ONENET 平台配置从产品创建到规则引擎绕不开的 5 个硬性步骤很多开发者卡在“小程序收不到数据”问题往往不出在代码而出在 ONENET 控制台配置漏项。本项目依赖平台基础能力必须完成以下五步缺一不可。3.1 创建产品并选择“MQTT”接入方式进入 ONENET 控制台 → “产品管理” → “创建产品”产品名称JiShe_Monitor_v1接入方式必须选“MQTT”非 HTTP/HTTPS因 STM32 使用 MQTT 协议数据格式JSON与 STM32 上报格式强制对齐安全认证三元组认证对应 STM32 中client_id构造逻辑提示若误选“HTTP”接入STM32 即使成功连接 MQTT BrokerONENET 也会拒绝接收数据控制台“设备列表”中设备状态始终为“未激活”。3.2 为每个鸡舍设备添加实例并严格填写三元组在产品下点击“设备管理” → “添加设备”设备名称device_namejs_001与 STM32 代码中DEVICE_NAME一致设备标识符auth_infoa1b2c3d4e5f6g7h816 位十六进制由设备 SN 计算得出产品 IDproduct_id自动填充不可修改添加后在设备详情页可查看“设备密钥”此密钥即auth_info必须与 STM32 固件中硬编码的值完全相同。3.3 定义数据流Datastream名称、类型、单位三位一体在设备详情页 → “数据流管理” → “添加数据流”数据流名称temperature小写字母无下划线与 STM32 上报 JSON key 一致数据类型float单位℃描述鸡舍温度用于小程序界面标注重复操作添加humidity、light_intensity、air_quality等数据流。注意ONENET 不允许数据流名称含空格或大写字母否则小程序解析失败。3.4 配置物模型Thing Model定义可下发的控制属性在产品管理页 → “物模型” → “编辑物模型”添加服务Servicecontrol_window输入参数window_status类型int取值范围0,1添加服务control_light输入参数light_status类型int取值范围0,1保存后ONENET 自动生成/sys/{pid}/{dn}/thing/service/property/setTopicSTM32 订阅此 Topic 才能收到指令。3.5 启用规则引擎将原始数据转为小程序可读的结构化消息ONENET 默认存储原始 JSON但小程序index.js中getDeviceData()请求的是/v1/device/data接口该接口返回的是平台加工后的结构化数据。需启用规则引擎实现字段映射规则名称jishemonitor_transform触发条件当设备上报temperature、humidity等数据流时处理逻辑SELECT temperature AS temp, humidity AS humi, light_intensity AS light, air_quality AS aq, window_status AS window, light_status AS light_ctrl FROM device_data输出目标API 服务供小程序调用提示若未启用规则引擎小程序调用/v1/device/data接口将返回空数组[]即使 STM32 已成功上报。4. 小程序端不只是 UI 渲染更是 ONENET API 的精准调用与状态同步小程序index.js并非简单轮询而是基于 ONENET RESTful API 实现双向通信。其核心在于app.js中全局onLaunch初始化 ONENET Token以及index.js中getDeviceData()与sendControlCmd()的请求构造。4.1 获取 ONENET Token小程序启动时必须完成的鉴权前置app.js中onLaunch函数调用getToken()向 ONENET/auth/token接口申请短期访问令牌有效期 2 小时// app.js getToken() { wx.request({ url: https://api.heclouds.com/auth/token, method: POST, data: { apikey: YOUR_ONENET_APIKEY, // 从 ONENET 控制台“APIKey 管理”获取 expire: 7200 // 2小时 }, header: { Content-Type: application/json }, success: (res) { if (res.data.code 200) { this.globalData.token res.data.data.token; // 存入全局变量 } } }); }注意apikey必须是 ONENET 平台创建的、拥有device:read和device:control权限的 Key。若权限不足后续所有 API 调用均返回403 Forbidden。4.2 实时数据拉取GET 请求必须携带 token 与设备标识index.js中getDeviceData()每 5 秒轮询一次请求地址为https://api.heclouds.com/devices/{device_id}/datapoints// index.js getDeviceData() { wx.request({ url: https://api.heclouds.com/devices/${this.data.deviceId}/datapoints, method: GET, header: { api-key: getApp().globalData.token // 注意此处是 token非 apikey }, data: { datastream_id: [temperature, humidity, light_intensity, air_quality], limit: 1 }, success: (res) { if (res.data.code 200 res.data.data.datapoints.length 0) { const dp res.data.data.datapoints[0]; this.setData({ temp: dp.temperature.value, humi: dp.humidity.value, light: dp.light_intensity.value, aq: dp.air_quality.value }); } } }); }4.2.1 关键参数说明表参数值说明urlhttps://api.heclouds.com/devices/{device_id}/datapoints{device_id}为 ONENET 设备详情页中“设备ID”12位数字非device_nameheader.api-keytoken字符串不是 apikey是app.js中申请的短期 tokendata.datastream_id数组[temperature,...]必须与 ONENET 数据流名称完全一致大小写敏感data.limit1仅取最新一条避免小程序渲染旧数据4.3 控制指令下发POST 请求需构造符合物模型的 JSON Bodymine.js中sendCommand()向 ONENET/devices/{device_id}/cmds接口发送控制命令// mine.js sendCommand(cmdType, value) { wx.request({ url: https://api.heclouds.com/devices/${this.data.deviceId}/cmds, method: POST, header: { api-key: getApp().globalData.token, Content-Type: application/json }, data: { cmd: { service_id: cmdType window ? control_window : control_light, paras: cmdType window ? { window_status: value } : { light_status: value } } }, success: (res) { if (res.data.code 200) { wx.showToast({ title: 指令已发送 }); } } }); }4.3.1 ONENET 控制指令 API 要求service_id必须与物模型中定义的服务名称一致如control_windowparas必须为对象key 名与物模型输入参数名一致如window_status若paras中 key 名错误如写成windowONENET 返回400 Bad Request错误信息为msg:invalid paras。5. 调试与排错从串口日志到小程序 Network 面板的四层定位法当小程序显示“暂无数据”或“控制失败”不要盲目改代码。按以下四层顺序排查90% 的问题可 10 分钟内定位。5.1 STM32 串口日志层确认物理连接与 AT 指令执行结果使用串口助手波特率 115200连接 STM32观察启动日志✅ 正常流程ATCGATT? → CGATT:1附着网络→ATQMTOPEN连接 MQTT→ATQMTCONN鉴权成功→ATQMTPUB发布成功❌ 典型失败CGATT:0NB 模组未注册到基站检查 SIM 卡、天线、APN本项目 APN 为cmnetQMTCONN: 0,12MQTT 连接被拒绝检查client_id三元组是否与 ONENET 设备信息一致QMTPUB: 0,3发布失败检查 Topic 名称应为/sys/{pid}/{dn}/thing/event/property/post。5.2 ONENET 控制台设备状态层验证数据是否抵达平台进入 ONENET 设备详情页 → “数据流” → 选择temperature若有时间轴折线图且最新点时间与 STM32 上报时间接近 → 数据已上云若折线图为空 → 检查 STM32 日志或平台“设备日志”需开启若折线图有数据但小程序无显示 → 问题在小程序端 API 调用。5.3 小程序开发者工具 Network 面板层抓包验证 API 请求真实性在微信开发者工具中打开 Network 面板触发getDeviceData()查看datapoints请求Status 200 但data.datapoints为空 → ONENET Token 权限不足或device_id错误Status 401 → Token 过期需重新调用getToken()Status 403 → APIKey 权限缺失需在 ONENET 控制台补授权查看cmds请求Status 200 → 指令已发至 ONENET等待设备响应Status 400 →paras字段不符合物模型检查service_id与paraskey 名。5.4 小程序 setData 层确认数据绑定与 WXML 渲染逻辑在index.jssuccess回调中插入断点success: (res) { console.log(raw response:, res); // 查看原始返回 if (res.data.code 200) { const dp res.data.data.datapoints[0]; console.log(parsed data:, dp); // 确认 dp.temperature 存在 this.setData({ temp: dp.temperature.value }); // 断点在此行 } }若console.log(parsed data)显示undefined→datapoints[0]为空问题在 API 层若setData后 WXML 中{{temp}}仍为空 → 检查index.wxml中是否遗漏style="width:16px;margin-left:4px;vertical-align:text-bottom;cursor:text;" />
返回列表