联盛德W801系列4-MQTT使用

1.SDK配置mqtt_demo使能

在系统可以通过wifi上网的前提下(参考《联盛德W801系列2-WIFI一键配网,信息保存》),使能 DEMO_MQTT,如下图:
在这里插入图片描述

2.连接到MQTT服务器

服务器可以选用公共的平台,如腾讯云,可以参考下面的文章:
使用mqtt.fx连接腾讯云IoT Cloud——超详细
也可以自己建立一个本地或者公网的mqtt服务器,参考下面文章:
WINDOWS10环境下利用开源软件EMQX搭建MQTT服务器
MQTT客户端软件《mqtt.fx》比较好用,如果你会用mqtt.fx连接到MQTT服务器,那么,使用W801的SDK连接MQTT服务器就简单了。只要定义好几个宏定义就可以了,看下面的源代码:

	#define MQTT_DEMO_SERVER_ADDR          "192.168.110.25"
	#define MQTT_DEMO_SERVER_PORT           1883
	#define USENAME 	"admin"
	#define PASSWORD  	"public"
	#define CLIENT_ID  	"W801-WIFI" 
	#define MQTT_DEMO_CLIENT_ID            CLIENT_ID
	#define publish_TOPIC  	"/mattress/0123456789AB/pub"	// 中间部分是MAC
	#define subscribe_TOPIC "/mattress/0123456789AB/sub"	// 中间部分是MAC

使用demo程序,可以先串口命令 t-connect(“ssid”,“pwd”),联网,再 t-mqtt,启动mqtt例程,如果串口打印下面信息,说明连接服务器成功:

step1: init mqtt lib.
step2: establishing TCP connection.
step3: establishing mqtt connection.
step4: subscribe mqtt,/mattress/0123456789AB/sub
step5: start the Heart-beat preservation timer

看看初始化源代码:

static int mqtt_demo_init(void)
{
    int packet_length, ret = 0;
    uint16_t msg_id, msg_id_rcv;

    wm_printf("step1: init mqtt lib.\r\n");
    mqtt_init(&mqtt_demo_mqtt_broker, MQTT_DEMO_CLIENT_ID);//	这里会清零三元组
	
    //strcpy(mqtt_demo_mqtt_broker.clientid, CLIENT_ID);
	sprintf(mqtt_demo_mqtt_broker.clientid,"ID_%02X%02X%02X%02X%02X%02X",MAC2STR(g_macBuf));
    strcpy(mqtt_demo_mqtt_broker.username, USENAME);
    strcpy(mqtt_demo_mqtt_broker.password, PASSWORD);

    wm_printf("step2: establishing TCP connection.\r\n");
    ret = mqtt_demo_init_socket(&mqtt_demo_mqtt_broker, MQTT_DEMO_SERVER_ADDR, MQTT_DEMO_SERVER_PORT, mqtt_demo_mqtt_keepalive);
    if(ret)
    {
        wm_printf("init_socket ret=%d\n", ret);
        return -4;
    }

    wm_printf("step3: establishing mqtt connection.\r\n");
    ret = mqtt_connect(&mqtt_demo_mqtt_broker);
    if(ret)
    {
        wm_printf("mqtt_connect ret=%d\n", ret);
        return -5;
    }

    packet_length = mqtt_demo_read_packet(MQTT_DEMO_READ_TIME_SEC, MQTT_DEMO_READ_TIME_US);
    if(packet_length < 0)
    {
        wm_printf("Error(%d) on read packet!\n", packet_length);
        mqtt_demo_close_socket(&mqtt_demo_mqtt_broker);
        return -1;
    }

    if(MQTTParseMessageType(mqtt_demo_packet_buffer) != MQTT_MSG_CONNACK)
    {
        wm_printf("CONNACK expected!\n");
        mqtt_demo_close_socket(&mqtt_demo_mqtt_broker);
        return -2;
    }

    if(mqtt_demo_packet_buffer[3] != 0x00)
    {
        wm_printf("CONNACK failed!\n");
        mqtt_demo_close_socket(&mqtt_demo_mqtt_broker);
        return -2;
    }

	char TopicBuf[64];
	sprintf(TopicBuf,SUBSCRIBE_TOP,MAC2STR(g_macBuf));
    wm_printf("step4: subscribe mqtt,%s\r\n",TopicBuf);
    mqtt_subscribe(&mqtt_demo_mqtt_broker, TopicBuf, &msg_id);

    packet_length = mqtt_demo_read_packet(MQTT_DEMO_READ_TIME_SEC, MQTT_DEMO_READ_TIME_US);
    if(packet_length < 0)
    {
        wm_printf("Error(%d) on read packet!\n", packet_length);
        mqtt_demo_close_socket(&mqtt_demo_mqtt_broker);
        return -1;
    }

    if(MQTTParseMessageType(mqtt_demo_packet_buffer) != MQTT_MSG_SUBACK)
    {
        wm_printf("SUBACK expected!\n");
        mqtt_demo_close_socket(&mqtt_demo_mqtt_broker);
        return -2;
    }

    msg_id_rcv = mqtt_parse_msg_id(mqtt_demo_packet_buffer);
    if(msg_id != msg_id_rcv)
    {
        wm_printf("%d message id was expected, but %d message id was found!\n", msg_id, msg_id_rcv);
        mqtt_demo_close_socket(&mqtt_demo_mqtt_broker);
        return -3;
    }

    wm_printf("step5: start the Heart-beat preservation timer\r\n");
    ret = tls_os_timer_create(&mqtt_demo_heartbeat_timer,
                              mqtt_demo_heart_timer,
                              NULL, (10 * HZ), TRUE, NULL);
    if (TLS_OS_SUCCESS == ret)
        tls_os_timer_start(mqtt_demo_heartbeat_timer);

    return 0;
}

