STM32上移植MQTT协议使用W5500以太网芯片连接阿里云

STM32上移植MQTT协议使用W5500以太网芯片连接阿里云

前言

有机会做这次项目很开心,学到了很多东西,但刚开始的时候是真的从零开始,网上找的资料都大相径庭,很多代码都有问题,而且把别人做的模块一个个移植实在是很麻烦,为了不让后来需要做类似这个物联网项目的大家走歪路,我尽可能的把我这次做项目的核心事务说清楚,个人能力有限,有错的地方麻烦轻喷和指教,谢谢各位。

正题

首先先把要做什么搞清楚,顾名思义,本次项目内容即:
①把MQTT协议移植到stm32上;
②通过W5500以太网芯片,用网线连接至本地路由;
③由stm32本地网口连接到阿里云

使用到的工具:Keil5、pahoMqtt包、stm32f103RCT6、W5500、网线*1
调试工具:485通信usart口串口通信、串口调试助手、通信猫、阿里云、MQTTfx、MQTTBOX(当然如果不使用串口,单步Debug也是可以,我当时就这样,搞的我快崩溃了,最后还是弄了串口方便很多,也很简单)

学习资料:这边安利和推广一下B站上一个叫橙子的up主,他做的视频挺好的,很适合萌新上手,视频看看能明白很多东西;
友情链接:stm32 mqtt移植
如果有违权的地方麻烦友情提醒一下,方便我删除。

进入正题,MQTT协议,硬件部分不多说。
首先是下载Mqtt包,可以上github上下载,很多其他mqtt移植的博文都有链接,不过这里也贴一下;
友情链接:PahoMqtt-Github下载
下载后解压,把\paho.mqtt.embedded-c-master\MQTTPacket\src下的所有源文件导入到你的工程项目中,然后导入src所有的头文件路径,最后再把/sample下的transport.c、.h也导入;
这样首先第一步移植就成功了,很多人说transport.c里面有很多函数是Linux下编译的,所以用不了,那必然是用不了的2333,我们要做的事情就是重写transport.c和transport.h文件里的函数。主要有

transport_sendPacketBuffer //发送数据包到服务器
transport_getdata //从服务器获取数据
transport_open // 创建socket,绑定、连接等
transport_close // 关闭socket
这四个函数;
后续要Connect、Pub、Sub、Disconnect,都是基于这四个函数,以及一些MQTT_Package里头自带的一些不需要你写的函数;
最最重要的是搞清楚MQTT协议的工作方式,才能把功能实现,无论你是用一些常用的比如SIM800C、8266、Lora、W5500之类的通信途径,都是大同小异的;
工作方式:具体的几个名词不细讲。本地作为客户端连接到服务器,服务器作为Broker,你可以向Broker订阅话题,也可以向Broker发布话题,同样Broker作为服务器也可以对你Pub和Sub,宏观来看,是对等的;
首先你要有服务器的域名或者IP,只有域名就得做DNS解析,有IP直接IP连,端口号视你要连的服务器规定,阿里云就是固定的1883,通信猫18831之类的,在连接阿里云之前,建议把连接通信猫作为实验调通了作为练手;
连接时,有以下几个参数:
data.clientID.cstring // 客户端ID
data.keepAliveInterval //心跳时长,所谓心跳就是等待时间,阿里云规定要//大于60,否则拒绝连接
data.cleansession// 清除位,默认1
data.username.cstring//用户名
data.password.cstring//密码
data.MQTTVersion //规定版本号,比如Viersion4,连接阿里云,透传
关于Qos不细说;
连接阿里云的具体过程:发出连接报文>收到CONNACK(云确认到你的连接的回执)>订阅>收到SUBACK,返回Qos>不断Read做轮询,该干什么干什么,比如Pub、心跳以及等待云端Pub;
阿里云规定Pub和Sub的报文格式为Json格式,因此在Pub和在Sub解析的时候都要注意Json格式是否符合,网上有很多在线检测
很重要的一点,自带的Socket库里头的read都是阻塞的,如果你需要同时(近似)Pub和Sub,需修改成非阻塞的;

核心代码

下面贴上核心代码。

/**
*@Todo:通过tcp发数据到服务器
*/
int transport_sendPacketBuffer(unsigned char* buf, int buflen)
{
  return send(SOCK_TCPS,buf,buflen);
}

/**
*@Todo:阻塞方式接收tcp服务器发送之数据
*/
int transport_getdata(unsigned char* buf, int count)
{
  return recv(SOCK_TCPS,buf,count);
}

/**
*@Todo:打开一个socket并连接到服务器
*@retval 小于表示fail
*/
int transport_open(void){
	int32_t ret;
	//新建socket
	ret = socket(1, Sn_MR_TCP,2001, 0x00);
	 if(ret != SOCK_TCPS){
    printf("%d:Socket Error\r\n",SOCK_TCPS);
    while(1);
  }else{
    printf("%d:Opened\r\n",SOCK_TCPS);
  }
	 //连接TCP服务器
	ret = connect(SOCK_TCPS,domain_ip,1883); 
	if(ret != SOCK_OK){
    printf("%d:Socket Connect Error\r\n",SOCK_TCPS);
    //while(1);
  }else{
    printf("%d:Connected\r\n",SOCK_TCPS);
  }		
	return 0;
}

