OneNET MQTT协议讲解与应用

本文详细介绍了MQTT协议的基础知识,重点讲解了如何使用OneNET MQTT协议进行设备连接、心跳保持、数据流上传、消息发布和订阅。提供了一整套基于FreeRTOS的示例代码,适用于无线通讯模块直接使用socket接口的场景。此外,还提到了如何使用MQTT调试工具辅助测试。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

       OneNET提供的MQTT协议和IBM释放出来的不太一样,是由OneNET工程师根据MQTT协议本身的理解写的,如果读者想更快的了解MQTT协议,建议可以先看看OneNET的MQTT协议SDK,很容易理解。OneNET的MQTT协议是使用MQTT_V3.1.1版本,更多关于MQTT协议的讲解可以到OneNET平台下载:MQTT协议和OneNET设备终端接入协议-MQTT。在本文会使用到MQTT调试工具,关于工具的简单使用可以参考我另外一篇博客用MQTT调试工具调试OneNET MQTT协议

        本文主要讲解MQTT的消息发布和订阅以及数据流上报,不讲解命令下发,对命令下发感兴趣的读者可以看EDP协议,可参考我另外一篇博客OneNET的EDP协议讲解与应用。本文用socket的方式来通信,这里不再列出socket的代码,读者可以到我另外一篇博客OneNET的EDP协议讲解与应用查看。本文列出的代码部分是参考OneNET工程师——张继瑞的代码修改的,代码可能存在bug,请读者自行辨别,仅作为参考。

 一、什么是MQTT?

我们直接到官网查看:

MQTT stands for MQ Telemetry Transport. It is a publish/subscribe, extremely simple and lightweight messaging protocol, designed for constrained devices and low-bandwidth, high-latency or unreliable networks. The design principles are to minimise network bandwidth and device resource requirements whilst also attempting to ensure reliability and some degree of assurance of delivery. These principles also turn out to make the protocol ideal of the emerging “machine-to-machine” (M2M) or “Internet of Things” world of connected devices, and for mobile applications where bandwidth and battery power are at a premium.

 

二、OneNET MQTT协议讲解和应用

1、连接请求

 (1)连接OneNET平台,MQTT协议对应的服务器地址为:"183.230.40.39",端口为6002。

 使用socket连接服务器:

typedef enum
{
	MQTT_SRV_CONNECT_OK = 0,
	MQTT_SRV_CONNECT_ERROR = 1
}mqtt_srv_connect_t;

int g_onenet_mqtt_socket_id = -1; /* socket id */

char *g_onenet_mqtt_srv_ip_addr = "183.230.40.39";
int   g_onenet_mqtt_srv_ip_port = 6002;

/**************************************************************
函数名称 : onenet_mqtt_service_connect
函数功能 : mqtt 服务器连接
输入参数 : ip_addr:ip地址,ip_port:端口
返回值  	 : MQTT_SRV_CONNECT_OK:连接成功,
		   MQTT_SRV_CONNECT_ERROR:连接失败
备注		 : 无
**************************************************************/
mqtt_srv_connect_t onenet_mqtt_service_connect(char *ip_addr, unsigned int ip_port)
{
	g_onenet_mqtt_socket_id = socket_create();
	
	if(g_onenet_mqtt_socket_id < 0)
	{
		ONENET_MQTT_LOG("connect failed, g_onenet_mqtt_socket_id < 0");
		return MQTT_SRV_CONNECT_ERROR;
	}

	if(CONNECT_ERROR == socket_connect_service(g_onenet_mqtt_socket_id, ip_addr, ip_port))
	{
		ONENET_MQTT_LOG("connect failed, connect error");
		return MQTT_SRV_CONNECT_ERROR;
	}
	else
	{
		ONENET_MQTT_LOG("connect success");
		return MQTT_SRV_CONNECT_OK;
	}
	
}


onenet_mqtt_service_connect(g_onenet_mqtt_srv_ip_addr, g_onenet_mqtt_srv_ip_port);

(2)连接设备,连接设备的方式有两种:

连接设备代码:

typedef enum
{
	MQTT_DEV_CONNECT_OK = 0,
	MQTT_DEV_CONNECT_ERROR = 1
}mqtt_dev_link_t;

#define ONENET_MQTT_KEEP_ALIVE_TIME	256	/* 保活时间 */

char *g_onenet_mqtt_pro_api_key = "LnEhym2IW1pTfBhFMjKK8s7HFJQ=";
char *g_onenet_mqtt_pro_id = "194320";
char *g_onenet_mqtt_dev_auth_info = "mqttdev001";
char *g_onenet_mqtt_dev_id = "505550506";

