STM32+有人4G模块接入阿里云平台

相关阅读

一、配置4G模块

本次实验采用的4G模块为有人LET-7S1模块,利用模块的透传功能完成硬件设备与云端的通信,由于4G模块启动需要时间,在STM32连接时需要等待模块启动完成,在此通过模块全局参数的启动信息来实现,启动信息内容会在模块启动后通过串口发送,本次实验的信息为:DJ LTE OK。

1. 连接地址配置

2. 模块参数配置

二、移植程序

1. HAL构建工程

本次实验采用的串口Demo程序详见:STM32-HAL库串口空闲中断DMA接收数据Demo-CSDN博客

  • 串口1:日志打印,控制
  • 串口3:4G模块通信串口
  • TIM1:计时

2. 移植MQTT

在STM32上实现MQTT客户端,可以选择多种MQTT库。其中,Eclipse Paho MQTT C库是一个流行的选择,因为它简单、轻量级且功能强大。本次采用的是Paho MQTT C库。

2.1. MQTT移植
  • 下载MQTT库
  • 导入工程:
    • 导入c文件
    • 导入h文件
  • 导入文件成功够便可以编写驱动,在工程中新建user_lte源文件和头文件,在该文件中完成针对本次使用的有人4G模块的mqtt应用函数。
2.2. MQTT连接
流程

源码
void LTE_Mqtt_Connect(uint8_t *ack_data)
{
	if(strstr((char *)ack_data,"DJ LTE OK"))//判断lte模块是否启动
	{
		//mqtt 连接
		Mqtt_Connect();
	}
    
	if(ack_data[0]>>4 == CONNACK) //查看报文类型,判断mqtt是否连接成功
	{
		//mqtt 订阅服务消息
		memset(ex_mqtt_topic,0,TOPIC_LENGTH);
		sprintf(ex_mqtt_topic,Topic_ServiceGet);
		Mqtt_Subscribe(ex_mqtt_topic);
		printf("mqtt connect success\r\n");
	}
	if(ack_data[0]>>4 == SUBACK) //查看报文类型,判断订阅消息是否成功
	{
		is_mqtt_connected = 1;//连接成功
		connect_overtime = 0;//超时计数清零
		printf("mqtt subscribe success\r\n");
	}
}

void Mqtt_Connect(void)
{
	MQTTPacket_connectData data = MQTTPacket_connectData_initializer;
	int buflen = sizeof(mqtt_parse_buf);
	data.keepAliveInterval = 120;	//(S)会话时长,超过下线
	data.cleansession = 1;
	data.clientID.cstring = MYclientID;
	data.username.cstring = MYusername;
	data.password.cstring = MYpassword;
	/*拼接MQTT连接语句*/
	memset(mqtt_parse_buf,0,buflen);
	int len = MQTTSerialize_connect(mqtt_parse_buf, buflen, &data);//拼装mqtt内容
	HAL_UART_Transmit(LTE_UART, mqtt_parse_buf, len, 0xffff);
}
注意事项

在Mqtt_Connect()中需要提供的参数分别为:

  • keepAliveInterval,会话时间,超过该时间的1.5倍没有消息传递将会掉线,本次实验中设置的为120s则在180s内没有通信设备将离线
  • 三元组信息在设备详情界面查找
    • clientID
    • username
    • password
2.3. 订阅
源码

该代码中主要利用MQTTSerialize_subscribe函数对相关主题进行订阅

void Mqtt_Subscribe(char * subtopic_buf)
{
	static uint16_t picket_id = 1;
	
	MQTTString topicString = MQTTString_initializer;
	int req_qos = 0, len = 0;
	int buflen = sizeof(mqtt_parse_buf);
	
	topicString.cstring = subtopic_buf;
	memset(mqtt_parse_buf,0,buflen);
	len = MQTTSerialize_subscribe(mqtt_parse_buf, buflen, 0, ++picket_id, 1, &topicString, &req_qos);
	HAL_UART_Transmit(LTE_UART, mqtt_parse_buf, len, 0xffff);
}
2.4. 发布
源码

