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 硬件环境

  1. W55MH32Q-EVB
  2. 网络连接线
  3. 杜邦线若干
  4. 交换机或路由器
  5. DHT11温湿度传感器LDE

2.2 软件环境

  1. OneNET物联网开放平台。
  2. HBuilder X
  3. WIZ UartTool V1.0串口助手
  4. 例程链接: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采集传感器数据下发命令控制相应设备功能感谢大家的耐心阅读!如果您在阅读过程中有任何疑问,或者希望进一步了解这款产品及其应用,欢迎随时通过私信或评论区留言。我们会尽快回复您的消息,为您提供更详细的解答和帮助!

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