3.W801通过MQTT与其他客户端通信

实际使用中,一般是通过手机来控制W801,具体的说,手机订阅W801的发布主题,W801订阅手机的发布主题。为了区分不同的设备,由于MAC地址是唯一的,那么主题中包含MAC地址是个不错的选择。所以在实际应用中,客户端ID和订阅主题每个设备是不同的,源代码中不能使用下面的宏定义:

	#define CLIENT_ID  	"W801-WIFI" 
	#define MQTT_DEMO_CLIENT_ID            CLIENT_ID
	#define publish_TOPIC  	"/mattress/0123456789AB/pub"	// 中间部分是MAC
	#define subscribe_TOPIC "/mattress/0123456789AB/sub"	// 中间部分是MAC

应该是类似这样(仅供参考):

#define		PUBLIC_STATUS_TOP	"/mattress/%02X%02X%02X%02X%02X%02X/pub"
#define		SUBSCRIBE_TOP 		"/mattress/%02X%02X%02X%02X%02X%02X/sub"
#define MAC2STR(a) (a)[0], (a)[1], (a)[2], (a)[3], (a)[4], (a)[5]	
//	获取MAC,保存在全局变量中
	char g_macBuf[6];
	tls_get_mac_addr(g_macBuf);
//	生成 客户端ID
	sprintf(mqtt_demo_mqtt_broker.clientid,"ID_%02X%02X%02X%02X%02X%02X",MAC2STR(g_macBuf));
//	生成订阅主题
	char TopicBuf[64];
	sprintf(TopicBuf,SUBSCRIBE_TOP,MAC2STR(g_macBuf));
    mqtt_subscribe(&mqtt_demo_mqtt_broker, TopicBuf, &msg_id);
//	生成发布主题
	MQTT_MSG_PRINT(jsonBuf);
	char PublicTopicBuf[64];
	sprintf(PublicTopicBuf,PUBLIC_STATUS_TOP,MAC2STR(g_macBuf));
	mqtt_publish(&mqtt_demo_mqtt_broker, (const char *)PublicTopicBuf, (const char *)jsonBuf, 39, 0);    

3.1通信协议格式

我定义的MQTT通信格式如下:

{
“ledctl”:“112D44”,
“heating”:“1122DXN”
}
我们只关心ledctl这个字段的前面3个字节:cmd[0]=设置/查询,cmd[1]=LED开关 , cmd[2]=LED间隔时间(200毫秒的倍数),

3.2处理json字符串

mqtt基本都是以json格式传输,把头文件《cjson.h》包含进来就可以使用cjson的API,我这里只用到3个API:

/* Supply a block of JSON, and this returns a cJSON object you can interrogate. Call cJSON_Delete when finished. */
extern cJSON *cJSON_Parse(const char *value);
/* Render a cJSON entity to text for transfer/storage. Free the char* when finished. */
extern char  *cJSON_Print(cJSON *item);
/* Get item "string" from object. Case insensitive. */
extern cJSON *cJSON_GetObjectItem(cJSON *object,const char *string);

3.3从json字符串中读取命令

这里只演示简单的LED控制,以字符串{“ledctl”:“112D44”}为例,

char *cmd= cJSON_GetObjectItem(cjson,LED_KEY)->valuestring;

cmd = “112D44”,那么

cmd[0]=‘1’,是设置指令
cmd[1]=‘1’,是打开LED闪烁;如果要关闭LED闪烁,使cmd[1]=‘0’
cmd[2]=‘2’,闪烁的间隔是2*200=400毫秒

extern	xQueueHandle mymqtt_Queue;		//mqtt 

