基于物联网的嵌入式多模态环境感知与动态消杀净化系统
基于物联网的嵌入式多模态环境感知与动态消杀净化系统
采购表
设备采购统计表
| 类别 | 设备/模块名称 | 型号/参数 | 数量 | 参考单价(元) | 供应商/获取途径 | 备注 |
|---|---|---|---|---|---|---|
| 主控开发板 | STM32U5 Nucleo开发板 | NUCLEO-U575ZI-Q | 1 | 免费(押金申请) | ST官方申请(需提交项目计划) | 主控芯片STM32U575,支持LPBAM低功耗模式 |
| 传感器模块 | 6轴惯性传感器(加速度计+陀螺仪) | LSM6DSV16XTR | 1 | 免费(样片申请) | ST官方申请 | 用于振动检测、人流密度感知 |
| 温湿度传感器 | HTS221TR | 1 | 免费(样片申请) | ST官方申请 | 监测环境温湿度,评估细菌滋生风险 | |
| 激光TOF距离传感器(8x8区域检测) | VL53L5CX | 1 | 300 | 第三方(淘宝/得捷电子) | 检测空间占用率,需自行设计安装支架 | |
| 电化学气体传感器(VOC/甲醛检测) | SGP30 | 1 | 120 | 第三方(淘宝) | 检测挥发性有机物浓度 | |
| 紫外线强度传感器 | GUVA-S12SD | 1 | 80 | 第三方(淘宝) | 监测紫外线灯消杀效果 | |
| 通信模块 | NB-IoT模组(低功耗广域网)(广和通L610模组) | BC26 | 1 | 150 | 第三方(淘宝/移远代理商) | 用于数据上传至云端 |
| NFC读卡器开发板 | X-NUCLEO-NFC09A1 | 1 | 免费(押金申请) | ST官方申请 | 实现设备身份认证与参数配置 | |
| 执行机构 | 紫外线灯模块(UVC 275nm) | 12V/5W带驱动电路 | 2 | 50 | 第三方(淘宝) | 用于动态消杀,需配合PWM控制 |
| 喷雾消杀装置(12V微型水泵+雾化喷头) | 12V/0.5A | 1 | 80 | 第三方(淘宝) | 喷洒消毒液,需MOS管驱动电路 | |
| 电源管理 | 锂电池(3.7V/2000mAh) | 18650电池+保护板 | 2 | 20 | 第三方(淘宝) | 供电核心,需搭配升压模块(5V/12V输出) |
| 低功耗电源管理模块 | TPS63020 | 1 | 15 | 第三方(得捷电子) | 3.7V升压至5V/12V,效率>90% | |
| 结构件 | 3D打印外壳(定制设计) | PLA材料 | 1 | 100 | 本地3D打印服务 | 包含传感器安装孔位、消杀喷口 |
| 其他辅材 | PCB打样(主控+传感器集成板) | 2层板/10x10cm | 5 | 50(5片总价) | 嘉立创/捷配 | 集成传感器接口与驱动电路 |
| 杜邦线、连接器 | 20cm/公对母 | 30 | 0.5/根 | 淘宝 | 用于模块间连接 | |
| 总预算(参考) | 约1200元 | 不含免费申请设备(主控板、ST传感器等),实际成本可压缩至800元内 |
关键说明
-
ST官方免费资源:
• 主控板(NUCLEO-U575ZI-Q)、LSM6DSV16XTR、HTS221TR、NFC开发板可通过ST官网申请(需押金,赛后退还)。• 申请链接:ST开发板申请入口。
-
替代方案:
• 激光TOF传感器:若预算有限,可改用ST推荐的VL6180(单点测距,成本约50元),但精度下降。• 通信模块:可用STM32WL LoRa模组替代NB-IoT(需修改通信协议)。
-
核心成本控制:
• 优先申请ST免费硬件,第三方传感器选择国产平替型号(如SGP30替换为ENS160,成本降至80元)。• 3D外壳可手工制作(亚克力板切割),节省60元。
建议团队提前2个月启动采购,优先申请ST资源,确保开发周期!
申请单:
类似作品
公交车内整体消毒只需10秒!光谷企业家居家研发出智能“弥雾消杀系统”
STM32 NUCLEO-U575ZI-Q
STM32U575是否可以进行通信?是否可以进行云端通信?(与云端服务器进行通信)
如果不行,那加上广和通ADP-L610-Arduino模组呢?
ADP-L610-Arduino_V3.0(低配版)
通信模块:ADP-L610-Arduino
ADP-L610-Arduino开发板介绍
云平台区分
AT命令介绍
SSCOM串口调试工具使用
短信功能
连接公有云(腾讯云)
华为云:https://bbs.elecfans.com/jishu_2327740_1_1.html
连接公有云(华为云)
https://bbs.elecfans.com/jishu_2327740_1_1.html
接入地址337c64ac60.st1.iotda-device.cn-north-4.myhuaweicloud.com
{
"device_id": "6857bbd732771f177b44a649_tempSensor",
"shadow": [
{
"service_id": "wsdcgq",
"desired": {
"properties": null,
"event_time": null
},
"reported": {
"properties": {
"temp_info": 25
},
"event_time": "20250626T102128Z"
},
"version": 0
},
{
"service_id": "Sensor",
"desired": {
"properties": null,
"event_time": null
},
"reported": {
"properties": {
"temp_info": 27,
"temperature": 27
},
"event_time": "20250629T115721Z"
},
"version": 8
}
]
}
后续需要做的事情:增加/修改显示数据(富氧含量)
- 增加/修改华为云“产品”——》“详情”——》“模型定义”——》“服务列表”——》“新增属性”
- 修改L610中的代码
- 修改微信小程序
空气质量:airQuality
最后更新时间:updateTime
AT+HMPUB=1,"$oc/devices/6857bbd732771f177b44a649_tempSensor/sys/properties/report",70,"{\"services\":[{\"service_id\":\"Sensor\",\"properties\":{\"temperature\":27}}]}"
AT+HMPUB=1,"$oc/devices/6857bbd732771f177b44a649_tempSensor/sys/properties/report",105,"{\"services\":[{\"service_id\":\"Sensor\",\"properties\":{\"temperature\":27,\"humidity\":45,\"formaldehyde\":0.053}}]}"
AT+HMPUB=1,"$oc/devices/6857bbd732771f177b44a649_tempSensor/sys/properties/report",117,"{\"services\":[{\"service_id\":\"Sensor\",\"properties\":{\"temperature\":27,\"humidity\":45,\"formaldehyde\":0.052,\"oxygen\":21}}]}"
printf("AT+HMPUB=1,\"$oc/devices/6857bbd732771f177b44a649_tempSensor/sys/properties/report\",117,\"{\\\"services\\\":[{\\\"service_id\\\":\\\"Sensor\\\",\\\"properties\\\":{\\\"temperature\\\":23,\\\"humidity\\\":58,\\\"formaldehyde\\\":0.011,\\\"oxygen\\\":21}}]}\"\r\n");
中国移动物联网开发平台OneNET云平台
温度传感器设备秘钥:12345678
连接华为云
AT
AT+MIPCALL?
[16:25:48.120]发→◇AT
□
[16:25:48.122]收←◆AT
OK
[16:25:58.160]发→◇ATI
□
[16:25:58.162]收←◆ATI
Fibocom
L610-CN-62-36
16000.1208.00.86.02.02
V1.2
OK
[16:26:10.871]发→◇AT+CPIN?
□
[16:26:10.872]收←◆AT+CPIN?
+CPIN: READY
OK
[16:27:14.101]发→◇AT+CSQ
□
[16:27:14.103]收←◆AT+CSQ
+CSQ: 21,99
OK
[16:27:27.885]发→◇AT+CGREG?
□
[16:27:27.886]收←◆AT+CGREG?
+CGREG: 0,1
OK
[16:27:41.460]发→◇AT+MIPCALL?
□
[16:27:41.463]收←◆AT+MIPCALL?
+MIPCALL: 0
OK
[16:27:48.445]发→◇AT+MIPCALL=1
□
[16:27:48.447]收←◆AT+MIPCALL=1
[16:27:48.484]收←◆
OK
+MIPCALL: 10.67.24.233
[16:27:55.251]发→◇AT+MIPCALL?
□
[16:27:55.253]收←◆AT+MIPCALL?
+MIPCALL: 1,10.67.24.233
OK
[16:28:28.684]发→◇AT+HMCON=0,60,"337c64ac60.st1.iotda-device.cn-north-4.myhuaweicloud.com","1883","6857bbd732771f177b44a649_tempSensor","12345678",0
□
[16:28:28.685]收←◆AT+HMCON=0,60,"337c64ac60.st1.iotda-device.cn-north-4.myhuaweicloud.com","1883","6857bbd732771f177b44a649_tempSensor","12345678",0
[16:28:29.290]收←◆
+HMCON OK
[16:37:17.666]发→◇AT+HMPUB=1,"$oc/devices/6857bbd732771f177b44a649_tempSensor/sys/properties/report",117,"{\"services\":[{\"service_id\":\"Sensor\",\"properties\":{\"temperature\":23,\"humidity\":60,\"formaldehyde\":0.082,\"oxygen\":21}}]}"
□
[16:37:17.668]收←◆AT+HMPUB=1,"$oc/devices/6857bbd732771f177b44a649_tempSensor/sys/properties/report",117,"{\"services\":[{\"service_id\":\"Sensor\",\"properties\":{\"temperature\":23,\"humidity\":60,\"formaldehyde\":0.082,\"oxygen\":21}}]}"
[16:37:17.789]收←◆
+HMPUB OK
新
+HMPUB=1,"$oc/devices/6857bbd732771f177b44a649_tempSensor/sys/properties/report",87,"{\"services\":[{\"service_id\":\"Sensor\",\"properties\":{\"temperature\":30,\"humidity\":71,\"formaldehyde\":0.010,\"oxygen\":4280}}]}"
AT+HMPUB=1,"$oc/devices/6857bbd732771f177b44a649_tempSensor/sys/properties/report",119,"{\"services\":[{\"service_id\":\"Sensor\",\"properties\":{\"temperature\":30,\"humidity\":71,\"formaldehyde\":0.010,\"oxygen\":4280}}]}"
+HMPUB ERR:1
AT+CFUN=1
AT+CGSN?
AT+CFSN?
AT+CGMR?
AT+CGDCONT?
AT+CGDCONT=1,"IP","3gnet"
// 强制关闭当前冲突连接
AT+MIPCALL=0
// 复位网络堆栈(关键!清除运营商风控标记)
AT+CFUN=0
// 等待20秒
AT+CFUN=1
// 重建CID=0的有效通路
AT+CGACT=0,0 // 先解绑,好像不需要
AT+CGACT=0,1 // 再绑定
// 触发IP重新分配
AT+MIPCALL=1
// 预期响应
+MIPCALL: 10.x.x.x // 新IP
OK
微信小程序(微信开发者工具)
项目类型:物联网设备监控系统
核心功能描述:
构建一个环境监测数据看板小程序,实时显示来自嵌入式设备的:
1. 环境温度数值及可视化变化曲线
2. 相对湿度数值及动态变化图
3. 甲醛浓度检测值及超标预警系统
通过HTTP API接口(支持JSON格式)连接后端服务器:
- 数据获取接口:接收{"temp":25.3,"humidity":60,"formaldehyde":0.05}格式的环境数据
- 设备控制接口:向嵌入式设备发送消杀启动指令
用户界面要求:
1. 首页:卡片式数据展示(温度计/湿度计/甲醛检测可视化组件)
2. 历史数据页:ECharts折线图展示24小时趋势
3. 控制面板:手动消杀开关及自动模式设置
4. 报警通知:甲醛超标时弹出微信服务通知
哔哩哔哩:【手把手讲解华为云物联网云平台的使用以及应用侧的开发(2024最新版)】
应用侧开发:
1. 调用什么API接口获取设备端上传的数据?
获取影子数据的接口
在线接口调试:https://console.huaweicloud.com/apiexplorer/#/openapi/IOTDA/debug?api=ShowDeviceShadow
2. 调用什么API接口下发命令给设备端?
两个方法:下发命令 和 修改设备属性
查询设备属性:https://console.huaweicloud.com/apiexplorer/#/openapi/IOTDA/debug?api=ListProperties
修改设备属性:https://console.huaweicloud.com/apiexplorer/#/openapi/IOTDA/debug?api=UpdateProperties
3. 调用API接口需要什么必要的参数?
IAM账号:
IAM密码:
(domain.user)主账号名称:凭证(项目ID):
设备ID(为了获取token):
| 项目ID | 项目 | 所属区域 |
|---|---|---|
| db5bbccba3484859b838ebbe7a86479c | cn-north-4 | 华北-北京四 |
url: 'https://iotda.cn-north-4.myhuaweicloud.com/v5/iot/db5bbccba3484859b838ebbe7a86479c/devices/6857bbd732771f177b44a649_tempSensor/shadow',
临时代码
index.js_v1.1
// pages/index/index.js
Page({
data: {
activeTab: 'real-time',
temperature: 'N/A',
humidity: 'N/A',
airQuality: 'N/A', // 45
airStatus: 'N/A', // 优
formaldehyde: 'N/A',
updateTime: 'N/A',
overallStatus: 'N/A', // 良好
overallStatusClass: '', // good
showFormaldehydeWarning: false,
autoUpdate: true,
formaldehydeAdvice: '' // 新增甲醛处理建议字段
},
onLoad: function() {
// 加载华为云IoT设备数据
this.loadDeviceData();
// 启动轮询更新
this.startPolling();
},
onUnload: function() {
// 清理定时器
this.stopPolling();
},
onHide: function() { // 正确放置onHide生命周期
if (typeof this.stopPolling === 'function') {
this.stopPolling();
}
},
onShow: function() { // 正确放置onShow生命周期
if (this.data.autoUpdate) {
this.startPolling();
}
},
loadDeviceData: async function() {
const that = this;
// 1. 先获取华为云Token
try {
const token = await that.getHuaweiCloudToken();
// 2. 请求设备影子数据
wx.request({
url: 'https://337c64ac60.st1.iotda-app.cn-north-4.myhuaweicloud.com:443/v5/iot/db5bbccba3484859b838ebbe7a86479c/devices/6857bbd732771f177b44a649_tempSensor/shadow',
method: 'GET',
header: {
'Content-Type': 'application/json',
'X-Auth-Token': token // 使用动态获取的Token
},
success(res) {
if (res.statusCode === 200) {
// 3. 正确解析华为云数据结构
const shadowData = res.data.shadow || [];
const environmentService = shadowData.find(item => item.service_id === 'Sensor');
if (environmentService && environmentService.reported) {
const properties = environmentService.reported.properties || {};
const eventTime = environmentService.reported.event_time;
// 解析数据
const temperature = properties.temperature;
const humidity = properties.humidity;
const airQuality = properties.pm25; // 根据实际属性名调整
const formaldehyde = properties.formaldehyde;
// 4. 转换时间格式(华为云使用UTC时间)
let updateTimeStr = 'N/A'; // 默认值
if (eventTime) {
// 使用正则处理华为云特殊时间格式: "20250629T115721Z"
const isoTime = eventTime.replace(/^(\d{4})(\d{2})(\d{2})T(.+Z)$/, '$1-$2-$3T$4');
const dateObj = new Date(isoTime);
// 检查是否为有效日期
if (!isNaN(dateObj.getTime())) {
updateTimeStr = dateObj.toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: false
}).replace(/\//g, '-');
}
}
// 计算状态数据
const statusData = that.calculateStatus(
temperature,
humidity,
airQuality,
formaldehyde
);
// 5. 更新数据
that.setData({
temperature: temperature ? temperature : 'N/A',
humidity: humidity ? humidity : 'N/A',
//airQuality: airQuality || 'N/A',
formaldehyde: formaldehyde || 'N/A',
updateTime: updateTimeStr,
airStatus: statusData.airStatus,
overallStatus: statusData.overallStatus,
overallStatusClass: statusData.overallStatusClass,
showFormaldehydeWarning: statusData.showFormaldehydeWarning,
formaldehydeAdvice: statusData.formaldehydeAdvice
// ...其他数据
});
} else {
console.warn('未找到环境服务数据');
wx.showToast({ title: '数据格式异常', icon: 'none' });
}
} else if (res.statusCode === 401) {
console.warn('Token过期,重新获取');
that.refreshTokenAndRetry();
} else {
wx.showToast({ title: `请求失败: ${res.statusCode}`, icon: 'none' });
}
},
fail(err) {
console.error('API请求失败', err);
wx.showToast({ title: '网络错误', icon: 'none' });
}
});
} catch (error) {
console.error('获取Token失败', error);
wx.showToast({ title: '认证失败', icon: 'none' });
}
},
// 计算环境状态
calculateStatus: function(temperature, humidity, airQuality, formaldehyde) {
// 解析数值
const tempValue = parseFloat(temperature) || 0;
const humValue = parseFloat(humidity) || 0;
const airValue = parseFloat(airQuality) || 0;
const hchoValue = parseFloat(formaldehyde) || 0;
// 空气质量状态
const airStatus = airValue <= 50 ? '优' :
airValue <= 100 ? '良' :
airValue <= 150 ? '轻度污染' :
airValue <= 200 ? '中度污染' : '重度污染';
// 默认状态
let overallStatus = '良';
let overallStatusClass = 'good';
let showFormaldehydeWarning = false;
let formaldehydeAdvice = '';
// 特殊状态检测
if (hchoValue > 0.08) {
overallStatus = '甲醛超标';
overallStatusClass = 'danger';
showFormaldehydeWarning = true;
formaldehydeAdvice = '请立即开窗通风,使用空气净化器';
} else if (airValue > 150) {
overallStatus = '空气质量差';
overallStatusClass = 'warning';
}
// 综合状态评分
const statusScore =
(hchoValue <= 0.08 ? 1 : 0) * 40 +
(airValue <= 50 ? 1 : airValue <= 100 ? 0.8 : 0.5) * 30 +
(tempValue >= 18 && tempValue <= 28 ? 1 : 0.7) * 20 +
(humValue >= 40 && humValue <= 60 ? 1 : 0.7) * 10;
// 综合状态覆盖
if (statusScore >= 90) {
overallStatus = '优';
overallStatusClass = 'excellent';
} else if (statusScore >= 70 && !showFormaldehydeWarning) {
overallStatus = '良';
overallStatusClass = 'good';
} else if (!showFormaldehydeWarning) {
overallStatus = '差';
overallStatusClass = 'poor';
}
return {
airStatus,
overallStatus,
overallStatusClass,
showFormaldehydeWarning,
formaldehydeAdvice
};
},
// 获取华为云Token的方法
getHuaweiCloudToken: function() {
return new Promise((resolve, reject) => {
// 先检查缓存
const cachedToken = wx.getStorageSync('huawei_token');
const expireTime = wx.getStorageSync('token_expire');
// 如果Token有效且未过期
if (cachedToken && expireTime > Date.now()) {
return resolve(cachedToken);
}
// 否则请求新Token
wx.request({
url: 'http://192.168.3.10:3000/token',
success: (res) => {
if (res.data.token) {
// 缓存Token(华为云Token有效期通常24小时)
wx.setStorageSync('huawei_token', res.data.token);
wx.setStorageSync('token_expire', Date.now() + 23 * 60 * 60 * 1000); // 23小时
resolve(res.data.token);
} else {
reject(new Error('无效的Token响应'));
}
},
fail: reject
});
});
},
// Token过期重试逻辑
refreshTokenAndRetry: function() {
const that = this;
this.getHuaweiCloudToken().then(newToken => {
that.loadDeviceData(); // 用新Token重试
}).catch(err => {
console.error('刷新Token失败', err);
});
},
switchTab: function(e) { // 跳转页面按钮
const tab = e.currentTarget.dataset.tab;
if (tab === 'history') {
// 跳转到历史页面
wx.navigateTo({
url: '/pages/history/history'
});
} else {
this.setData({
activeTab: tab
});
}
},
toggleAutoUpdate: function(e) {
const autoUpdate = e.detail.value;
this.setData({ autoUpdate });
if (autoUpdate) {
this.startPolling();
} else {
this.stopPolling();
}
},
startPolling: function() {
// 每5秒更新一次数据
if (this.data.autoUpdate) {
this.pollingTimer = setInterval(() => {
this.loadDeviceData();
}, 5000);
}
},
stopPolling: function() {
if (this.pollingTimer) {
clearInterval(this.pollingTimer);
this.pollingTimer = null;
}
}
});
index.js_v1.2
氧气浓度
// pages/index/index.js
Page({
data: {
activeTab: 'real-time',
temperature: 'N/A',
humidity: 'N/A',
airQuality: 'N/A', // 保留但不再使用
airStatus: 'N/A',
formaldehyde: 'N/A',
updateTime: 'N/A',
overallStatus: 'N/A',
overallStatusClass: '',
showFormaldehydeWarning: false,
autoUpdate: true,
formaldehydeAdvice: ''
},
onLoad: function() {
this.loadDeviceData();
this.startPolling();
},
onUnload: function() {
this.stopPolling();
},
onHide: function() {
this.stopPolling();
},
onShow: function() {
if (this.data.autoUpdate) {
this.startPolling();
}
},
loadDeviceData: async function() {
const that = this;
try {
const token = await that.getHuaweiCloudToken();
wx.request({
url: 'https://337c64ac60.st1.iotda-app.cn-north-4.myhuaweicloud.com:443/v5/iot/db5bbccba3484859b838ebbe7a86479c/devices/6857bbd732771f177b44a649_tempSensor/shadow',
method: 'GET',
header: {
'Content-Type': 'application/json',
'X-Auth-Token': token
},
success(res) {
if (res.statusCode === 200) {
const shadowData = res.data.shadow || [];
const environmentService = shadowData.find(item => item.service_id === 'Sensor');
if (environmentService && environmentService.reported) {
const properties = environmentService.reported.properties || {};
const eventTime = environmentService.reported.event_time;
// 解析数据 - 使用正确的属性名
const temperature = properties.temperature || null;
const humidity = properties.humidity || null;
const formaldehyde = properties.formaldehyde || null;
// 格式化更新时间
let updateTimeStr = 'N/A';
if (eventTime) {
// 处理时间格式
let isoTime = eventTime;
if (/^\d{8}T\d{6}Z$/.test(eventTime)) {
isoTime = eventTime.replace(
/^(\d{4})(\d{2})(\d{2})T(\d{2})(\d{2})(\d{2})Z$/,
'$1-$2-$3T$4:$5:$6Z'
);
}
const dateObj = new Date(isoTime);
if (!isNaN(dateObj.getTime())) {
updateTimeStr = dateObj.toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: false
}).replace(/\//g, '-');
}
}
// 计算状态数据
const statusData = that.calculateStatus(
temperature,
humidity,
null, // 没有空气质量数据
formaldehyde
);
// 更新界面
that.setData({
temperature: temperature ?? 'N/A',
humidity: humidity ?? 'N/A',
formaldehyde: formaldehyde ?? 'N/A',
updateTime: updateTimeStr,
airStatus: statusData.airStatus,
overallStatus: statusData.overallStatus,
overallStatusClass: statusData.overallStatusClass,
showFormaldehydeWarning: statusData.showFormaldehydeWarning,
formaldehydeAdvice: statusData.formaldehydeAdvice
});
} else {
console.warn('未找到环境服务数据');
wx.showToast({ title: '数据格式异常', icon: 'none' });
}
} else if (res.statusCode === 401) {
that.refreshTokenAndRetry();
} else {
wx.showToast({ title: `请求失败: ${res.statusCode}`, icon: 'none' });
}
},
fail(err) {
console.error('API请求失败', err);
wx.showToast({ title: '网络错误', icon: 'none' });
}
});
} catch (error) {
console.error('获取Token失败', error);
wx.showToast({ title: '认证失败', icon: 'none' });
}
},
// 计算环境状态(使用修改后的版本)
calculateStatus: function(temperature, humidity, airQuality, formaldehyde) {
// 解析数值
const tempValue = parseFloat(temperature) || 0;
const humValue = parseFloat(humidity) || 0;
const hchoValue = parseFloat(formaldehyde) || 0;
// 空气质量状态 - 使用甲醛作为替代
const airStatus = hchoValue <= 0.08 ? '优' :
hchoValue <= 0.15 ? '良' :
hchoValue <= 0.3 ? '轻度污染' : '重度污染';
// 默认状态
let overallStatus = '良';
let overallStatusClass = 'good';
let showFormaldehydeWarning = false;
let formaldehydeAdvice = '';
// 特殊状态检测
if (hchoValue > 0.08) {
overallStatus = '甲醛超标';
overallStatusClass = 'danger';
showFormaldehydeWarning = true;
formaldehydeAdvice = '请立即开窗通风,使用空气净化器';
}
// 综合状态评分(调整权重)
const statusScore =
(hchoValue <= 0.08 ? 1 : 0) * 50 + // 甲醛权重增加
(tempValue >= 18 && tempValue <= 28 ? 1 : 0.7) * 30 +
(humValue >= 40 && humValue <= 60 ? 1 : 0.7) * 20;
// 综合状态覆盖
if (statusScore >= 90) {
overallStatus = '优';
overallStatusClass = 'excellent';
} else if (statusScore >= 70) {
overallStatus = '良';
overallStatusClass = 'good';
} else {
overallStatus = '差';
overallStatusClass = 'poor';
}
return {
airStatus,
overallStatus,
overallStatusClass,
showFormaldehydeWarning,
formaldehydeAdvice
};
},
// 获取华为云Token的方法(保持不变)
getHuaweiCloudToken: function() {
return new Promise((resolve, reject) => {
const cachedToken = wx.getStorageSync('huawei_token');
const expireTime = wx.getStorageSync('token_expire');
if (cachedToken && expireTime > Date.now()) {
return resolve(cachedToken);
}
wx.request({
url: 'http://192.168.3.10:3000/token',
success: (res) => {
if (res.data.token) {
wx.setStorageSync('huawei_token', res.data.token);
wx.setStorageSync('token_expire', Date.now() + 23 * 60 * 60 * 1000);
resolve(res.data.token);
} else {
reject(new Error('无效的Token响应'));
}
},
fail: reject
});
});
},
// 其他方法保持不变...
});
STM32U575与L610串口通信
STM32 NUCLEO-U575ZI-Q
STM32U575ZIT6Q
官网资料:https://www.st.com.cn/zh/evaluation-tools/nucleo-u575zi-q.html#documentation
下载烧录配置:
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file : main.c
* @brief : Main program body
******************************************************************************
* @attention
*
* <h2><center>© Copyright (c) 2023 STMicroelectronics.
* All rights reserved.</center></h2>
*
* This software component is licensed by ST under BSD 3-Clause license,
* the "License"; You may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
* opensource.org/licenses/BSD-3-Clause
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "usart.h"
#include "gpio.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include <stdio.h>
#include <string.h>
//¶¨ÒåÊý¾ÝÀàÐÍ
char *strx,*extstrx,*Readystrx;
char *strx1,*extstrx,*Readystrx;
char RxBuffer[1024],Rxcouter;
char *strstr(const char *, const char *);
uint8_t Res;
int len;
int number;
char *id;
uint8_t gSendCount = 0; //·¢ËÍÊý¾Ý¼ÆÊý
#define RX_BUF_MAX_LEN 32
uint8_t RXbuff2[1];
uint8_t RXbuff3[1];
uint8_t Databuff2[RX_BUF_MAX_LEN];
uint8_t Databuff3[RX_BUF_MAX_LEN];
uint8_t Data_num2;
uint8_t Data_num3;
typedef struct {
unsigned int oxygen;
float formaldehyde;
float temperature;
float humidity;
}SensorData;
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
void ReportToCloud(SensorData data);
/* USER CODE BEGIN PFP */
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
void Clear_Buffer(void)
{
uint8_t i;
for(i=0;i<Rxcouter;i++)
RxBuffer[i]=0;
Rxcouter=0;
}
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
UNUSED(huart);
if(huart->Instance ==LPUART1)
{
RxBuffer[Rxcouter++] = Res;
HAL_UART_Receive_IT(&hlpuart1, (uint8_t *)&Res, 1);
}
else if(huart->Instance == USART2)
{
Databuff2[Data_num2++] = RXbuff2[0];
HAL_UART_Receive_IT(&huart2,(uint8_t*)&RXbuff2,1);
HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_7);
}
}
void HAL_UART_AbortReceiveCpltCallback(UART_HandleTypeDef *huart)
{
if(huart->Instance == USART2)
{
HAL_UART_Transmit(&hlpuart1,(uint8_t*)&Databuff2,Data_num2,1000);
HAL_UART_Receive_IT(&huart2,(uint8_t*)&RXbuff2,1);
Data_num2=0;
}
}
int round_float(float f) {
if (f >= 0.0f) {
return (int)(f + 0.5f);
} else {
return (int)(f - 0.5f);
}
}
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
HAL_PWREx_EnableVddIO2();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_LPUART1_UART_Init();
MX_USART2_UART_Init();
/* USER CODE BEGIN 2 */
HAL_UART_Receive_IT(&hlpuart1,&Res,1);
HAL_UART_Receive_IT(&huart2,(uint8_t*)&RXbuff2,1);
__HAL_UART_CLEAR_IDLEFLAG(&huart2);
__HAL_UART_ENABLE_IT(&huart2,UART_IT_IDLE);
//Ä£¿é³õʼ»¯£¬µÈ´ý5Ãë
printf("Ä£¿é³õʼ»¯\r\n");
HAL_Delay(5000);
HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_7);
//²éѯ°æ±¾ÐÅÏ¢
printf("ATI\r\n");
HAL_Delay(1000);
HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_7);
strx=strstr((const char*)RxBuffer,(const char*)"Fibocom");
while(strx==NULL)
{
Clear_Buffer();
printf("²éѯÐÅϢʧ°Ü");
HAL_Delay(1000);
printf("ATI\r\n");
HAL_Delay(1000);
strx=strstr((const char*)RxBuffer,(const char*)"Fibocom");
}
Clear_Buffer();
printf("°æ±¾ÐÅÏ¢ÕýÈ·");
HAL_Delay(1000);
//²éѯSIM¿¨
printf("AT+CPIN?\r\n");
HAL_Delay(1000);
strx=strstr((const char*)RxBuffer,(const char*)"READY");
while(strx==NULL)
{
// SensorData Data;
// memcpy(&Data,(SensorData*)&Databuff2,16);
// printf("anion : %ld,\r\n",Data.oxygen);
// printf("ch2o : %0.2f\r\n",Data.formaldehyde);
// printf("temp : %0.2f\r\n",Data.temperature);
// printf("hum : %0.2f\r\n",Data.humidity);
// HAL_Delay(3000);
// char testBuff[7] ={0};
// unsigned int a = 0;
// a = (Databuff2[3] << 24) + (Databuff2[2] << 16) + (Databuff2[1] << 8) + Databuff2[0];
// memset(testBuff,0, sizeof(testBuff));
// sprintf(testBuff,"%ld",a);
// printf("a : %s \r\n",testBuff);
//
// float b = 0.1;
// //b = (float)(Databuff2[4] << 24) + (Databuff2[5] << 16) + (Databuff2[6] << 8) + Databuff2[7];
//
//
// for(int i=0; i<4; i++){
// printf("Databuff2[%d]:0x%x",4+i,Databuff2[4+i]);
// }
// memset(testBuff,0, sizeof(testBuff));
//// sprintf(testBuff,"%0.2f",b);
//
// sprintf(testBuff,"%s",(char*)&b);
// for(int i=0 ;i<4; i++){
// printf("testBuff[%d]:%x\r\n",i,testBuff[i]);
// }
// //printf("b : %s \r\n",testBuff);
// HAL_Delay(1000);
Clear_Buffer();
printf("SIM¿¨²éѯʧ°Ü");
HAL_Delay(1000);
printf("AT+CPIN?\r\n");
HAL_Delay(1000);
strx=strstr((const char*)RxBuffer,(const char*)"READY");
}
Clear_Buffer();
printf("SIM¿¨ÒÑ×¼±¸ºÃ");
HAL_Delay(1000);
//²éѯÐźÅ
printf("AT+CSQ\r\n");
HAL_Delay(1000);
strx=strstr((const char*)RxBuffer,(const char*)"+CSQ:");
while(strx==NULL)
{
Clear_Buffer();
printf("ÐźŲéѯʧ°Ü");
HAL_Delay(1000);
printf("AT+CSQ\r\n");
HAL_Delay(1000);
strx=strstr((const char*)RxBuffer,(const char*)"+CSQ:");
}
Clear_Buffer();
printf("ÐźÅÕý³£");
HAL_Delay(1000);
//²éÑ¯×¢ÍøÇé¿ö
printf("AT+CGREG?\r\n");
HAL_Delay(1000);
strx=strstr((const char*)RxBuffer,(const char*)"0,1");
while(strx==NULL)
{
Clear_Buffer();
printf("×¢Íø²»¿ÉÓÃ");
HAL_Delay(1000);
printf("AT+CGREG?\r\n");
HAL_Delay(1000);
strx=strstr((const char*)RxBuffer,(const char*)"0,1");
}
Clear_Buffer();
printf("×¢Íø¿ÉÓÃ");
HAL_Delay(1000);
//ÇëÇóIP
printf("AT+MIPCALL?\r\n");
HAL_Delay(1000);
strx=strstr((const char*)RxBuffer,(const char*)"+MIPCALL: 1");
while(strx==NULL)
{
Clear_Buffer();
printf("»¹Î´»ñÈ¡µ½IP");
HAL_Delay(1000);
printf("AT+MIPCALL=1\r\n");
HAL_Delay(1000);
strx=strstr((const char*)RxBuffer,(const char*)"+MIPCALL: ");
}
Clear_Buffer();
printf("»ñÈ¡IP³É¹¦");
HAL_Delay(1000);
//Á¬½Ó»ªÎªÔÆ
//printf("AT+HMCON=0,60,\"a16110f598.iot-mqtts.cn-north-4.myhuaweicloud.com\",\"8883\",\"6110e20e0ad1ed0286438504_Humidifier001\",\"123456789\",0\r\n");
printf("AT+HMCON=0,60,\"337c64ac60.st1.iotda-device.cn-north-4.myhuaweicloud.com\",\"1883\",\"6857bbd732771f177b44a649_tempSensor\",\"12345678\",0\r\n");
HAL_Delay(2000);
strx=strstr((const char*)RxBuffer,(const char*)"+HMCON OK");
while(strx==NULL)
{
Clear_Buffer();
printf("Á¬½Óʧ°Ü");
HAL_Delay(1000);
printf("AT+HMCON=0,60,\"337c64ac60.st1.iotda-device.cn-north-4.myhuaweicloud.com\",\"1883\",\"6857bbd732771f177b44a649_tempSensor\",\"12345678\",0\r\n");
HAL_Delay(1000);
strx=strstr((const char*)RxBuffer,(const char*)"+HMCON OK");
}
Clear_Buffer();
printf("Á¬½Ó³É¹¦");
HAL_Delay(1000);
//Éϱ¨ÊôÐÔ
//printf("AT+HMPUB=1,\"$oc/devices/6110e20e0ad1ed0286438504_Humidifier001/sys/properties/report\",76,\"{\\\"services\\\":[{\\\"service_id\\\":\\\"Sprayswitchcontrol\\\",\\\"properties\\\":{\\\"Switch\\\":1}}]}\"\r\n");
printf("AT+HMPUB=1,\"$oc/devices/6857bbd732771f177b44a649_tempSensor/sys/properties/report\",117,\"{\\\"services\\\":[{\\\"service_id\\\":\\\"Sensor\\\",\\\"properties\\\":{\\\"temperature\\\":26,\\\"humidity\\\":58,\\\"formaldehyde\\\":0.011,\\\"oxygen\\\":21}}]}\"\r\n");
HAL_Delay(1000);
strx=strstr((const char*)RxBuffer,(const char*)"+HMPUB OK");
while(strx==NULL)
{
Clear_Buffer();
printf("Éϱ¨Ê§°Ü");
HAL_Delay(1000);
printf("AT+HMPUB=1,\"$oc/devices/6857bbd732771f177b44a649_tempSensor/sys/properties/report\",117,\"{\\\"services\\\":[{\\\"service_id\\\":\\\"Sensor\\\",\\\"properties\\\":{\\\"temperature\\\":23,\\\"humidity\\\":58,\\\"formaldehyde\\\":0.011,\\\"oxygen\\\":21}}]}\"\r\n");
HAL_Delay(1000);
strx=strstr((const char*)RxBuffer,(const char*)"+HMPUB OK");
}
Clear_Buffer();
printf("Éϱ¨³É¹¦");
HAL_Delay(1000);
Clear_Buffer();
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
SensorData Data;
memcpy(&Data,(SensorData*)&Databuff2,16);
// printf("anion : %ld,\r\n",Data.oxygen);
// printf("ch2o : %0.2f\r\n",Data.formaldehyde);
// printf("temp : %0.2f\r\n",Data.temperature);
// printf("hum : %0.2f\r\n",Data.humidity);
ReportToCloud(Data);
HAL_Delay(3000);
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
void ReportToCloud(SensorData data) {
// Construct JSON data
int temp = round_float(data.temperature);
int humi = round_float(data.humidity);
char json_payload[200];
snprintf(json_payload, sizeof(json_payload),
"{\\\"services\\\":[{\\\"service_id\\\":\\\"Sensor\\\",\\\"properties\\\":{\\\"temperature\\\":%d,\\\"humidity\\\":%d,\\\"formaldehyde\\\":%.3f,\\\"oxygen\\\":%d}}]}",
temp, humi, data.formaldehyde, data.oxygen);
// Calculate JSON length
int payload_len = strlen(json_payload) - 48; // strlen(json_payload) - len(\\\)
// Construct AT instruction
char at_command[300];
snprintf(at_command, sizeof(at_command),
"AT+HMPUB=1,\"$oc/devices/6857bbd732771f177b44a649_tempSensor/sys/properties/report\",%d,\"%s\"\r\n",
payload_len, json_payload);
// Send AT command
printf("%s", at_command);
HAL_Delay(1000); // Waiting for the instruction to be sent to complete
// Check response
strx=strstr((const char*)RxBuffer,(const char*)"+HMPUB OK");
while(strx==NULL)
{
Clear_Buffer();
printf("Éϱ¨Ê§°Ü");
HAL_Delay(1000);
printf("%s", at_command);
HAL_Delay(1000);
strx=strstr((const char*)RxBuffer,(const char*)"+HMPUB OK");
}
Clear_Buffer();
printf("Éϱ¨³É¹¦");
HAL_Delay(1000);
}
/**
* @brief System Clock Configuration
* @retval None
*/
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
/** Configure the main internal regulator output voltage
*/
if (HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE4) != HAL_OK)
{
Error_Handler();
}
/** Initializes the CPU, AHB and APB busses clocks
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_MSI;
RCC_OscInitStruct.MSIState = RCC_MSI_ON;
RCC_OscInitStruct.MSICalibrationValue = RCC_MSICALIBRATION_DEFAULT;
RCC_OscInitStruct.MSIClockRange = RCC_MSIRANGE_4;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/** Initializes the CPU, AHB and APB busses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2
|RCC_CLOCKTYPE_PCLK3;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_MSI;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
RCC_ClkInitStruct.APB3CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0) != HAL_OK)
{
Error_Handler();
}
__HAL_RCC_PWR_CLK_DISABLE();
}
/* USER CODE BEGIN 4 */
/* USER CODE END 4 */
/**
* @brief This function is executed in case of error occurrence.
* @retval None
*/
void Error_Handler(void)
{
/* USER CODE BEGIN Error_Handler_Debug */
/* User can add his own implementation to report the HAL error return state */
__disable_irq();
while (1)
{
}
/* USER CODE END Error_Handler_Debug */
}
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t *file, uint32_t line)
{
/* USER CODE BEGIN 6 */
/* User can add his own implementation to report the file name and line number,
ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
/* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */
/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
问题:
- 确定传感器数据的发送格式(长度?16进制?4位表示一个信息?)
- 发送浮点型信息到华为云是否可以?
struct SensorData
STM32U575与ADP-L610-Arduino连接华为云工程
这个代码是使用STM32CubeMX生成的基于STM32U5系列微控制器的主程序,通过LPUART1与广和通L610模块通信,实现物联网设备(加湿器)连接到华为云IoT平台的完整功能。以下是详细解析:
一、程序整体结构
graph TD
A[头文件 & 全局声明] --> B[用户自定义函数]
B --> C[main主程序]
C --> D[时钟配置]
C --> E[外设初始化]
C --> F[业务逻辑]
F --> G[AT指令交互]
G --> H[云端通信]
二、核心功能详解
1. 头文件和全局声明
#include "main.h"
#include "usart.h"
#include "gpio.h"
#include <stdio.h>
#include <string.h>
char RxBuffer[1024]; // 串口接收缓冲区
uint8_t Res; // 单字节接收变量
int len, number;
char *id; // 用于解析云端指令ID
- RxBuffer[1024]:1KB接收缓冲区,存储L610模块返回的AT指令响应
- Res:中断接收时使用的单字节缓存
2. 关键自定义函数
串口接收回调函数
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
if(huart->Instance == LPUART1) {
RxBuffer[Rxcouter++] = Res; // 字符存入缓冲区
HAL_UART_Receive_IT(&hlpuart1, (uint8_t *)&Res, 1); // 重新启用接收中断
}
}
- 工作模式:每收到1字节触发中断,累积存放到RxBuffer
- Rxcouter:缓冲区指针(代码中未显式声明,可能是截图遗漏)
缓冲区清理函数
void Clear_Buffer() {
for(uint8_t i=0; i<Rxcouter; i++)
RxBuffer[i] = 0;
Rxcouter = 0;
}
3. main主程序流程
初始化阶段
HAL_Init(); // 初始化HAL库
HAL_PWREx_EnableVddIO2(); // 使能VDDIO2电源域(STM32U5特有)
SystemClock_Config(); // 配置160MHz系统时钟
MX_GPIO_Init(); // 初始化GPIO
MX_LPUART1_UART_Init(); // 初始化LPUART1
HAL_UART_Receive_IT(&hlpuart1, &Res, 1); // 启动串口接收中断
设备初始化流程(AT指令序列)
// 模块初始化
printf("模块初始化\r\n");
HAL_Delay(5000);
// 检查模块版本
printf("ATI\r\n"); // 查询模块信息
while(strstr(RxBuffer, "Fibocom") == NULL); // 检测Fibocom响应
// SIM卡检测
printf("AT+CPIN?\r\n"); // SIM卡状态查询
while(strstr(RxBuffer, "READY") == NULL);
// 信号强度检查
printf("AT+CSQ\r\n"); // 信号质量查询
while(strstr(RxBuffer, "+CSQ:") == NULL);
// 网络注册状态
printf("AT+CGREG?\r\n"); // 网络注册状态
while(strstr(RxBuffer, "0,1") == NULL); // 0,1表示已注册到归属网络
// 获取IP地址
printf("AT+MIPCALL=1\r\n"); // 激活移动IP连接
while(strstr(RxBuffer, "+MIPCALL:") == NULL);
4. 华为云IoT接入
MQTT连接建立
printf("AT+HMCON=0,60,\"a16110f598.iot-mqtts.cn-north-4.myhuaweicloud.com\",\"8883\",\"6110e20e0ad1ed0286438504_Humidifier001\",\"123456789\",0\r\n");
while(strstr(RxBuffer, "+HMCON OK") == NULL);
- 连接到华为云MQTT服务器(端口8883)
- 设备ID:
6110e20e0ad1ed0286438504_Humidifier001 - 密码:
123456789
设备属性上报
printf("AT+HMPUB=1,\"$oc/devices/6110e20e0ad1ed0286438504_Humidifier001/sys/properties/report\",76,\"{\\\"services\\\":[{\\\"service_id\\\":\\\"Sprayswitchcontrol\\\",\\\"properties\\\":{\\\"Switch\\\":1}}]}\"\r\n");
while(strstr(RxBuffer, "+HMPUB OK") == NULL);
- 属性路径:
$oc/devices/[设备ID]/sys/properties/report - 上报内容:开关状态(
"Switch":1)
5. 主循环处理
while(1) {
if(RxBuffer[0] != 0) { // 检测接收数据
// 解析云端指令
char* cmd = strstr(RxBuffer, "SpraySwitchParam");
char* reqId = strstr(RxBuffer, "request_id=");
if(cmd) {
number = cmd[18]; // 提取指令参数值(开关状态)
// 解析请求ID (格式:request_id="xxx")
id = strtok(reqId, "\"");
id = strtok(NULL, "\"");
// 构造响应消息
printf("AT+HMPUB=1,\"$oc/devices/6110e20e0ad1ed0286438504_Humidifier001/sys/commands/response/request_id=%s\",102,...", id);
}
Clear_Buffer();
}
HAL_Delay(500);
}
- 命令响应机制:收到云平台下发的控制指令后,提取
request_id构造响应路径 - 指令格式:
SpraySwitchParam=值控制加湿器开关
三、华为云IoT平台交互协议
1. MQTT主题格式
| 主题类型 | 路径模板 | 用途 |
|---|---|---|
| 属性上报 | $oc/devices/{device_id}/sys/properties/report |
设备主动上报状态 |
| 命令响应 | $oc/devices/{device_id}/sys/commands/response/{request_id} |
响应平台指令 |
| 命令下发 | $oc/devices/{device_id}/sys/commands/request/{request_id} |
接收平台指令 |
2. 数据格式示例
// 属性上报
{
"services": [{
"service_id": "Sprayswitchcontrol",
"properties": {"Switch": 1}
}]
}
// 指令响应
{
"paras": {"SpraySwitchParam":1},
"service_id":"Sprayswitchcontrol",
"command_name":"SpraySwitchCommand"
}
四、潜在问题与改进建议
-
安全性问题:
- MQTT密码明文存储(
123456789) - 应采用动态令牌或证书认证
- MQTT密码明文存储(
-
缓冲区隐患:
RxBuffer[Rxcouter++] = Res; // 未检查Rxcouter是否<1024改进:添加溢出检查
if(Rxcouter < sizeof(RxBuffer)-1) RxBuffer[Rxcouter++] = Res; -
阻塞式延迟:
HAL_Delay(1000); // 阻塞CPU改进:改用状态机+超时检测
-
AT指令解析:
使用strstr搜索特定字符串,建议改用环形缓冲区和状态机解析响应 -
电源管理缺失:
未利用STM32U5的超低功耗特性,可添加睡眠模式
五、关键硬件依赖
- STM32U575ZI-Q特性:
- LPUART1:用于连接L610模块
- VDDIO2:专用于GPIO的独立电源域
- 低功耗模式:未在本代码中启用
- 广和通L610:
- 支持LTE Cat.1网络
- AT指令集兼容华为云IoT对接要求
- 工作电流:峰值~500mA
总结
这段代码实现了:
- STM32U5通过LPUART驱动L610模块
- 完整的华为云IoT设备接入流程
- 设备状态上报与控制命令响应
- 基于阻塞式轮询的简单通信框架
需要重点优化:
- AT指令交互机制
- 安全认证方案
- 低功耗设计
- 错误处理机制
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file : main.c
* @brief : Main program body
******************************************************************************
* @attention
*
* <h2><center>© Copyright (c) 2023 STMicroelectronics.
* All rights reserved.</center></h2>
*
* This software component is licensed by ST under BSD 3-Clause license,
* the "License"; You may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
* opensource.org/licenses/BSD-3-Clause
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "usart.h"
#include "gpio.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include <stdio.h>
#include <string.h>
//¶¨ÒåÊý¾ÝÀàÐÍ
char *strx,*extstrx,*Readystrx;
char *strx1,*extstrx,*Readystrx;
char RxBuffer[1024],Rxcouter;
char *strstr(const char *, const char *);
uint8_t Res;
int len;
int number;
char *id;
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
void Clear_Buffer(void)
{
uint8_t i;
for(i=0;i<Rxcouter;i++)
RxBuffer[i]=0;
Rxcouter=0;
}
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
UNUSED(huart);
if(huart->Instance ==LPUART1)
{
RxBuffer[Rxcouter++] = Res;
HAL_UART_Receive_IT(&hlpuart1, (uint8_t *)&Res, 1);
}
}
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
HAL_PWREx_EnableVddIO2();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_LPUART1_UART_Init();
/* USER CODE BEGIN 2 */
HAL_UART_Receive_IT(&hlpuart1,&Res,1);
//Ä£¿é³õʼ»¯£¬µÈ´ý5Ãë
printf("Ä£¿é³õʼ»¯\r\n");
HAL_Delay(5000);
//²éѯ°æ±¾ÐÅÏ¢
printf("ATI\r\n");
HAL_Delay(1000);
strx=strstr((const char*)RxBuffer,(const char*)"Fibocom");
while(strx==NULL)
{
Clear_Buffer();
printf("²éѯÐÅϢʧ°Ü");
HAL_Delay(1000);
printf("ATI\r\n");
HAL_Delay(1000);
strx=strstr((const char*)RxBuffer,(const char*)"Fibocom");
}
Clear_Buffer();
printf("°æ±¾ÐÅÏ¢ÕýÈ·");
HAL_Delay(1000);
//²éѯSIM¿¨
printf("AT+CPIN?\r\n");
HAL_Delay(1000);
strx=strstr((const char*)RxBuffer,(const char*)"READY");
while(strx==NULL)
{
Clear_Buffer();
printf("SIM¿¨²éѯʧ°Ü");
HAL_Delay(1000);
printf("AT+CPIN?\r\n");
HAL_Delay(1000);
strx=strstr((const char*)RxBuffer,(const char*)"READY");
}
Clear_Buffer();
printf("SIM¿¨ÒÑ×¼±¸ºÃ");
HAL_Delay(1000);
//²éѯÐźÅ
printf("AT+CSQ\r\n");
HAL_Delay(1000);
strx=strstr((const char*)RxBuffer,(const char*)"+CSQ:");
while(strx==NULL)
{
Clear_Buffer();
printf("ÐźŲéѯʧ°Ü");
HAL_Delay(1000);
printf("AT+CSQ\r\n");
HAL_Delay(1000);
strx=strstr((const char*)RxBuffer,(const char*)"+CSQ:");
}
Clear_Buffer();
printf("ÐźÅÕý³£");
HAL_Delay(1000);
//²éÑ¯×¢ÍøÇé¿ö
printf("AT+CGREG?\r\n");
HAL_Delay(1000);
strx=strstr((const char*)RxBuffer,(const char*)"0,1");
while(strx==NULL)
{
Clear_Buffer();
printf("×¢Íø²»¿ÉÓÃ");
HAL_Delay(1000);
printf("AT+CGREG?\r\n");
HAL_Delay(1000);
strx=strstr((const char*)RxBuffer,(const char*)"0,1");
}
Clear_Buffer();
printf("×¢Íø¿ÉÓÃ");
HAL_Delay(1000);
//ÇëÇóIP
printf("AT+MIPCALL?\r\n");
HAL_Delay(1000);
strx=strstr((const char*)RxBuffer,(const char*)"+MIPCALL: 1");
while(strx==NULL)
{
Clear_Buffer();
printf("»¹Î´»ñÈ¡µ½IP");
HAL_Delay(1000);
printf("AT+MIPCALL=1\r\n");
HAL_Delay(1000);
strx=strstr((const char*)RxBuffer,(const char*)"+MIPCALL: ");
}
Clear_Buffer();
printf("»ñÈ¡IP³É¹¦");
HAL_Delay(1000);
//Á¬½Ó»ªÎªÔÆ
//printf("AT+HMCON=0,60,\"a16110f598.iot-mqtts.cn-north-4.myhuaweicloud.com\",\"8883\",\"6110e20e0ad1ed0286438504_Humidifier001\",\"123456789\",0\r\n");
printf("AT+HMCON=0,60,\"337c64ac60.st1.iotda-device.cn-north-4.myhuaweicloud.com\",\"1883\",\"6857bbd732771f177b44a649_tempSensor\",\"12345678\",0\r\n");
HAL_Delay(2000);
strx=strstr((const char*)RxBuffer,(const char*)"+HMCON OK");
while(strx==NULL)
{
Clear_Buffer();
printf("Á¬½Óʧ°Ü");
HAL_Delay(1000);
printf("AT+HMCON=0,60,\"a16110f598.iot-mqtts.cn-north-4.myhuaweicloud.com\",\"8883\",\"6110e20e0ad1ed0286438504_Humidifier001\",\"123456789\",0\r\n");
HAL_Delay(1000);
strx=strstr((const char*)RxBuffer,(const char*)"+HMCON OK");
}
Clear_Buffer();
printf("Á¬½Ó³É¹¦");
HAL_Delay(1000);
//Éϱ¨ÊôÐÔ
//printf("AT+HMPUB=1,\"$oc/devices/6110e20e0ad1ed0286438504_Humidifier001/sys/properties/report\",76,\"{\\\"services\\\":[{\\\"service_id\\\":\\\"Sprayswitchcontrol\\\",\\\"properties\\\":{\\\"Switch\\\":1}}]}\"\r\n");
printf("AT+HMPUB=1,\"$oc/devices/6857bbd732771f177b44a649_tempSensor/sys/properties/report\",117,\"{\\\"services\\\":[{\\\"service_id\\\":\\\"Sensor\\\",\\\"properties\\\":{\\\"temperature\\\":23,\\\"humidity\\\":58,\\\"formaldehyde\\\":0.011,\\\"oxygen\\\":21}}]}\"\r\n");
HAL_Delay(1000);
strx=strstr((const char*)RxBuffer,(const char*)"+HMPUB OK");
while(strx==NULL)
{
Clear_Buffer();
printf("Éϱ¨Ê§°Ü");
HAL_Delay(1000);
printf("AT+HMPUB=1,\"$oc/devices/6110e20e0ad1ed0286438504_Humidifier001/sys/properties/report\",76,\"{\\\"services\\\":[{\\\"service_id\\\":\\\"Sprayswitchcontrol\\\",\\\"properties\\\":{\\\"Switch\\\":1}}]}\"\r\n");
HAL_Delay(1000);
strx=strstr((const char*)RxBuffer,(const char*)"+HMPUB OK");
}
Clear_Buffer();
printf("Éϱ¨³É¹¦");
HAL_Delay(1000);
Clear_Buffer();
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
HAL_Delay(500);
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
/**
* @brief System Clock Configuration
* @retval None
*/
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
/** Configure the main internal regulator output voltage
*/
if (HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE4) != HAL_OK)
{
Error_Handler();
}
/** Initializes the CPU, AHB and APB busses clocks
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_MSI;
RCC_OscInitStruct.MSIState = RCC_MSI_ON;
RCC_OscInitStruct.MSICalibrationValue = RCC_MSICALIBRATION_DEFAULT;
RCC_OscInitStruct.MSIClockRange = RCC_MSIRANGE_4;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/** Initializes the CPU, AHB and APB busses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2
|RCC_CLOCKTYPE_PCLK3;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_MSI;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
RCC_ClkInitStruct.APB3CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0) != HAL_OK)
{
Error_Handler();
}
__HAL_RCC_PWR_CLK_DISABLE();
}
/* USER CODE BEGIN 4 */
/* USER CODE END 4 */
/**
* @brief This function is executed in case of error occurrence.
* @retval None
*/
void Error_Handler(void)
{
/* USER CODE BEGIN Error_Handler_Debug */
/* User can add his own implementation to report the HAL error return state */
__disable_irq();
while (1)
{
}
/* USER CODE END Error_Handler_Debug */
}
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t *file, uint32_t line)
{
/* USER CODE BEGIN 6 */
/* User can add his own implementation to report the file name and line number,
ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
/* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */
/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
作品上传
作品名称:
基于物联网的嵌入式多模态环境感知与动态消杀净化系统
作品简介:
针对传统环境消杀与监测方式实时性弱、功能单一、交互不便的不足,本作品“基于物联网的嵌入式多模态环境感知与动态消杀净化系统”以STM32U5超低功耗主控芯片为核心,集成多模态传感器(温湿度、负氧离子、甲醛、TVOC、PM2.5等),实现对室内环境健康的全方位实时感知。
系统具备智能动态消杀净化能力(包含加湿功能、增加负氧离子功能、立体消毒功能等),其中立体消毒功能经验证有效杀菌率高达99.9%。用户可通过直观的本地控制屏(清晰展示各项环境指标与消杀进度)或便捷的语音控制,灵活切换“康养”、“养肤”、“母婴”等多种定制化场景模式,满足不同环境健康需求。
所有关键环境数据(温湿度、负氧离子含量、甲醛浓度等)均通过华为云IoTDA平台稳定传输,并实时同步至配套微信小程序,实现环境状态的远程查看,为用户打造可知、可控的健康舒适环境。
作品关键词
环境感知、空气净化、物联网监控
技术文档
加拍传感器照片(七合一传感器和负氧离子浓度传感器)
流程图:
触摸屏模块、执行器模块、语音处理和信息上传模块
1. 硬件分层架构设计
-
三级供电架构:
电压等级 供电模块 24V 雾化器(高压隔离驱动) 12V 4G模块、显示屏、风机 5V STM32U575主控、语音模块 -
双核协同控制:
- 主控层:STM32U575通过UART/SPI/I2C连接传感器、4G模块及显示屏
- 驱动层:iLTE9866协处理器→MB9F功率芯片→雾化器(三级安全隔离)
-
抗干扰设计:传感器独立线性供电,4层PCB叠层+磁珠滤波抑制噪声(信号误差≤±0.5%FS)。
2. 软件系统开发
边缘层(STM32U575) → 传输层(4G MQTT) → 云端(Huawei IoTDA) → 应用层(微信小程序)
- 关键实现:
- 动态阈值算法实时校准传感器数据
- 中断恢复逻辑(断电≤5分钟续消)
- 语音指令响应链:MIC→语音模块→串口→STM32U575(响应<0.5s)
flowchart TD
subgraph 设备端
A[STM32U575微控制器] -->|串口通信+AT指令| B[广和通ADP-L610模组]
C[本地控制屏UI] -->|SPI交互| A
end
subgraph 云平台
B -->|MQTT| D[华为云IoTDA]
end
subgraph 服务层
D -->|HTTPS Webhook| E[Node.js后端]
end
subgraph 用户端
E -->|WebSocket| F[微信小程序]
G[用户语音交互] -->|操作指令| A
end
style 设备端 fill:#F0F9FF,stroke:#1E88E5,stroke-width:2px
style 云平台 fill:#E8F5E9,stroke:#388E3C,stroke-width:2px
style 服务层 fill:#FFF3E0,stroke:#EF6C00,stroke-width:2px
style 用户端 fill:#FFEBEE,stroke:#D32F2F,stroke-width:2px
classDef hardware fill:#BBDEFB,stroke:#1976D2,color:#0D47A1,stroke-width:1.5px;
classDef cloud fill:#C8E6C9,stroke:#2E7D32,color:#1B5E20,stroke-width:1.5px;
classDef server fill:#FFE0B2,stroke:#E65100,color:#BF360C,stroke-width:1.5px;
classDef user fill:#FFCDD2,stroke:#C62828,color:#B71C1C,stroke-width:1.5px;
class A,B,C hardware;
class D cloud;
class E server;
class F,G user;
linkStyle 0 stroke:#1565C0,stroke-width:1.5px;
linkStyle 1 stroke:#1565C0,stroke-width:1.5px;
linkStyle 2 stroke:#388E3C,stroke-width:1.5px;
linkStyle 3 stroke:#EF6C00,stroke-width:1.5px;
linkStyle 4 stroke:#E65100,stroke-width:1.5px;
感谢您的确认!关于您希望开发的连接华为云物联网设备的微信小程序,我先帮您进一步完善和扩展需求,确保功能完整且体验流畅。
基于您提供的实时数据展示和历史数据折线图需求,我补充了登录注册功能保障数据安全,设计了首页集成实时数据和甲醛超标提醒,历史数据页面支持多指标时间范围选择和图表交互,个人信息页支持资料修改和退出登录。底部导航栏设计包含首页、历史数据和个人信息,方便用户快速切换。考虑到微信小程序的使用场景,所有新增和编辑操作均采用弹窗表单,避免页面跳转过多,提升用户体验。
接下来,我将输出完整且结构规范的需求文档,方便您确认和后续开发。
需求文档
1. 页面风格样式
- 整体风格简洁现代,突出科技感,适合物联网设备监测场景。
- 主要色调采用蓝色和白色,搭配绿色和红色作为状态和提醒色。
- 实时数据和历史数据均用卡片式布局,卡片带圆角和阴影,层次分明。
- 图表采用清晰的折线图,颜色区分不同指标,支持触摸滑动查看数据点。
- 按钮和交互元素尺寸适合手机操作,保证点击准确。
- 底部固定导航栏,包含首页、历史数据、个人信息,方便页面切换。
- 甲醛超标提醒以红色显著展示,确保用户第一时间注意。
2. 核心业务流程
2.1 登录与注册
- 用户打开小程序,默认进入登录页面。
- 输入账号和密码进行登录,登录成功进入首页,失败显示错误提示。
- 新用户点击“注册”,填写用户名、密码、邮箱等信息注册,注册成功后跳转回登录页面。
2.2 首页(实时数据展示与提醒)
- 登录后默认进入首页。
- 首页顶部显示当前设备的温湿度、空气质量和甲醛含量实时数据,数据自动刷新(约30秒一次)。
- 当甲醛含量超过安全阈值时,首页顶部显著显示红色提醒信息。
- 首页中间以卡片形式展示各项实时指标数据,清晰易读。
- 首页底部导航栏包含:首页(当前页)、历史数据、个人信息。
- 首页提供快捷按钮,用户可快速跳转至历史数据页面。
2.3 历史数据页面
- 展示温湿度、空气质量和甲醛含量的历史数据折线图。
- 页面顶部提供时间范围选择(近24小时、7天、30天)和指标切换标签。
- 支持图表缩放和滑动查看具体数据点。
- 支持下拉刷新加载最新历史数据。
- 页面布局简洁,突出数据趋势和对比。
2.4 个人信息页面
- 用户查看和修改个人资料(用户名、邮箱、密码)。
- 支持退出登录功能,退出后返回登录页面。
页面切换与导航
- 底部导航栏固定显示:首页、历史数据、个人信息,方便用户快速切换。
- 登录和注册页面不包含在底部导航栏中,仅首次访问或退出登录时显示。
- 首页快捷按钮可直接跳转至历史数据页面。
3. 页面之间的关系
- 用户首次打开小程序,进入登录页面。
- 登录成功后进入首页。
- 首页展示实时数据和甲醛超标提醒,提供跳转历史数据页面的入口。
- 底部导航栏允许用户随时切换首页、历史数据、个人信息三个主要页面。
- 历史数据页面支持查看不同指标和时间范围的折线图。
- 个人信息页面支持资料查看修改和退出登录,退出后返回登录页面。
浙公网安备 33010602011771号