pahoMQTT+SIM800C+STM32 移植和使用

1. 介绍

随着物联网的全面普及,作为终端的单片机也需要联网,本文讲述的是一种低成本的物联网方案,硬件使用GPRS模块和STM32单片机,网络基于MQTT报文协议,相比于WIFI局域网,GPRS(最新LTE)具有更大的适用性。同时MQTT协议具有使用方便,资源消耗少和成本低的优点,非常适合物联网设备使用。

硬件:STM32F103RBT6  SIM800C 

软件:stm32_hal  Keil MDK V5  pahoMQTT 

pahoMQTT 选择Embedded C版本,主要考虑单片机平台的资源有限,嵌入式版本不使用动态内存分配,基本与平台无关,使用起来相对比较困难。

选择Embedded C/C++版本,github下载地址

2. 拷贝需要移植的文件

pahoMQTT实现的文件,在目录 paho.mqtt.embedded-c-master\MQTTPacket\src 下(10个C文件及头文件)

接口和主函数调文件  在目录 paho.mqtt.embedded-c-master\MQTTPacket\samples下(只需要2个C文件和对应头文件)

移植后的工程目录如下所示

3.修改接口和主测试文件

以上文件都找齐后,移植只需要修改transport.c下的几个接口函数,paho例程如下:

/*原文件使用的Socket接口作为例子,包含
transport_sendPacketBuffer  透传一串字符
transport_getdata           接收一串字符
transport_open              打开和连接MQTT
transport_close             关闭连接 (可省略为空)

移植时,只需要重写以上上个函数
*/
static int mysock = INVALID_SOCKET;


int transport_sendPacketBuffer(int sock, unsigned char* buf, int buflen)
{
	int rc = 0;
	rc = write(sock, buf, buflen);
	return rc;
}


int transport_getdata(unsigned char* buf, int count)
{
	int rc = recv(mysock, buf, count, 0);
	//printf("received %d bytes count %d\n", rc, (int)count);
	return rc;
}
/*不懂,移植时同上*/
int transport_getdatanb(void *sck, unsigned char* buf, int count)
{
	int sock = *((int *)sck); 	/* sck: pointer to whatever the system may use to identify the transport */
	/* this call will return after the timeout set on initialization if no bytes;
	   in your system you will use whatever you use to get whichever outstanding
	   bytes your socket equivalent has ready to be extracted right now, if any,
	   or return immediately */
	int rc = recv(sock, buf, count, 0);	
	if (rc == -1) {
		/* check error conditions from your system here, and return -1 */
		return 0;
	}
	return rc;
}

/**
return >=0 for a socket descriptor, <0 for an error code
@todo Basically moved from the sample without changes, should accomodate same usage for 'sock' for clarity,
removing indirections
*/
int transport_open(char* addr, int port)
{
int* sock = &mysock;
	int type = SOCK_STREAM;
	struct sockaddr_in address;
#if defined(AF_INET6)
	struct sockaddr_in6 address6;
#endif
	int rc = -1;
#if defined(WIN32)
	short family;
#else
	sa_family_t family = AF_INET;
#endif
	struct addrinfo *result = NULL;
	struct addrinfo hints = {0, AF_UNSPEC, SOCK_STREAM, IPPROTO_TCP, 0, NULL, NULL, NULL};
	static struct timeval tv;

	*sock = -1;
	if (addr[0] == '[')
	  ++addr;

	if ((rc = getaddrinfo(addr, NULL, &hints, &result)) == 0)
	{
		struct addrinfo* res = result;

		/* prefer ip4 addresses */
		while (res)
		{
			if (res->ai_family == AF_INET)
			{
				result = res;
				break;
			}
			res = res->ai_next;
		}

#if defined(AF_INET6)
		if (result->ai_family == AF_INET6)
		{
			address6.sin6_port = htons(port);
			address6.sin6_family = family = AF_INET6;
			address6.sin6_addr = ((struct sockaddr_in6*)(result->ai_addr))->sin6_addr;
		}
		else
#endif
		if (result->ai_family == AF_INET)
		{
			address.sin_port = htons(port);
			address.sin_family = family = AF_INET;
			address.sin_addr = ((struct sockaddr_in*)(result->ai_addr))->sin_addr;
		}
		else
			rc = -1;

		freeaddrinfo(result);
	}

	if (rc == 0)
	{
		*sock =	socket(family, type, 0);
		if (*sock != -1)
		{
#if defined(NOSIGPIPE)
			int opt = 1;

			if (setsockopt(*sock, SOL_SOCKET, SO_NOSIGPIPE, (void*)&opt, sizeof(opt)) != 0)
				Log(TRACE_MIN, -1, "Could not set SO_NOSIGPIPE for socket %d", *sock);
#endif

			if (family == AF_INET)
				rc = connect(*sock, (struct sockaddr*)&address, sizeof(address));
	#if defined(AF_INET6)
			else
				rc = connect(*sock, (struct sockaddr*)&address6, sizeof(address6));
	#endif
		}
	}
	if (mysock == INVALID_SOCKET)
		return rc;

	tv.tv_sec = 1;  /* 1 second Timeout */
	tv.tv_usec = 0;  
	setsockopt(mysock, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv,sizeof(struct timeval));
	return mysock;
}

