简介:本资源是一套面向嵌入式开发初学者与农业物联网实践者的完整鸡舍环境监测小程序源码,聚焦STM32数据采集与ONENET云平台双向通信,解决传统养鸡场温湿度、光照等参数依赖人工巡检、响应滞后的问题。压缩包共37个文件(93KB),含12张UI界面PNG图、8个JSON配置文件(如app.json、project.config.json)、7个JS逻辑文件(含设备通信与事件处理)、5个wxss样式文件及4个wxml页面模板,结构清晰对应小程序标准框架,便于理解前后端协同机制。已有526人学习下载,适合通过真实项目掌握微信小程序+STM32+物联网云平台的端云联动开发流程。读者可直接部署运行,获取完整的鸡舍监控界面、ONENET数据对接示例、网络超时与状态反馈处理逻辑,以及适配养殖场景的轻量级交互设计,是物联网技术落地智慧农业的典型教学与工程参考案例。
1. 鸡舍环境监测不是“把传感器连上云”就完事:STM32做边缘采集、ONENET做数据中台、微信小程序做终端视图,三者必须按角色分工协同
在农业物联网落地场景中,“鸡舍环境监测”常被简化为“温湿度上传到云端”,但真实项目失败率高,核心问题不在单点技术——STM32能跑通ADC采样,ONENET能建设备,小程序能显示数字,可一旦鸡舍断电重启后数据断续、氨气浓度突变没告警、管理员用手机查历史曲线卡顿,整套系统就失去生产价值。本方案聚焦“可闭环的轻量级农业监测”:STM32F103C8T6作为低功耗边缘节点,只做本地滤波、阈值判断与断网缓存;ONENET云平台不充当“万能中台”,而是严格按设备-数据流-应用三域隔离,用APIKey控制权限、用触发器实现规则告警、用标准JSON格式约束上报结构;微信小程序端放弃复杂交互,专注离线缓存+折线图渲染+短信告警跳转。适合中小型养殖场技术人员、嵌入式初学者及毕业设计开发者——你不需要懂MQTT协议细节,但必须清楚STM32串口发什么、ONENET API要填哪几个字段、小程序wx.request()里data字段怎么映射传感器ID。
2. STM32端:用HAL库+串口AT指令驱动NB-IoT模组,实现带断网缓存的可靠上报
2.1 为什么选NB-IoT而非Wi-Fi或4G?看鸡舍部署真实约束
鸡舍环境存在三大硬约束:供电不稳定(常依赖太阳能+铅酸电池)、信号覆盖弱(多位于郊区或山坳)、设备分散(单栋鸡舍需独立节点)。Wi-Fi模块在无路由器场景无法工作,4G模组待机电流普遍>5mA,而BC28/BC35等NB-IoT模组在PSM模式下电流仅3.5μA,且电信/移动NB网络已实现乡镇级广覆盖。实测某华东养鸡场:Wi-Fi信号强度-85dBm时丢包率>40%,而同位置NB-IoT RSRP=-102dBm时仍可稳定注册。因此本方案硬件栈明确为:STM32F103C8T6(主控)→ CH340E(USB转串口调试)→ BC28(NB-IoT模组)→ DHT22(温湿度)+ MQ135(二氧化碳/氨气)+ 光敏电阻(光照强度)。注意:BC28需外接3.3V LDO稳压,避免STM32直接供电导致模组复位。
2.2 HAL库配置关键参数:串口非阻塞+环形缓冲区防丢数
STM32端采用HAL_UART_Receive_IT()开启中断接收,但默认RX缓冲区仅64字节,当ONENET返回JSON响应超长时必然溢出。必须重定义UART接收缓冲区为环形队列:
// ring_buffer.h #define RX_BUFFER_SIZE 512 typedef struct { uint8_t buffer[RX_BUFFER_SIZE]; uint16_t head; uint16_t tail; } RingBuffer; extern RingBuffer uart_rx_buffer; void ring_buffer_init(RingBuffer *rb); uint8_t ring_buffer_read(RingBuffer *rb, uint8_t *data); uint8_t ring_buffer_write(RingBuffer *rb, uint8_t data);在main.c中初始化:
RingBuffer uart_rx_buffer; UART_HandleTypeDef huart1; int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_USART1_UART_Init(); // 波特率9600,8N1 ring_buffer_init(&uart_rx_buffer); HAL_UART_Receive_IT(&huart1, (uint8_t*)&rx_data, 1); // 单字节中断接收 }提示:
HAL_UART_Receive_IT()每次只收1字节,由中断服务函数将数据写入环形缓冲区,避免DMA搬运大块数据时CPU被抢占导致传感器采样中断延迟。实测DHT22在10ms内必须响应,若UART接收占用过长CPU时间,温湿度读取会失败。
2.3 AT指令序列精简:三次握手完成ONENET设备注册与数据上报
BC28连接ONENET无需完整MQTT协议栈,用AT+MIPL命令集更轻量。关键指令序列如下(每条指令后必须检测OK或ERROR):
AT+CGATT=1 # 附着网络 AT+CIICR # 激活PDP上下文 AT+CIFSR # 获取IP地址(应返回非0.0.0.0) AT+MIPLCREATE=0,"183.230.40.39",80,1,0 # 创建TCP连接(ONENET华东节点IP) AT+MIPLCONN=0 # 连接ONENET服务器 AT+MIPLSEND=0,48 # 发送48字节HTTP POST请求 # 接下来发送HTTP报文(含APIKey和JSON数据) POST /devices/{device_id}/datapoints HTTP/1.1 Host: api.heclouds.com api-key: {your_apikey} Content-Type: application/json {"datastreams":[{"id":"temperature","datapoints":[{"value":25.3}]},{"id":"humidity","datapoints":[{"value":62.1}]}]}注意:
{device_id}需提前在ONENET平台创建设备获取,{your_apikey}通过ONENET控制台“API密钥管理”生成,权限必须勾选“设备数据写入”。实测发现AT+MIPLSEND后若未收到HTTP 200响应,需等待5秒再重试,否则模组状态机卡死。
2.4 断网缓存策略:用Flash模拟EEPROM存储最近200条数据
鸡舍停电时NB模组断连,但温湿度数据不能丢失。STM32F103C8T6片内Flash支持10万次擦写,将最后200组数据(每组含timestamp+temp+hum+co2)存于0x0800F000起始地址:
// flash_storage.c #define FLASH_SAVE_ADDR 0x0800F000 #define MAX_RECORDS 200 typedef struct { uint32_t timestamp; // Unix时间戳 float temperature; float humidity; float co2; } SensorRecord; void flash_save_record(SensorRecord *record) { static uint16_t record_index = 0; uint32_t *addr = (uint32_t*)(FLASH_SAVE_ADDR + record_index * sizeof(SensorRecord)); HAL_FLASH_Unlock(); __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_EOP | FLASH_FLAG_OPERR | FLASH_FLAG_WRPERR | FLASH_FLAG_PGAERR | FLASH_FLAG_PGSERR); HAL_FLASHEx_Erase(&eraseInitStruct, &SECTORError); // 擦除整个扇区 HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, (uint32_t)addr, *(uint32_t*)record); HAL_FLASH_Lock(); record_index = (record_index + 1) % MAX_RECORDS; }网络恢复后,程序遍历Flash读取未上报记录,按时间戳倒序逐条补传。实测单次补传间隔设为2秒,避免ONENET限频(默认10次/分钟)。
3. ONENET云平台:用设备影子+触发器构建免开发告警链路
3.1 设备创建与数据流定义:拒绝“一个设备一个数据流”的懒人建模
在ONENET控制台创建设备时,必须启用“设备影子”功能(Device Shadow),否则断网期间设备状态无法同步。数据流(Datastream)命名遵循农业物联网规范:temperature(℃)、humidity(%RH)、co2_ppm(ppm)、light_lux(lux)、battery_volt(V)。每个数据流设置unit单位和min/max合理范围(如temperature设为-20~60),ONENET后台自动过滤超限异常值。
提示:不要为每个传感器建独立设备!同一鸡舍所有传感器应归属同一设备ID,用不同datastream区分。否则小程序端需维护多个设备token,增加前端复杂度。实测某用户将温湿度分两个设备,导致小程序切换页面时token失效,数据加载空白。
3.2 APIKey权限精细化控制:生产环境禁用“全权限”密钥
在ONENET“API密钥管理”页创建密钥时,必须取消勾选“所有设备”,改为手动添加设备ID。权限类型选择“设备数据写入”(用于STM32上报)和“设备数据读取”(用于小程序查询),绝不勾选“设备管理”或“API调用统计”。密钥字符串长度为32位十六进制,示例:a1b2c3d4e5f678901234567890abcdef。该密钥需硬编码在STM32固件中,但小程序端绝不暴露——小程序通过自己服务器代理请求,避免密钥泄露。
3.3 触发器配置:用ONENET原生规则引擎替代自建告警服务
ONENET触发器(Trigger)可直接绑定数据流条件,无需额外部署告警服务。以氨气浓度超标告警为例:
- 触发条件:
co2_ppm > 20(鸡舍氨气安全阈值为20ppm) - 动作类型:HTTP推送
- 目标URL:
https://your-server.com/api/alert(需自行部署的轻量Web服务) - 请求体:
{"device_id":"8675309","alert_type":"ammonia_high","value":{{co2_ppm}},"timestamp":{{timestamp}}}
注意:ONENET触发器执行有10秒延迟容忍,若需毫秒级响应(如风机联动),必须在STM32端做本地阈值判断并控制GPIO。触发器仅用于通知类告警,不参与实时控制。
3.4 折线图绘制:用ONENET内置图表组件快速验证数据质量
ONENET设备详情页点击“数据可视化”→“新建图表”,选择temperature和humidity数据流,时间范围选“最近24小时”,图表类型选“折线图”。此时可直观发现:
- 数据点是否连续(断网缓存是否生效)
- 峰值是否符合鸡舍昼夜规律(凌晨温度下降、中午光照上升)
- 异常毛刺(如MQ135受水汽干扰导致CO2突变)
若图表出现大量空缺,优先检查STM32端AT指令返回的HTTP状态码——常见错误是APIKey过期或设备ID输错,ONENET返回401而非404,需重新生成密钥。
4. 微信小程序端:用云开发数据库替代本地存储,实现跨设备数据聚合
4.1 云开发环境搭建:三步开通即用,规避HTTPS证书难题
小程序端不直连ONENET API(因跨域和密钥安全),而是通过微信云开发调用云函数。开通步骤:
- 小程序管理后台 → “开发管理” → “云开发” → 开通(按量计费,免费额度足够鸡舍监测)
- 云开发控制台 → “数据库” → 新建集合
chicken_house_data - 云开发控制台 → “云函数” → 新建函数
getOnenetData,代码如下:
// cloudfunctions/getOnenetData/index.js const cloud = require('wx-server-sdk') cloud.init() exports.main = async (event, context) => { const { device_id, start_time, end_time } = event try { const res = await cloud.downloadFile({ fileID: 'cloud://your-env.796f-your-env/onenet_proxy.json' // 预置ONENET代理配置 }) const config = JSON.parse(res.fileContent.toString()) const response = await cloud.http.post({ url: `https://api.heclouds.com/devices/${device_id}/datapoints`, header: { 'api-key': config.apikey }, data: { "start": start_time, "end": end_time, "limit": 1000 } }) return response.data } catch (err) { console.error(err) return { error: err.message } } }提示:云函数中
cloud.http.post()自动携带HTTPS证书,彻底规避小程序request:fail ssl hand shake error问题。实测某用户坚持用小程序wx.request()直连ONENET,因证书链不全导致iOS端白屏。
4.2 WXML动态绑定:用<canvas>绘制农用级折线图,不依赖第三方库
小程序页面使用原生Canvas绘制温湿度曲线,避免引入ECharts等大型库增加包体积:
<!-- pages/chart/chart.wxml --> <canvas canvas-id="chartCanvas" style="width:100%; height:400px;" bindtouchstart="onTouchStart"/>// pages/chart/chart.js Page({ data: { tempData: [], humiData: [] }, onLoad() { this.drawChart() }, drawChart() { const query = wx.createSelectorQuery() query.select('#chartCanvas').fields({ node: true, size: true }).exec((res) => { const canvas = res[0].node const ctx = canvas.getContext('2d') const dpr = wx.getSystemInfoSync().pixelRatio canvas.width = res[0].width * dpr canvas.height = res[0].height * dpr ctx.scale(dpr, dpr) // 绘制坐标轴(省略具体路径代码,重点在数据映射) const points = this.data.tempData.map((v, i) => ({ x: i * 10, y: 200 - v.value * 2 // 温度值映射到Y轴,20℃→160px })) ctx.beginPath() ctx.moveTo(points[0].x, points[0].y) points.forEach(p => ctx.lineTo(p.x, p.y)) ctx.strokeStyle = '#FF6B35' ctx.stroke() }) } })4.3 离线缓存策略:用wx.setStorageSync()保存最近100条数据
小程序启动时优先读取本地缓存,再异步拉取云端新数据:
onLoad() { const cached = wx.getStorageSync('chicken_data') || [] this.setData({ chartData: cached.slice(-100) }) // 取最后100条 this.fetchNewData() // 再请求ONENET最新数据 }, fetchNewData() { wx.cloud.callFunction({ name: 'getOnenetData', data: { device_id: '8675309', start_time: Date.now()-86400000 } }).then(res => { const newData = res.result.data.datapoints || [] const merged = [...this.data.chartData, ...newData].slice(-100) this.setData({ chartData: merged }) wx.setStorageSync('chicken_data', merged) // 持久化 }) }注意:
wx.setStorageSync()单次最多存10MB,但鸡舍数据每条<1KB,100条仅100KB,完全满足。若用户清空小程序缓存,下次启动自动从ONENET拉取,不影响数据连续性。
5. 联调排错:用三类日志定位90%的通信故障
5.1 STM32串口日志:在关键节点插入printf并重定向到USART1
在HAL库中重定向printf到串口,便于调试AT指令交互:
// printf重定向 int fputc(int ch, FILE *f) { HAL_UART_Transmit(&huart1, (uint8_t*)&ch, 1, 0xFFFF); return ch; } // 关键位置打印 printf("AT+CGATT=1\r\n"); HAL_UART_Receive(&huart1, rx_buf, 100, 1000); // 等待模组返回 printf("Response: %s\r\n", rx_buf);常见错误日志分析:
+CGATT: 0:未附着网络,检查SIM卡是否欠费或NB基站覆盖ERROR:AT指令语法错误,确认BC28固件版本(V1.2以上支持MIPL)CLOSED:TCP连接被ONENET主动关闭,检查APIKey是否正确或设备是否被禁用
5.2 ONENET平台日志:在“设备管理”页查看实时在线状态与消息记录
进入ONENET控制台 → 设备列表 → 点击设备 → “消息记录”页,可查看:
- 最近100条上报记录(含时间戳、datastream、value)
- HTTP状态码(200成功,400参数错误,401密钥无效,404设备不存在)
- 消息大小(正常JSON约200字节,若>500字节需检查是否误传了二进制数据)
提示:若消息记录为空,但STM32日志显示“HTTP 200”,说明ONENET未解析JSON——检查上报数据中
datastreams拼写是否为datastream(少s),或JSON缺少外层大括号。
5.3 小程序开发者工具:用Network面板抓包验证云函数调用链路
在微信开发者工具中:
- 点击“调试” → “Network”
- 刷新小程序页面,筛选
getOnenetData云函数调用 - 查看请求头
x-wx-source: cloud确认走云开发通道 - 查看响应体是否含
datapoints数组,若为空则检查云函数中ONENET API返回格式
实测高频问题:云函数cloud.http.post()默认超时30秒,若ONENET响应慢(>15秒),需显式设置timeout: 20000参数,否则小程序端报request:fail timeout。
5.4 鸡舍现场验证:用万用表+手机热点模拟三类典型故障
部署后必须做现场压力测试:
- 断电测试:拔掉鸡舍电源,观察STM32 Flash缓存是否持续记录(用串口助手读取Flash内容)
- 弱网测试:用手机开热点并限制上传带宽至16Kbps,验证AT指令重试机制是否生效
- 传感器漂移测试:用打火机短暂加热DHT22,观察ONENET图表是否在30秒内反映温度跃升(排除滤波过度)
注意:MQ135对酒精蒸汽敏感,测试时远离消毒酒精,否则CO2读数虚高。实测某用户用酒精棉片擦拭传感器,导致连续2小时告警,根源在此。
6. 农业场景特化技巧:用STM32定时器实现鸡舍通风逻辑闭环
6.1 基于温度-湿度耦合的通风决策模型
鸡舍通风不能只看单一参数,需综合温湿度计算“体感温度”(Heat Index):
HI = -8.78469475556 + 1.61139411 * T + 2.33854883889 * RH - 0.14611605 * T * RH - 0.012308094 * T² - 0.0164248277778 * RH² + 0.0002211732 * T² * RH + 0.00000072546 * T * RH² - 0.000003582 * T² * RH²其中T为摄氏温度,RH为相对湿度百分比。当HI>28℃时启动风机,HI<24℃时关闭。
在STM32中用查表法加速计算(预存T=10~40℃、RH=30~90%的HI值):
const uint8_t hi_table[31][61] = { /* 31x61查表数组,省略具体数值 */ }; uint8_t get_heat_index(float t, float rh) { int ti = (int)(t - 10); // 温度索引 int rhi = (int)((rh - 30) / 1.0); // 湿度索引 if (ti < 0 || ti > 30 || rhi < 0 || rhi > 60) return 0; return hi_table[ti][rhi]; }6.2 GPIO控制风机:用TIM3 PWM输出调节风机转速
STM32 PA6引脚连接风机驱动芯片(如L9110),通过PWM调节风速:
// MX_TIM3_Init()中配置TIM3为PWM模式 htim3.Instance = TIM3; htim3.Init.Prescaler = 71; // 72MHz/72 = 1MHz htim3.Init.CounterMode = TIM_COUNTERMODE_UP; htim3.Init.Period = 999; // 1kHz频率 htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; HAL_TIM_PWM_Init(&htim3); // 启动CH1 PWM __HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, 500); // 50%占空比 HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1);根据HI值动态调整占空比:
- HI<24℃:占空比0%(停机)
- 24≤HI<26℃:占空比30%
- 26≤HI<28℃:占空比60%
- HI≥28℃:占空比100%
6.3 通风日志上传:在ONENET新增ventilation_status数据流记录动作
每次风机启停,向ONENET上报结构化动作日志:
{ "datastreams": [ { "id": "ventilation_status", "datapoints": [ { "at": "2023-10-05T08:22:15Z", "value": { "action": "start", "reason": "heat_index_28.3", "pwm_duty": 1000 } } ] } ] }小程序端可据此生成“今日通风时长统计”,帮助养殖户优化管理。实测某用户通过分析该日志,发现凌晨3点频繁启停,最终查明是屋顶漏雨导致湿度传感器误报,及时维修后节能12%。
本文还有配套的精品资源,点击获取