基于 W55MH32 与 OneNET 的手机 APP 远程控制及数据可视化实现
目录
1 前言
MQTT是一种轻量级通信协议,基于TCP/IP,采用发布-订阅模式,广泛应用于物联网领域。
MQTT的工作原理围绕着三个核心部分:发布者(Publishers)、代理(Broker,也称服务器)和订阅者(Subscribers)。发布者负责发送消息到特定的主题(Topic),代理则接收这些消息并将其转发给所有订阅了该主题的订阅者。这种模式允许设备间异步通信,且设备不需要直接了解彼此的存在,从而降低了系统的复杂性。


W55MH32 是 WIZnet 推出的高性能以太网单片机。它采用高性能 Arm® Cortex-M3 内核,主频最高达 216MHz,内置 1024KB FLASH、96KB SRAM 。尤为突出的是,其搭载 WIZnet TCP/IP offload 引擎(TOE),集成全硬件 TCP/IP 协议栈、MAC 及 PHY ,还配备 32KB 独立以太网收发缓存,供 8 个硬件 socket 使用,是真正的All-in-One解决方案。
2 项目环境
2.1 硬件环境
- W55MH32Q-EVB
- 网络连接线
- 杜邦线若干
- 交换机或路由器
- DHT11温湿度传感器、LDE灯
2.2 软件环境
- OneNET物联网开放平台。
- HBuilder X
- WIZ UartTool V1.0串口助手
- 例程链接:w5500.com/w55mh32.html
3.硬件连接和方案
3.1 方案图示

3.2 硬件连接
LED_正极 ---> W55MH32_GPIOA0
DHT11_OUT ---> W55MH32_GPIOA6
4 MQTT连接OneNET云平台收发数据流程
注册账号在这里不进行赘述,下面我们看如何建立物模型。
4.1 准备阶段
创建产品和添加物模型:登录OneNET物联网平台,创建产品并在产品下添加以下物模型功能。
创建设备:在刚刚创建的产品下创建相应设备。

4.2 设备详情
设备详情:在设备详情页中可以查看设备密钥和设备ID等重要信息。

4.3 token密钥生成
设备与 OneNet 平台通信时,Token 作为身份凭证用于安全认证。需使用 Token 生成工具:
[OneNET Token 生成工具文档](文档中心)
1.res字段:products/{产品id}/devices/{设备名},key为设备级key。将{产品id}替换成自己产品的id,{设备名}替换成自己的设备名。
2.et字段:填写访问过期的时间。可以在此网站获取时间戳(Unix timestamp)转换工具 - 在线工具。
3.key字段:填写自己的设备的密钥。
4.填写完成后点击generate即可得到token密钥