/**
*@Todo:关闭Socket
*/
int transport_close(void)
{
  disconnect(SOCK_TCPS);
  close(SOCK_TCPS);
  return 0;
}
/***************************以上为基本四函数************************/


	while(1){
		timeh++;
			if(connect_flag == 1&&timeh==1000)
		  {  // Json格式,根据Ailiyun中定义变量进行数据发布,payload为负载
				Ua = 56.7 -rand()%10+1;
				Ub = 34.8 -rand()%10+1;
				Uc = 69.2 -rand()%10+1;
	      sprintf((char*)payload_out,"{\"params\":{\"nUa\":+%0.1f,\"Ub\":+%0.1f,\"Uc\":+%0.1f},\"method\":\"thing.event.property.post\"}",Ua,Ub,Uc);
				payload_out_len = strlen((char*)payload_out);
				topicString.cstring = topic_pub_set_info;		//发布推送主题
				len = MQTTSerialize_publish(buf, buflen, 0, req_qos, retained, msgid, topicString, payload_out, payload_out_len);
				rc = transport_sendPacketBuffer(buf, len);
				if(rc == len)															//
					printf("send PUBLISH Successfully\r\n"); // 发出去和收回来的长度一样,代表发布ok
		  }
			if(timeh>=1000)
				timeh = 0;
			switch(msgtypes)
		{

			case CONNECT:	len = MQTTSerialize_connect(buf, buflen, &data); 						//发送连接信息
							rc = transport_sendPacketBuffer(buf, len);		//发送连接信息
							if(rc == len){															//
								printf("send CONNECT Successfully\r\n");
							}
							else{
								msgtypes =  CONNECT; 
								continue;
							}
							break;

			case CONNACK:   if(MQTTDeserialize_connack(&sessionPresent, &connack_rc,buf, buflen))	//收回执
							{
								printf("MQTT is conncet OK!\r\n");									//收到回执,连接成功
								connect_flag = 1;
								msgtypes = SUBSCRIBE;													//订阅操作
							}
							else
							{
								printf("Unable to connect, return code %d\r\n", connack_rc);		//失败
							}
							break;
			case SUBSCRIBE: topicString.cstring = topic_sub_set_info;
							len = MQTTSerialize_subscribe(buf, buflen, 0, msgid, 1, &topicString, &req_qos);
							rc = transport_sendPacketBuffer(buf, len);
							if(rc == len){
								goto tt;
							}
							else
							{
								continue;
							}
							break;
			case SUBACK: 	rc = MQTTDeserialize_suback(&submsgid, 1, &subcount, &granted_qos,buf, buflen);	//订阅成功 QoS                                                     
							printf("granted qos is %d\r\n", granted_qos);       							 
							if (granted_qos != 0)
								//goto exit;
							printf("Get qos");
	
							break;
			case PUBLISH:	rc = MQTTDeserialize_publish(&dup, &qos, &retained, &msgid, &receivedTopic,&payload_in, &payloadlen_in, (unsigned char*)buf, buflen);	//收到服务器消息
							printf("message arrived : %s\r\n", payload_in);
							json = cJSON_Parse((char *)payload_in);			//解析数据包
							if (!json)  
							{  
								printf("Error before: [%s]\r\n",cJSON_GetErrorPtr());  
							} 
							else
							{
								json_id = cJSON_GetObjectItem(json , "id"); 
								if(json_id->type == cJSON_String)
								{
									printf("id:%s\r\n", json_id->valuestring);  
								}
								json_params = cJSON_GetObjectItem(json , "params");
								if(json_params)  
								{  
									
									if(cJSON_GetObjectItem(json_params, "LEDSwitch"))
									{
                      
									}
									if(cJSON_GetObjectItem(json_params, "p2"))
									{
										
										}  
									}
								} 
							cJSON_Delete(json);   //释放空间
							if(qos == 1)
							{
								printf("publish qos is 1,send publish ack.\r\n");							//
								memset(buf,0,buflen);
								len = MQTTSerialize_ack(buf,buflen,PUBACK,dup,msgid);   					//publish ack                       
								rc = transport_sendPacketBuffer(buf, len);			
								if(rc == len)
									printf("send PUBACK Successfully\r\n");
								else
									printf("send PUBACK failed\r\n");                                       
							}
							break;
			case PUBACK:    printf("PUBACK!\r\n");	
							break;

			case PUBREC:    printf("PUBREC!\r\n");     				//just for qos2
							USART2_sendbyte(12);
							break;
							
			case PUBREL:    printf("PUBREL!\r\n");        			//just for qos2
							break;
			case PUBCOMP:   printf("PUBCOMP!\r\n");        			//just for qos2
							break;
			case PINGREQ:   len = MQTTSerialize_pingreq(buf, buflen);							
							rc = transport_sendPacketBuffer(buf, len);
							break;
			case PINGRESP:	printf("mqtt server Pong\r\n");  			   
 							msgtypes = PINGREQ;
							break;
			default:
							break;
		}
		
		if(msgtypes!=SUBSCRIBE){
  tt:			
			    rc=MQTTPacket_read(buf, buflen, transport_getdata);       	//轮询,根据收到的回执进行相应的处理
			    if(rc >0)													//如果收到正确的,存在的回执,则进到相应状态
				  {
				  	msgtypes = rc;
				  }else msgtypes=PINGREQ;
		  }

	}
	exit:
	transport_close();

MQTT通信部分使用了以上的状态机,其实大部分代码网上都有,但是都不是完全正确,而且根据每个人情况不同,代码的理解也不同。

结论

写着写着感觉文字有点多…不太会写文章,大家有问题还是问吧,如果有看见会回答的,我个人认为这个项目大家没必要去花钱买资料看,网上可以白嫖的很多的,从0开始确实很艰辛…我也是花了几天时间去搞明白,最重要的是要理解,盲目的肝代码莫得用处(对自己说的…)

  • 9
    点赞
  • 49
    收藏
    觉得还不错? 一键收藏
  • 12
    评论
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值