在代码中利用MQTTSerialize_publish函数对发布主题和内容进行拼接并利用串口透传发送。

void Mqtt_Publish(char * topic, unsigned char * payload) 
{
	MQTTString topicString = MQTTString_initializer;
	topicString.cstring = topic;
	int buflen = sizeof(mqtt_parse_buf);
	uint8_t dup = 0;//重复
	int qos =1;
	uint8_t retain = 0;
	uint16_t packetid = 3;
	uint8_t ret =0;
	memset(mqtt_parse_buf,0,buflen);
	int len = MQTTSerialize_publish(mqtt_parse_buf, buflen, dup, qos, retain, packetid, topicString, payload, strlen((char *)payload));
	
	ret = HAL_UART_Transmit(LTE_UART, mqtt_parse_buf, len, 0xffff);

	printf("send_ret:%d\r\n",ret);
	
	mqtt_publish_count++;
}
2.5. 保活机制

mqtt的保活是在会话有效时间内发送ping消息,在本次实验中每60s发送了一次保活消息(ping)

源码
void Mqtt_ping(void)
{
    int buflen = sizeof(mqtt_parse_buf);

    int len = MQTTSerialize_pingreq(mqtt_parse_buf, buflen);
    HAL_UART_Transmit(LTE_UART, mqtt_parse_buf, len, 0xffff);
	printf("***ping send hex:\r\n");
	for(int i=0;i<len;i++)
	{
		printf("%02x ",mqtt_parse_buf[i]);
	}
	printf("***ping send len:%d\r\n",len);
	mqtt_ping_count++;
}
效果
  • 未发送ping消息:根据阿里云运行日志可以看到设备上线后3分钟掉线

  • 发送ping消息:设备一直在线,并能应答ping消息内容,通过设备端串口1反馈日志信息进行查看。

2.6. 异常处理

标志名称

作用

变化机制

清除机制

connect_overtime

连接超时标志

mqtt未连接成功时,每秒自加

  • 连接成功后停止
  • 模块初始化后清零

mqtt_publish_count

mqtt消息发布计数(检测消息发布是否成功)

  • mqtt消息发布时自加
  • 收到反馈发布ack时自减
  • 模块初始化后清零

publish_overtime

发布超时标志

mqtt_publish_count发布计数值大于0时,每秒自加

  • 模块初始化后清零
  • 收到反馈ack时清零

ping_overtime

ping消息超时标志

连接成功后,每秒自加

  • 模块初始化后清零
  • 收到反馈ack时清零
uint8_t connect_overtime = 0;	//mqtt连接超时标志位
int8_t mqtt_publish_count = 0;	//mqtt发布消息计数值
uint8_t publish_overtime = 0;	//mqtt发布超时标志位
uint8_t ping_overtime = 0;		//mqtt ping超时标志位

void Mqtt_Err_Deal(void)
{
	//mqtt 异常处理
	if(is_mqtt_connected==0)//mqtt 未连接成功
	{
		if(connect_overtime > 30)//30s 超时
		{
			printf("mqtt connect error\r\n");
			printf("LTE reset\r\n");
			LTE_Init();
		}
	}else{
		//mqtt 连接成功 发布失败
		if(publish_overtime > 30)//30s 超时
		{
			printf("mqtt publish error\r\n");
			printf("LTE reset\r\n");
			LTE_Init();
		}
		//ping失败
		if(ping_overtime > 130)//130s 超时
		{
			printf("mqtt ping error\r\n");
			printf("LTE reset\r\n");
			LTE_Init();
		}	
	}
}

3. 创建以及解析Json

具体json内容可以参考STM32单片机与cJSON:构建并解析JSON数据_单片机 cjson-CSDN博客

