简介:本资源是一套面向嵌入式开发初学者与农业物联网实践者的鸡舍环境监测小程序完整源码,聚焦STM32数据采集与ONENET云平台双向通信,解决传统养鸡场温湿度、光照等参数依赖人工巡检、响应滞后的问题。压缩包共37个文件(93KB),含12个PNG界面图标、8个JSON配置文件(如app.json、project.config.json)、7个JS逻辑文件(含设备通信与事件处理)、5个WXSS样式文件及4个WXML模板文件,结构清晰对应小程序标准分层架构,便于理解页面渲染、数据绑定与云平台API对接机制。已有526人学习下载,适合通过真实农业场景项目掌握微信小程序+STM32+IoT云平台协同开发的全流程。读者可直接部署运行,获取完整的前端交互逻辑、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 0x05(Connection Refused, Bad User Name or Password),但串口日志可能只显示AT+QMTCONN返回ERROR,需用AT+QMTSTAT?查看详细错误码。
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 平台端数据流配置验证表
| 数据流名称 | 数据类型 | 单位 | 是否必填 | 小程序绑定字段 |
|---|---|---|---|---|
temperature | float | ℃ | 是 | index.js中data.temp |
humidity | float | % | 是 | index.js中data.humi |
light_intensity | int | lux | 否 | index.js中data.light |
air_quality | int | AQI | 否 | index.js中data.aq |
window_status | int | 0/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 Broker,ONENET 也会拒绝接收数据,控制台“设备列表”中设备状态始终为“未激活”。
3.2 为每个鸡舍设备添加实例,并严格填写三元组
在产品下点击“设备管理” → “添加设备”:
- 设备名称(device_name):
js_001(与 STM32 代码中DEVICE_NAME一致) - 设备标识符(auth_info):
a1b2c3d4e5f6g7h8(16 位十六进制,由设备 SN 计算得出) - 产品 ID(product_id):自动填充,不可修改
添加后,在设备详情页可查看“设备密钥”,此密钥即auth_info,必须与 STM32 固件中硬编码的值完全相同。
3.3 定义数据流(Datastream):名称、类型、单位三位一体
在设备详情页 → “数据流管理” → “添加数据流”:
- 数据流名称:
temperature(小写字母,无下划线,与 STM32 上报 JSON key 一致) - 数据类型:
float - 单位:
℃ - 描述:鸡舍温度(用于小程序界面标注)
重复操作,添加humidity、light_intensity、air_quality等数据流。注意:ONENET 不允许数据流名称含空格或大写字母,否则小程序解析失败。
3.4 配置物模型(Thing Model):定义可下发的控制属性
在产品管理页 → “物模型” → “编辑物模型”:
- 添加服务(Service):
control_window- 输入参数:
window_status(类型:int,取值范围:0,1)
- 输入参数:
- 添加服务:
control_light- 输入参数:
light_status(类型:int,取值范围:0,1)
- 输入参数:
保存后,ONENET 自动生成/sys/{pid}/{dn}/thing/service/property/setTopic,STM32 订阅此 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 关键参数说明表
| 参数 | 值 | 说明 |
|---|---|---|
url | https://api.heclouds.com/devices/{device_id}/datapoints | {device_id}为 ONENET 设备详情页中“设备ID”,12位数字,非device_name |
header.api-key | token字符串 | 不是 apikey,是app.js中申请的短期 token |
data.datastream_id | 数组['temperature',...] | 必须与 ONENET 数据流名称完全一致,大小写敏感 |
data.limit | 1 | 仅取最新一条,避免小程序渲染旧数据 |
4.3 控制指令下发:POST 请求需构造符合物模型的 JSON Body
mine.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_window);paras:必须为对象,key 名与物模型输入参数名一致(如window_status);- 若
paras中 key 名错误(如写成window),ONENET 返回400 Bad Request,错误信息为"msg":"invalid paras"。
5. 调试与排错:从串口日志到小程序 Network 面板的四层定位法
当小程序显示“暂无数据”或“控制失败”,不要盲目改代码。按以下四层顺序排查,90% 的问题可 10 分钟内定位。
5.1 STM32 串口日志层:确认物理连接与 AT 指令执行结果
使用串口助手(波特率 115200)连接 STM32,观察启动日志:
- ✅ 正常流程:
AT+CGATT? → +CGATT:1(附着网络)→AT+QMTOPEN(连接 MQTT)→AT+QMTCONN(鉴权成功)→AT+QMTPUB(发布成功); - ❌ 典型失败:
+CGATT:0:NB 模组未注册到基站,检查 SIM 卡、天线、APN(本项目 APN 为cmnet);+QMTCONN: 0,12:MQTT 连接被拒绝,检查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 控制台补授权;
- Status 200 但
- 查看
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;" />