尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

STM32+ONENET+小程序的鸡舍环境监测闭环方案

STM32+ONENET+小程序的鸡舍环境监测闭环方案 简介本资源是一套面向嵌入式开发初学者与农业物联网实践者的完整鸡舍环境监测小程序源码聚焦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主控→ CH340EUSB转串口调试→ BC28NB-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(); // 波特率96008N1 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协议栈用ATMIPL命令集更轻量。关键指令序列如下每条指令后必须检测OK或ERRORATCGATT1 # 附着网络 ATCIICR # 激活PDP上下文 ATCIFSR # 获取IP地址应返回非0.0.0.0 ATMIPLCREATE0,183.230.40.39,80,1,0 # 创建TCP连接ONENET华东节点IP ATMIPLCONN0 # 连接ONENET服务器 ATMIPLSEND0,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密钥管理”生成权限必须勾选“设备数据写入”。实测发现ATMIPLSEND后若未收到HTTP 200响应需等待5秒再重试否则模组状态机卡死。2.4 断网缓存策略用Flash模拟EEPROM存储最近200条数据鸡舍停电时NB模组断连但温湿度数据不能丢失。STM32F103C8T6片内Flash支持10万次擦写将最后200组数据每组含timestamptemphumco2存于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_ppmppm、light_luxlux、battery_voltV。每个数据流设置unit单位和min/max合理范围如temperature设为-20~60ONENET后台自动过滤超限异常值。提示不要为每个传感器建独立设备同一鸡舍所有传感器应归属同一设备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推送目标URLhttps://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-idchartCanvas stylewidth:100%; height:400px; bindtouchstartonTouchStart/// 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但鸡舍数据每条1KB100条仅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(ATCGATT1\r\n); HAL_UART_Receive(huart1, rx_buf, 100, 1000); // 等待模组返回 printf(Response: %s\r\n, rx_buf);常见错误日志分析CGATT: 0未附着网络检查SIM卡是否欠费或NB基站覆盖ERRORAT指令语法错误确认BC28固件版本V1.2以上支持MIPLCLOSEDTCP连接被ONENET主动关闭检查APIKey是否正确或设备是否被禁用5.2 ONENET平台日志在“设备管理”页查看实时在线状态与消息记录进入ONENET控制台 → 设备列表 → 点击设备 → “消息记录”页可查看最近100条上报记录含时间戳、datastream、valueHTTP状态码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 IndexHI -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为相对湿度百分比。当HI28℃时启动风机HI24℃时关闭。在STM32中用查表法加速计算预存T10~40℃、RH30~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值动态调整占空比HI24℃占空比0%停机24≤HI26℃占空比30%26≤HI28℃占空比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%。本文还有配套的精品资源点击获取
返回列表