3.1. 创建Json
void sentPropertyJson(float val)
{
	cJSON *pOrder = cJSON_CreateObject();  
    cJSON *params = cJSON_CreateObject(); 
	char *json_body = NULL;  
	
	 if (pOrder == NULL || params == NULL)  
    {  
        printf("Creat Vital JSONobj Err\n");  
        cJSON_Delete(pOrder);  
        cJSON_Delete(params);  
        return;  
    }  	
	
	 // 添加参数到params对象,并检查每个调用的返回值  
    if (!cJSON_AddNumberToObject(params,"temp",val)) 
    {  
        printf("Add vital data to JSONobj Err\n");  
        cJSON_Delete(pOrder);  
        cJSON_Delete(params);  
        return;  
    }  
	// 添加params到pOrder对象  
    cJSON_AddItemToObject(pOrder, "params", params);  
	
	// 转换JSON对象为字符串  
    json_body = cJSON_PrintUnformatted(pOrder);  
    if (json_body == NULL)  
    {  
        printf("Print Vital JSON Err\n");  
        cJSON_Delete(pOrder);  
        return;  
    }  
	// 打印并发送JSON字符串  
    printf("Vital json: %s \r\n", json_body);
	
	if(is_mqtt_connected==1)//mqtt 连接成功
	{
		memset(mqtt_topic,0,TOPIC_LENGTH);
		sprintf(mqtt_topic,Topic_PropertyPost);
		Mqtt_Publish(mqtt_topic,(unsigned char *)json_body);
	}else{
		printf("mqtt publish err , mqtt don't connected \r\n");
	}
	
	// 释放内存   
    cJSON_free(json_body); 
	cJSON_Delete(pOrder);
}
3.2. 解析Json
int8_t Parse_MqttCmd(uint8_t *data)
{
	printf("in:%s \r\n",data);
	// 寻找JSON数据的开始位置  
	const char *json_start = strstr((char *)data, "{"); 
	if (json_start == NULL) 
	{  
		printf("JSON data not found in the received string.\n");  
		return -1;  
	}  
	size_t json_length = strlen(json_start);
	
	// 分配内存并复制JSON数据  
	char *json_data = (char *)malloc(json_length + 1);  
	if (json_data == NULL) {  
		printf("Memory allocation failed.\n");  
		return -1;  
	}  
	strncpy(json_data, json_start, json_length);  
	json_data[json_length] = '\0'; // 添加null终止符  

	//解析JSON数据  
	cJSON *root = cJSON_Parse(json_data);  
	if (root == NULL)
	{  
		printf("Failed to parse JSON data.\n");  
		cJSON_free(json_data);  
		return -1;  
	}  
	// ... 在这里处理JSON数据 ...  
		
	// 获取"params"字段的值  

	cJSON *paras = cJSON_GetObjectItemCaseSensitive(root, "params");/*获取obj中的paras的json*/
	if (paras && cJSON_IsObject(paras))
	{
		cJSON *sw_item = cJSON_GetObjectItemCaseSensitive(paras, "sw");/*开关状态*/  
		printf("---sw---\r\n");
		if (sw_item != NULL && cJSON_IsNumber(sw_item))
		{  
			int sw_value = sw_item->valueint; 	// cJSON使用int来表示bool值  
			printf("sw: %d \r\n", sw_value); 	// 输出sw的值,1代表true,0代表false 
		}else
		{  
			printf("Failed to get 'sw' value or it's not a boolean.\r\n");  
		}  
		cJSON *val_item = cJSON_GetObjectItemCaseSensitive(paras, "light");  
		if (val_item != NULL && cJSON_IsNumber(val_item))
		{  
			int val = val_item->valueint; // cJSON使用int来表示bool值  
			printf("light: %d \r\n", val); 
		}else
		{  
			printf("Failed to get 'light' value or it's not a number. \r\n");  
		} 
	}else
	{  
		printf("Failed to get 'sw' value or it's not a boolean.\n");  
	}  					
	// 释放资源 
	cJSON_Delete(root);  
	cJSON_free(json_data);  
	return 1;  
}

三、联调测试

1. 连接

2. 上报属性

3. 上报事件

4. 服务解析

四.源码下载

https://download.csdn.net/download/LJ_96/89815744

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值