void 	mqtt_msg_process(char * json_string)
{
	cJSON* cjson = cJSON_Parse(json_string);	//将JSON字符串转换成JSON结构体
	if(cjson == NULL)							//判断转换是否成功
	{
		MQTT_MSG_PRINT("cjson error...\r\n");
	}
	else
	{
		MQTT_MSG_PRINT("%s\n",cJSON_Print(cjson));		//打包成功调用cJSON_Print打印输出
	}	
	//printf("/*********************以下就是提取的数据**********************/\n");
	
	//	#cmd[0]=设置/查询,cmd[1]=LED开关 , cmd[2]=LED间隔时间,cmd[3]=,cmd[4]=,cmd[5]=
	char *cmd= cJSON_GetObjectItem(cjson,LED_KEY)->valuestring;	//解析字符串
	//	1.LED数据有效
	int cmdmsg;
	if(strlen(cmd) == 6){
		//MQTT_MSG_PRINT("%s,%s:%s\n",__FUNCTION__,RHYTHM_KEK,name);
		//	1.1	查询
		if(cmd[0] == '0')	replyMQTTStatus();
		//	1.2 设置
		else if(cmd[0] == '1'){
			if(cmd[1] == '1')		mqttParam.ledon=1;
			else					mqttParam.ledon=0;
			if((cmd[2] > '0')&&(cmd[2] < '9'))	mqttParam.setLedTimeIndex = cmd[2]-'0';
			if(mymqtt_Queue != NULL){
				cmdmsg = 1;
				xQueueSend(mymqtt_Queue,&cmdmsg,0);
				MQTT_MSG_PRINT("xQueueSend:mymqtt_Queue\n");
			}
		}
	}else		{
		MQTT_MSG_PRINT("%s,%s:nodata\n",__FUNCTION__,LED_KEY);
	}	

	cJSON_Delete(cjson);//清除结构体 
}

主程序调用下面查询MQTT消息的函数:

void	ProcessMQTT_CMD(void)
{
	int  msg;                      	
	if(xQueueReceive(mymqtt_Queue,&(msg),0))	
	{	//	1. 
		sysParam.cmdFrom = enu_cmdFromMQTT;
		if(msg == 1){
			sysParam.setLedTimeIndex =  mqttParam.setLedTimeIndex * 10;
			if(mqttParam.ledon){
				initLedTwingkle();
			}else{
				StatVibrateIsOn = 0;
			}
		}else if(msg == 2){
		}
	}
}

还有控制LED闪烁的函数,void LedTwinkle(void),每20毫秒调用一次:

void	initLedTwingkle(void)
{
	//	sysParam.setLedTimeIndex 在MQTT命令中赋值
	StatVibrateIsOn = 1;
	StatLedIsOn = 1;
	sysParam.vibrateTimeUnit = 0;
}
void	LedTwinkle(void)
{
	if(!StatVibrateIsOn)	return;
	if(StatLedIsOn){
		sysParam.vibrateTimeUnit++;
		if(sysParam.vibrateTimeUnit > sysParam.setLedTimeIndex){
			sysParam.vibrateTimeUnit = 0;
			StatLedIsOn = 0;
		}
	}else{
		sysParam.vibrateTimeUnit++;
		if(sysParam.vibrateTimeUnit > sysParam.setLedTimeIndex){
			sysParam.vibrateTimeUnit = 0;
			StatLedIsOn = 1;
		}		
	}
	tls_gpio_write(LED_G,StatLedIsOn);
}

3.4用mqtt.fx控制W801

mqtt.fx发布的主题就是W801订阅的主题,发送{“ledctl”:“112D44”},开始闪烁:
在这里插入图片描述
发送{“ledctl”:“102D44”},停止闪烁:
在这里插入图片描述

4.MQTT初始化代码应该放在哪里

  1. 联网成功就可以启动MQTT。
  2. 短时间的断网(比如5分钟,更长的时间不知道)后,恢复联网,mqtt客户端还可以继续工作,订阅的主题依然有效;那么,只需要第一次联网成功初始化MQTT,后面的断网重连不需要重新初始化MQTT。
    在这里插入图片描述
int mqtt_demo(void);
int	flagMqttIsStart=0;
static void con_net_status_changed_event(u8 status )
{
    switch(status)
    {
    case NETIF_WIFI_JOIN_SUCCESS:
        printf("NETIF_WIFI_JOIN_SUCCESS\n");
        break;
    case NETIF_WIFI_JOIN_FAILED:
        printf("NETIF_WIFI_JOIN_FAILED\n");
        break;
    case NETIF_WIFI_DISCONNECTED:
        printf("NETIF_WIFI_DISCONNECTED\n");
        break;
    case NETIF_IP_NET_UP:
    {
        struct tls_ethif *tmpethif = tls_netif_get_ethif();
        print_ipaddr(&tmpethif->ip_addr);
#if TLS_CONFIG_IPV6
        print_ipaddr(&tmpethif->ip6_addr[0]);
        print_ipaddr(&tmpethif->ip6_addr[1]);
        print_ipaddr(&tmpethif->ip6_addr[2]);
#endif
		if(flagMqttIsStart == 0){
			mqtt_demo();
			flagMqttIsStart = 1;
		}
    }
    break;
    default:
        //printf("UNKONWN STATE:%d\n", status);
        break;
    }
}

5.编译《cjson.c》出错

找不到标准数学函数pow(幂函数,在转换科学记数法表示的字符串用到),floor(浮点取整函数):
在这里插入图片描述
在这里插入图片描述
考虑到我没有用到这些功能,直接屏蔽错误行,编译通过,运行正常。

posted @ 2022-08-06 08:33  汉塘阿德  阅读(148)  评论(0编辑  收藏  举报  来源