int transport_close(int sock)
{
int rc;

	rc = shutdown(sock, SHUT_WR);
	rc = recv(sock, NULL, (size_t)0, 0);
	rc = close(sock);

	return rc;
}

网络连接使用的是SIM800C模块,使用AT指令实现网络数据的透传功能

一般启动步骤包含:上电->注册网络-> 附着GPRS->设置单链接->设置透传->激活网络->连接MQTT服务器 ,SIM800C初始化函数如下

........................一般函数略过........................

/*-------------------------------------------------*/
/*函数名:800C连接物联网云服务器                   */
/*参  数:data:数据                               */
/*返回值:无                                       */
/*-------------------------------------------------*/
char SIM800C_Connect_IoTServer(char *addr,int port)
{
	char i;                         //定义一个变量,用于for循环
	
	/*********SIM800C初始化**********/
	SIM800C_GPIO_Init();            //控制800C的IO初始化
	if(SIM800C_Power())             //控制800C开机或重启,如果返回1,说明开机重启失败,准备重启
	{            
		return 1;                   //返回1
	}
	
	/*********SIM800C注册网络**********/
	u1_printf("请等待注册上网络... ...\r\n");        //串口输出信息
	if(SIM800C_CREG(30))                             //等待注册上网络,超时单位1s,超时时间30s
	{                            
		u1_printf("注册网络超时,准备重启\r\n");     //串口输出信息
		return 2;                                    //返回2
	}
	else 
	{
		u1_printf("注册上网络\r\n");               //串口输出信息
	}
	
	
	/*********SIM800C查询信号**********/
	if(SIM800C_CSQ(60))                              //查询信号强度,超时单位100ms,超时时间6s
	{                             
		u1_printf("查询信号强度超时,准备重启\r\n"); //串口输出信息
		return 3;                                    //返回3	
	}
	
	/*********SIM800C附着GPRS**********/
	u1_printf("请等待附着上GPRS... ...\r\n");        //串口输出信息
	if(SIM800C_CGATT(30)){                           //查询附着GPRS,超时单位1s,超时时间30s
		u1_printf("查询附着GPRS超时,准备重启\r\n"); //串口输出信息
		return 4;                                    //返回4	
	}else u1_printf("附着上GPRS\r\n");               //串口输出信息
	
	/*********SIM800C设置单链接**********/
	u1_printf("请等待设置单链接... ...\r\n");        //串口输出信息
	if(SIM800C_SendCmd("AT+CIPMUX=0",60)){           //设置单链接模式,超时单位100ms,超时时间6s	
		u1_printf("设置单链接失败,准备重启\r\n");   //串口输出信息
		return 5;                                    //返回5	
	}else u1_printf("设置单链接模式成功\r\n");       //串口输出信息

	/*********SIM800C设置透传**********/
	u1_printf("请等待设置透传... ...\r\n");         //串口输出信息
	if(SIM800C_SendCmd("AT+CIPMODE=1",60)){         //设置透传模式,超时单位100ms,超时时间6s		
		u1_printf("设置透传失败,准备重启\r\n");    //串口输出信息
		return 6;                                   //返回6
	}else u1_printf("设置透传成功\r\n");            //串口输出信息   
	
	/*********SIM800C激活网络**********/
	if(SIM800C_ActivateNetwork()){                   //准备激活网络
		u1_printf("激活网络失败,准备重启\r\n");     //串口输出信息
		return 7;                                    //返回7
	}
	
	Delay_Ms(500);                                   //适当延时
	for(i=0;i<3;i++)                                 //重试3次                                   
	{
		u1_printf("请等待连接上服务器... ...\r\n");  //串口输出信息
		if(SIM800C_TCPConnect(addr,port,10)){        //同服务器建立TCP连接,超时单位1s,超时时间10s 
			u1_printf("连接失败,准备再次连接\r\n"); //串口输出信息
			Delay_Ms(500);                           //适当延时
			if(SIM800C_TCPClose(10)){                //准备再次连接前要先发送关闭指令,超时单位1s,超时时间10s 
				u1_printf("连接异常,准备重启\r\n"); //串口输出信息
				Delay_Ms(500);                       //适当延时
				return 8;                            //返回7
			}
		}else return 0;                              //正确,返回0
	}
	u1_printf("连接不上服务器,准备重启\r\n");       //串口输出信息
	return 9;
}

修改后的transport.c的程序如下 

#include "transport.h"
#include "sim800c.h"
#include "usart.h"
#include "Array.h"


/**********传输发送缓冲数据包*************/
int transport_sendPacketBuffer(unsigned char* buf, int buflen)
{
	int rc = 0;
	HAL_StatusTypeDef sta;
	sta = HAL_UART_Transmit(&huart2, buf, buflen, 1000);
	
	if(sta == 0)
		rc = buflen;
		
	return rc;
}

/*计算两次的间隔时间,单位 ms*/
uint32_t Diff_Tim(uint32_t pastim)
{
	uint32_t ntim;
	
	ntim = HAL_GetTick();
	
	if(ntim > pastim)
		return (ntim - pastim);
	else
		return (0xFFFFFFFF - pastim) + ntim;
}