/**************************************************************
函数名称 : onenet_mqtt_device_link
函数功能 : 与onenet设备创建连接
输入参数 : devid:创建设备的devid
		   proid:产品ID
		   auth_key:创建设备的masterKey或apiKey或设备鉴权信息
返回值  	 : MQTT_CONNECT_OK:连接成功,
		   MQTT_CONNECT_ERROR:连接失败
备注		 : 无
**************************************************************/
mqtt_dev_link_t onenet_mqtt_device_link(const char* devid, const char *proid, const char* auth_key)
{
	
	MQTT_PACKET_STRUCTURE mqttPacket = {NULL, 0, 0, 0};					//协议包
	unsigned char time_out = 100;

	ONENET_MQTT_LOG("devid: %s, proid:%s, api_key: %s\r\n", devid, proid, auth_key);
	
	if(MQTT_PacketConnect(proid, auth_key, devid, ONENET_MQTT_KEEP_ALIVE_TIME, 0, MQTT_QOS_LEVEL0, NULL, NULL, 0, &mqttPacket) == 0)
	{
		socket_send(g_onenet_mqtt_socket_id, mqttPacket._data, mqttPacket._len);//上传平台
		
		while(--time_out)
		{
			vTaskDelay(1);
		}
		MQTT_DeleteBuffer(&mqttPacket);								//删包
		return MQTT_DEV_CONNECT_OK;
	}
	else
	{
		ONENET_MQTT_LOG("link failed\r\n");
		return MQTT_DEV_CONNECT_ERROR;
	}
	
}

onenet_mqtt_device_link(g_onenet_mqtt_dev_id, g_onenet_mqtt_pro_id, g_onenet_mqtt_pro_api_key);

连接设备成功之后,可以在页面看到设备在线状态:

(3)断开设备连接,断开设备连接同时需要断开socket连接,代码:

/**************************************************************
函数名称 : onenet_mqtt_disconnect
函数功能 : 与onenet断开连接
输入参数 : 无
返回值  	 : 无
备注		 : 无
**************************************************************/
void onenet_mqtt_disconnect(void)
{
	MQTT_PACKET_STRUCTURE mqttPacket = {NULL, 0, 0, 0};	//协议包
	if(MQTT_PacketDisConnect(&mqttPacket) == 0)
	{
		socket_send(g_onenet_mqtt_socket_id, mqttPacket._data, mqttPacket._len);//上传平台
		MQTT_DeleteBuffer(&mqttPacket);	//删包
	}
}

/* 断开连接 */
onenet_mqtt_disconnect();
socket_close(g_onenet_mqtt_socket_id);
g_onenet_mqtt_socket_id = -1;

成功断开设备连接后,可在页面看到离线状态:

2、心跳保持

        心跳保持是需要定时向服务器发送1倍的keep_alive时间,这个keep_alive时间是前面连接平台是设置的时间,请看连接平台代码,在这我使用FreeRTOS的软件定时器定时向平台发送心跳。

心跳保持代码:

#define ONENET_MQTT_SEND_HEART_TIME ((ONENET_MQTT_KEEP_ALIVE_TIME - 20)*10)/* 心跳请求周期,单位100ms,MQTT心跳周期要求在1倍Keep_Alive内*/

/**************************************************************
函数名称 : onenet_mqtt_send_heart
函数功能 : 发送心跳
输入参数 : 无
返回值  	 : 无
备注		 : 无
**************************************************************/
void onenet_mqtt_send_heart(void)
{
	ONENET_MQTT_LOG("send heart");
	MQTT_PACKET_STRUCTURE mqttPacket = {NULL, 0, 0, 0};				//协议包
	MQTT_PacketPing(&mqttPacket);
	socket_send(g_onenet_mqtt_socket_id, mqttPacket._data, mqttPacket._len);
	MQTT_DeleteBuffer(&mqttPacket);									//删包
}

/**************************************************************
函数名称 : onenet_mqtt_send_heart_sw_timer_callback
函数功能 : 用FreeRTOS软件定时器定时发送心跳的回掉函数
输入参数 : xTimer:软件定时器任务句柄
返回值  	 : 无
备注		 : 无
**************************************************************/
void onenet_mqtt_send_heart_sw_timer_callback(TimerHandle_t xTimer)
{
	onenet_mqtt_send_heart();
}

/**************************************************************
函数名称 : onenet_mqtt_send_heart_sw_timer_task_init
函数功能 : 创建发送心跳软件定时器任务
输入参数 : 无
返回值  	 : 无
备注		 : 无
**************************************************************/
void onenet_mqtt_send_heart_sw_timer_task_init(void)
{
	g_onenet_mqtt_send_heart_sw_timer_handle = xTimerCreate("onenet_mqtt_send_heart_sw_timer_task",
                                      ONENET_MQTT_SEND_HEART_TIME * COAP_MAX_TRANSMIT_WAIT / portTICK_PERIOD_MS, 
                                      pdTRUE,
                                      NULL,
                                      onenet_mqtt_send_heart_sw_timer_callback);
}