4.4 连接、订阅和发布消息
订阅主题:$sys/{pid}/{device-name}/thing/property/set
订阅主题响应:$sys/iP20B5FpF6/d2/thing/property/set_reply
发布主题:$sys/{pid}/{device-name}/thing/property/post
发布主题响应 :$sys/iP20B5FpF6/d2/thing/property/post/reply
pid是product_id的简称,即产品ID。
接着我们可以使用上面记录的连接参数进行连接,当连接成功后,订阅上面的订阅主题。并通过发布主题上报物模型数据。
在OneNET平台,如果产品创建阶段选择的数据格式为OneJSON格式时,接收和发送数据格式都会遵守下面这个格式:
```c
{
"id": "123",
"version": "1.0",
"params": {
"temperature":"30.5"
}
}
```
id:值为"123",这是一个唯一的标识符;params是一个包含设备属性数据的对象用于上报物模型数据;version:值为 "1.0",表示该消息所遵循的协议版本。
5 例程修改
5.1 MQTT相关例程修改
找到do-mqtt.c文件,把上述中的参数更换为自己OneNet的参数,这些参数我们在上面都有讲到,可以自行替换。
// 定义MQTT连接参数结构体并初始化
mqttconn mqtt_params = {
.mqttHostUrl = "mqtts.heclouds.com", // MQTT服务器的URL地址
.server_ip = {0,}, // 服务器IP地址(此处未使用,保留默认值)
.port = 1883, // 连接端口号,1883为MQTT默认非加密端口
.clientid = "D2", // MQTT客户端ID,需保持唯一性
.username = "ifs7ax90qJ", // MQTT用户名,通常为产品ID
.passwd = "vers=md5&sign=YT2N73HSjmyy%2BbQEFMDjMw%3D%3D", // 用户密码,包含签名认证信息
.pubtopic = "$sys/ifs7ax90qJ/D2/thing/property/post", // 发布属性数据的主题
.pubtopic_reply = "$sys/ifs7ax90qJ/D2/thing/property/post/reply", // 属性数据发布响应主题
.subtopic = "$sys/ifs7ax90qJ/D2/thing/property/set", // 订阅属性设置的主题
.subtopic_reply = "$sys/ifs7ax90qJ/D2/thing/property/set_reply", // 属性设置响应主题
.pubQoS = QOS0, // 发布消息的服务质量等级(0:最多一次)
.willtopic = "/wizchip/will", // 遗嘱消息主题,客户端异常断开时发布
.willQoS = QOS0, // 遗嘱消息的服务质量等级
.willmsg = "wizchip offline!", // 遗嘱消息内容
.subQoS = QOS0, // 订阅消息的服务质量等级
};
数据上报部分代码修改:下方代码包含了上报到服务器物模型的数据以及标识符,标识符要与服务器物模型相同,数值为本地传感器采集到的数据,由单片机进行处理,然后把本地数据变量及设备状态填写到对应位置,就可以把本地数据上传到服务器
/**
* @brief 生成包含传感器数据和设备状态的JSON格式字符串
*
* 该函数将温度、湿度传感器数据以及LED引脚状态格式化到JSON字符串中,
* 用于数据传输或存储。函数会先进行参数合法性检查,确保缓冲区有效且足够大。
*
* @param json_buf [out] 用于存储生成的JSON字符串的缓冲区指针
* @param buf_len [in] 缓冲区的长度,单位为字节
*
* @return int 生成结果:1表示成功,0表示失败
*/
static int generate_sensor_json(char *json_buf, uint16_t buf_len) {
if (json_buf == NULL || buf_len < 256) {
return 0;
}
sprintf(json_buf,
"{\"id\":\"123\",\"version\":\"1.0\",\"params\":{"
"\"temp\":{\"value\":%d}," // 填充温度数据
"\"humi\":{\"value\":%d}," // 填充湿度数据
"\"LED\":{\"value\":%s}" // 填充LED状态
"}}",
temp, // 温度数据变量
humi, // 湿度数据变量
GPIO_ReadOutputDataBit(GPIOA, GPIO_Pin_0) ? "true" : "false");
return 1; // JSON字符串生成成功,返回成功状态
}
命令下发部分代码修改: 如果想在远端控制本地的设备我们可以在单片机中加入解析json数据的代码,然后在服务器下发指令,达到相应的效果。此段代码为解析服务器解析下发的指令,可以根据服务器下发指令是开灯还是关灯来操作本地的LED,如果想在服务器操作其他本地器件,则可根据上述代码进行添加。
/**
* @brief 解析云端下发的JSON格式控制指令
* @param msg 云端发送的JSON格式字符串
* @note 该函数负责解析JSON消息中的控制指令(如LED开关),
* 执行相应硬件操作,并向云端返回处理结果
*/
void json_decode(char *msg) {
int ret; // 函数返回值,用于判断MQTT发布是否成功
char replymsg[128] = {0}; // 存储回复云端的JSON消息
cJSON *id = NULL; // JSON消息中的"id"字段(用于消息匹配)
cJSON *jsondata = NULL; // 解析后的根JSON对象
cJSON *params = NULL; // JSON消息中的"params"字段(参数集合)
cJSON *LED = NULL; // "params"中的"LED"控制参数
// 解析JSON字符串,将其转换为cJSON对象
jsondata = cJSON_Parse(msg);
if (jsondata == NULL) { // 解析失败处理
printf("json parse fail.\r\n");
return;
}
// 提取JSON中的关键字段
id = cJSON_GetObjectItem(jsondata, "id"); // 获取消息ID(用于回复时匹配)
params = cJSON_GetObjectItem(jsondata, "params"); // 获取参数对象
LED = cJSON_GetObjectItem(params, "LED"); // 获取LED控制参数
// 处理LED控制命令
if(LED->valueint) // 若LED参数值为非0(通常表示"开启"指令)
{
led1_on(); // 调用LED开启函数
printf("led on\r\n"); // 打印调试信息
}
else // 若LED参数值为0(通常表示"关闭"指令)
{
led1_off(); // 调用LED关闭函数
printf("led off\r\n"); // 打印调试信息
}
// 构建并发送对云端的回复
pubmessage.qos = QOS0; // 设置回复消息的QoS等级为0(最多一次)
// 格式化回复消息:包含原消息ID、状态码200(成功)和提示信息
sprintf(replymsg, "{\"id\":\"%s\",\"code\":200,\"msg\":\"success\"}",
id->valuestring);
printf("reply:%s\r\n", replymsg); // 打印回复内容(调试用)
pubmessage.payload = replymsg; // 设置MQTT消息负载为回复内容
pubmessage.payloadlen = strlen(replymsg); // 设置负载长度
// 向云端指定主题发布回复消息
ret = MQTTPublish(&c, mqtt_params.subtopic_reply, &pubmessage);
if (ret != SUCCESSS) { // 发布失败处理
s_run_status = MQTT_ERR; // 更新系统状态为MQTT错误
} else { // 发布成功处理
printf("publish:%s,%s\r\n\r\n", mqtt_params.subtopic_reply,// 打印发布成功信息(调试用)
(char *)pubmessage.payload);
}
cJSON_Delete(jsondata); // 释放cJSON对象占用的内存,防止内存泄漏
}
5.2 温湿度数据采集部分代码添加
添加humiture.c及其.h文件以达到采集温湿度数据的目的,具体代码如下:
#include "humiture.h"
#include "delay.h"
#include "w55mh32_gpio.h"
/**
* @brief 初始化DHT11温湿度传感器
* @return 0:初始化成功 1:初始化失败
*/
uint8_t DHT11_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
// 使能GPIOA时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
// 配置PA6引脚为推挽输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 初始化为高电平
GPIO_SetBits(GPIOA,GPIO_Pin_6);
// 发送复位信号并检测传感器响应
DHT11_Rst();
return DHT11_Check();
}
/**
* @brief 向DHT11发送复位信号
*/
void DHT11_Rst(void)
{
DHT11_IO_OUT(); // 设置引脚为输出模式
DHT11_DQ_OUT(0); // 拉低总线至少18ms
delay_ms(20);
DHT11_DQ_OUT(1); // 释放总线
delay_us(30); // 等待30us
}
/**
* @brief 检测DHT11的响应信号
* @return 0:检测到响应 1:未检测到响应
*/
uint8_t DHT11_Check(void)
{
uint8_t retry=0;
DHT11_IO_IN(); // 设置引脚为输入模式
// 等待DHT11拉低总线
while (DHT11_DQ_IN && retry < 100)
{
retry++;
delay_us(1);
};
if(retry >= 100) return 1; // 超时未响应
else retry = 0;
// 等待DHT11释放总线
while (!DHT11_DQ_IN && retry < 100)
{
retry++;
delay_us(1);
};
if(retry >= 100) return 1; // 超时未释放
return 0; // 检测成功
}
/**
* @brief 从DHT11读取一个位
* @return 读取到的位值(0或1)
*/
uint8_t DHT11_Read_Bit(void)
{
uint8_t retry=0;
// 等待总线拉高
while(DHT11_DQ_IN && retry < 100)
{
retry++;
delay_us(1);
}
retry=0;
// 等待总线拉低
while(!DHT11_DQ_IN && retry < 100)
{
retry++;
delay_us(1);
}
// 延迟40us判断数据位
delay_us(40);
if(DHT11_DQ_IN) return 1; // 高电平为1
else return 0; // 低电平为0
}
/**
* @brief 从DHT11读取一个字节
* @return 读取到的字节数据
*/
uint8_t DHT11_Read_Byte(void)
{
uint8_t i, dat;
dat = 0;
// 循环读取8位数据
for (i = 0; i < 8; i++)
{
dat <<= 1; // 左移一位
dat |= DHT11_Read_Bit(); // 或上读取到的位
}
return dat;
}
/**
* @brief 从DHT11读取温湿度数据
* @param temp: 存储温度数据的指针
* @param humi: 存储湿度数据的指针
* @return 0:读取成功 1:读取失败
*/
uint8_t DHT11_Read_Data(uint8_t *temp, uint8_t *humi)
{
uint8_t buf[5]; // 存储读取到的5字节数据
uint8_t i;
// 发送复位信号并检测响应
DHT11_Rst();
if(DHT11_Check() == 0)
{
// 读取5字节数据
for(i = 0; i < 5; i++)
{
buf[i] = DHT11_Read_Byte();
}
// 校验数据(前4字节和等于第5字节)
if((buf[0] + buf[1] + buf[2] + buf[3]) == buf[4])
{
*humi = buf[0]; // 湿度整数部分
*temp = buf[2]; // 温度整数部分
}
}
else return 1; // 读取失败
return 0; // 读取成功
}
#ifndef HUMITURE_H
#define HUMITURE_H
#include <stdint.h>
/**
* @brief 配置DHT11数据引脚为输出模式
* 通过操作GPIOA的CRL寄存器,将PA6配置为推挽输出模式(50MHz)
* 输出模式下,CPU可以向传感器发送控制信号
*/
#define DHT11_IO_IN() {GPIOA->CRL&=0XF0FFFFFF;GPIOA->CRL|=8<<24;}
#define DHT11_IO_OUT() {GPIOA->CRL&=0XF0FFFFF;GPIOA->CRL|=3<<24;}
#define DHT11_DQ_OUT(X) GPIO_WriteBit(GPIOA, GPIO_Pin_6, X)
#define DHT11_DQ_IN GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_6)
uint8_t DHT11_Init(void);
uint8_t DHT11_Read_Data(uint8_t *temp,uint8_t *humi);
uint8_t DHT11_Read_Byte(void);
uint8_t DHT11_Read_Bit(void);
uint8_t DHT11_Check(void);
void DHT11_Rst(void);
#endif
6 HBuilder X
HBuilder X工程下载:
* 链接: https://pan.baidu.com/s/1oIIr3oTnHG-we22AkLQu8w?pwd=6en9
* 提取码:`6en9`
修改步骤:
1. 下载后导入HBuilder X。
2. 修改 `index.vue` 文件:
* 修改 Token: 替换 const params = {
author_key: '', //用户密钥
version: '2022-05-01',
user_id: '', //用户ID
} 中的author_key和user_id即可生成用户 Token。
* 修改产品 ID 和设备名: 在 `url` 中替换 `product_id` 和 `device_name` 参数为实际值。
3. 修改完毕后即可通过HBuilder X自带的发行功能对文件进行打包,打包成APP。

7 功能验证
打开手机APP可以看到APP成功获取OneNET平台的数据

通过手机APP下发命令,串口助手端打印相应执行信息,命令下发成功。

8 总结
本文详细介绍了手机APP通过OneNET云平台获取W55MH32Q采集的传感器数据,并下发命令控制相应设备的功能。感谢大家的耐心阅读!如果您在阅读过程中有任何疑问,或者希望进一步了解这款产品及其应用,欢迎随时通过私信或评论区留言。我们会尽快回复您的消息,为您提供更详细的解答和帮助!
更多推荐
所有评论(0)