/**********接收数据*************/
int transport_getdata(unsigned char* buf, int count)
{
	int rc = 0;
	
	uint32_t tim = HAL_GetTick();		//获得当前时间
	
	while(rc != count)					/*添加 1秒超时判断  如果超过1秒,跳出*/
	{
		rc = getQueueBuff(&u2_rx,(int8_t *)buf,count);
		
		if(Diff_Tim(tim) > 500)
		{
			break;
		}
		osDelay(5);
	}
	
	return rc;
}
int transport_getdatanb(unsigned char* buf, int count)
{
	int rc = 0;
	
	rc = getQueueBuff(&u2_rx,(int8_t *)buf,count);
	
	return rc;
}

/**********连接服务器*************/
int transport_open(char* addr, int port)
{
	return SIM800C_Connect_IoTServer(addr,port);
}

/**********断开服务器*************/
int transport_close(void)
{
	SIM800C_TCPClose(1);		
	printf("exit。。。\r\n");
}


/**********清空循环队列*************/
void RecClear(void)
{
	initQueue(&u2_rx);
}

 

4.主程序测试(pahoMQTT例程)

int main(int argc, char *argv[])
{
	MQTTPacket_connectData data = MQTTPacket_connectData_initializer;
	int rc = 0;
	int mysock = 0;
	unsigned char buf[200];
	int buflen = sizeof(buf);
	int msgid = 1;
	MQTTString topicString = MQTTString_initializer;
	int req_qos = 0;
	char* payload = "mypayload";
	int payloadlen = strlen(payload);
	int len = 0;
	char *host = "m2m.eclipse.org";
	int port = 1883;

	stop_init();
	if (argc > 1)
		host = argv[1];

	if (argc > 2)
		port = atoi(argv[2]);

	mysock = transport_open(host, port);
	if(mysock < 0)
		return mysock;

	printf("Sending to hostname %s port %d\n", host, port);

	data.clientID.cstring = "me";
	data.keepAliveInterval = 20;
	data.cleansession = 1;
	data.username.cstring = "testuser";
	data.password.cstring = "testpassword";

	len = MQTTSerialize_connect(buf, buflen, &data);
	rc = transport_sendPacketBuffer(mysock, buf, len);

	/* wait for connack */
	if (MQTTPacket_read(buf, buflen, transport_getdata) == CONNACK)
	{
		unsigned char sessionPresent, connack_rc;

		if (MQTTDeserialize_connack(&sessionPresent, &connack_rc, buf, buflen) != 1 || connack_rc != 0)
		{
			printf("Unable to connect, return code %d\n", connack_rc);
			goto exit;
		}
	}
	else
		goto exit;

	/* subscribe */
	topicString.cstring = "substopic";
	len = MQTTSerialize_subscribe(buf, buflen, 0, msgid, 1, &topicString, &req_qos);

	rc = transport_sendPacketBuffer(mysock, buf, len);
	if (MQTTPacket_read(buf, buflen, transport_getdata) == SUBACK) 	/* wait for suback */
	{
		unsigned short submsgid;
		int subcount;
		int granted_qos;

		rc = MQTTDeserialize_suback(&submsgid, 1, &subcount, &granted_qos, buf, buflen);
		if (granted_qos != 0)
		{
			printf("granted qos != 0, %d\n", granted_qos);
			goto exit;
		}
	}
	else
		goto exit;

	/* loop getting msgs on subscribed topic */
	topicString.cstring = "pubtopic";
	while (!toStop)
	{
		/* transport_getdata() has a built-in 1 second timeout,
		your mileage will vary */
		if (MQTTPacket_read(buf, buflen, transport_getdata) == PUBLISH)
		{
			unsigned char dup;
			int qos;
			unsigned char retained;
			unsigned short msgid;
			int payloadlen_in;
			unsigned char* payload_in;
			int rc;
			MQTTString receivedTopic;
            /*接收到消息  */
			rc = MQTTDeserialize_publish(&dup, &qos, &retained, &msgid, &receivedTopic,
					&payload_in, &payloadlen_in, buf, buflen);
			printf("message arrived %.*s\n", payloadlen_in, payload_in);
		}

		printf("publishing reading\n");
        /*发布消息*/
		len = MQTTSerialize_publish(buf, buflen, 0, 0, 0, 0, topicString, (unsigned char*)payload, payloadlen);
		rc = transport_sendPacketBuffer(mysock, buf, len);
	}

	printf("disconnecting\n");
	len = MQTTSerialize_disconnect(buf, buflen);
	rc = transport_sendPacketBuffer(mysock, buf, len);

exit:
	transport_close(mysock);

	return 0;
}

5. 结束

pahoMQTT embedded 移植相对简单,MQTT embedded提供的发布和订阅功能函数一般由两部分组成,先序列化在发送一串数据,使用起来困难一点点,根据MQTT的通信协议和机制,还是能够容易理解。主要难点在于SIM800C的驱动稳定可靠,需要完整的驱动程序朋友,欢迎留言。

 

评论 55
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值