/* 在连接设备成功之后发送开启定时器发送心跳 */
if(MQTT_DEV_CONNECT_OK == onenet_mqtt_device_link(g_onenet_mqtt_dev_id, g_onenet_mqtt_pro_id, g_onenet_mqtt_pro_api_key))
{
	xTimerStart(g_onenet_mqtt_send_heart_sw_timer_handle, 0);/* 连接设备成功之后开启发送心跳定时器 */
}

3、上传数据流

在这里,作为测试,我使用FreeRTOS的软件定时器,定时向平台上传数据流。

上传数据流代码:

/---------------------------------------------------------------------------------------/
typedef enum
{
	TYPE_BOOL = 0,
	TYPE_CHAR,
	TYPE_UCHAR,
	TYPE_SHORT,
	TYPE_USHORT,
	TYPE_INT,
	TYPE_UINT,
	TYPE_LONG,
	TYPE_ULONG,
	TYPE_FLOAT,
	TYPE_DOUBLE,
	TYPE_GPS,
	TYPE_STRING,
} DATA_TYPE;
 
typedef struct
{
	char *name;
	void *dataPoint;
	DATA_TYPE dataType;
	bool flag;
 
} DATA_STREAM;
 
typedef enum
{
	FORMAT_TYPE1 = 1,
	FORMAT_TYPE2,
	FORMAT_TYPE3,
	FORMAT_TYPE4,
	FORMAT_TYPE5
 
} FORMAT_TYPE;
 
/---------------------------------------------------------------------------------------/

//数据流
float g_mqtt_temperature = 26.5;
DATA_STREAM g_mqtt_data_stream[] = 
{
	{"temperature", &g_mqtt_temperature, TYPE_FLOAT, 1},
};
/* 数据流个数 */
unsigned short g_mqtt_data_stream_cnt = sizeof(g_mqtt_data_stream) / sizeof(g_mqtt_data_stream[0]);

/**************************************************************
函数名称 : onenet_mqtt_send_data
函数功能 : 上传数据到平台
输入参数 : type:发送数据的格式
		   devid:设备ID
		   apikey:设备apikey
		   streamArray:数据流
		   streamArrayNum:数据流个数
返回值  	 : 无
备注		 : 无
**************************************************************/
void onenet_mqtt_send_data(FORMAT_TYPE type, char *devid, char *apikey, DATA_STREAM *streamArray, unsigned short streamArrayCnt)
{
	
	MQTT_PACKET_STRUCTURE mqttPacket = {NULL, 0, 0, 0};	 //协议包
	short body_len = 0;
	
	ONENET_MQTT_LOG("MQTT_TYPE%d\r\n", type);
	
	body_len = DSTREAM_GetDataStream_Body_Measure(type, streamArray, streamArrayCnt, 0); //获取当前需要发送的数据流的总长度
	
	if(body_len > 0)
	{
		if(MQTT_PacketSaveData(devid, body_len, NULL, (uint8)type, &mqttPacket) == 0)
		{
			body_len = DSTREAM_GetDataStream_Body(type, streamArray, streamArrayCnt, mqttPacket._data, mqttPacket._size, mqttPacket._len);
			
			if(body_len > 0)
			{
				mqttPacket._len += body_len;
				ONENET_MQTT_LOG("Send %d Bytes\r\n", mqttPacket._len);
				socket_send(g_onenet_mqtt_socket_id, mqttPacket._data, mqttPacket._len);
			}
			else
			{
				ONENET_MQTT_LOG("WARN:	DSTREAM_GetDataStream_Body Failed\r\n");
			}	
			MQTT_DeleteBuffer(&mqttPacket);	//删包
		}
		else
		{
			ONENET_MQTT_LOG("WARN:	MQTT_NewBuffer Failed\r\n");
		}
	}
}

TimerHandle_t g_onenet_mqtt_send_data_sw_timer_handle = NULL;

/**************************************************************
函数名称 : onenet_mqtt_send_data_sw_timer_callback
函数功能 : 用FreeRTOS软件定时器定时上发数据的回掉函数
输入参数 : xTimer:软件定时器任务句柄
返回值  	 : 无
备注		 : 无
**************************************************************/
void onenet_mqtt_send_data_sw_timer_callback(TimerHandle_t xTimer)
{
	onenet_mqtt_send_data(FORMAT_TYPE3, g_onenet_mqtt_dev_id, g_onenet_mqtt_pro_api_key, g_mqtt_data_stream, g_mqtt_data_stream_cnt);//上传数据到平台
}

/**************************************************************
函数名称 : onenet_mqtt_send_data_sw_timer_task_init
函数功能 : 创建软件定时器任务
输入参数 : 无
返回值  	 : 无
备注		 : 对于MQTT的心跳请求必须在1.5倍keep_alive时间内发送
**************************************************************/
void onenet_mqtt_send_data_sw_timer_task_init(void)
{
	g_onenet_mqtt_send_data_sw_timer_handle = xTimerCreate("onenet_mqtt_send_data_sw_timer_task",
                                      200 * COAP_MAX_TRANSMIT_WAIT / portTICK_PERIOD_MS, 		/* 20s定时,软件定时器误差大 */
                                      pdTRUE,
                                      NULL,
                                      onenet_mqtt_send_data_sw_timer_callback);
}

打开上传数据流软件定时器开始定时上传数据流:

if(pdPASS == xTimerStart(g_onenet_mqtt_send_data_sw_timer_handle, 0))
{
    ONENET_MQTT_LOG("start success");
}

如果不想上传数据流,则关闭软件定时器:

if(pdPASS == xTimerStop(g_onenet_mqtt_send_data_sw_timer_handle, 0))
{
    ONENET_MQTT_LOG("stop success");
}

数据流成功上传,可以在设备查看到:

 

4、设备消息的发布

在这里,我们需要借助前面提到的MQTT调试工具,同时我们在OneNET再创建一个设备,现在我们有两个设备:

上面的MQTT_DEV_001设备由我们的硬件设备去连接,MQTT_DEV_002由我们的MQTT调试工具去连接。打开MQTT调试工具,并连接MQTT_DEV_002,同时订阅一个主题为“hello_topic”,如下:

接着,我们来实现硬件设备连接MQTT_DEV_001向调试工具连接的MQTT_DEV_002发布消息,代码如下:

typedef enum
{
	PUBLISH_MSG_OK = 0,
	PUBLISH_MSG_ERROR = 1
}mqtt_publish_topic_msg_t;


/**************************************************************
函数名称 : onenet_mqtt_publish_topic
函数功能 : onenet mqtt 发布主题消息
输入参数 : topic:发布的主题
		   msg:消息内容
返回值  	 : PUBLISH_MSG_OK: 发布消息成功
		   PUBLISH_MSG_ERROR:发布消息失败
备注		 : 无
**************************************************************/
mqtt_publish_topic_msg_t onenet_mqtt_publish_topic(const char *topic, const char *msg)
{
	MQTT_PACKET_STRUCTURE mqttPacket = {NULL, 0, 0, 0};							//协议包
	
	ONENET_MQTT_LOG("publish topic: %s, msg: %s\r\n", topic, msg);
	
	if(MQTT_PacketPublish(MQTT_PUBLISH_ID, topic, msg, strlen(msg), MQTT_QOS_LEVEL2, 0, 1, &mqttPacket) == 0)
	{
		socket_send(g_onenet_mqtt_socket_id, mqttPacket._data, mqttPacket._len);//上传平台
		MQTT_DeleteBuffer(&mqttPacket);	//删包
		return PUBLISH_MSG_OK;
	}
	else
	{
		return PUBLISH_MSG_ERROR;
	}
}

/*  发布主题为"hello_topic",消息为"hello_world" */
if(PUBLISH_MSG_OK == onenet_mqtt_publish_topic("hello_topic", "hello_world"))
{
	ONENET_MQTT_LOG("publish success");
}

消息发布成功之后,可以在MQTT调试工具查看到对应的消息,说明MQTT_DEV_002成功接收到MQTT_DEV_001发布过来的消息,如下所示:

 

5、设备消息的订阅

接下来,我们用硬件设备连接的MQTT_DEV_001去订阅一个主题为“topic_test”,然后使用MQTT调试工具去连接MQTT_DEV_002来向主题“topic_test”发布消息。

硬件设备消息订阅代码如下:

/* 主题 */
const signed char *g_mqtt_topics[] = {"mqtt_topic", "topic_test"};

typedef enum
{
	SUBSCRIBE_TOPIC_OK = 0,
	SUBSCRIBE_TOPIC_ERROR = 1
}mqtt_subscribe_topic_t;


/**************************************************************
函数名称 : onenet_mqtt_subscribe_topic
函数功能 : onenet mqtt 订阅主题
输入参数 : topic:订阅的topic
		   topic_cnt:topic个数
返回值  	 : SUBSCRIBE_TOPIC_OK:订阅成功
		   SUBSCRIBE_TOPIC_ERROR:订阅失败
备注		 : 无
**************************************************************/
mqtt_subscribe_topic_t onenet_mqtt_subscribe_topic(const signed char *topic[], unsigned char topic_cnt)
{
	
	unsigned char i = 0;
	
	MQTT_PACKET_STRUCTU
评论 21
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值