相关阅读
- 阿里云物理网平台搭建:设备接入阿里云物联网平台_阿里云的物联网平台接口地址pushthingconfigbydevice-CSDN博客
- STM32-HAL库串口Demo:STM32-HAL库串口空闲中断DMA接收数据Demo-CSDN博客
- cJSON详解:STM32单片机与cJSON:构建并解析JSON数据_单片机 cjson-CSDN博客
一、配置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库:
-
- 从Eclipse Paho项目的官方网站GitHub - eclipse/paho.mqtt.c: An Eclipse Paho C client library for MQTT for Windows, Linux and MacOS. API documentation: https://eclipse.github.io/paho.mqtt.c/ 下载Paho MQTT C库的源代码。
- 也可以在分享的源码中直接拷贝工程目录中的MQTT文件夹
- 导入工程:
- 导入c文件
- 导入h文件
- 导入c文件
- 导入文件成功够便可以编写驱动,在工程中新建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消息发布计数(检测消息发布是否成功) |
|
|
publish_overtime |
发布超时标志 |
mqtt_publish_count发布计数值大于0时,每秒自加 |
|
ping_overtime |
ping消息超时标志 |
连接成功后,每秒自加 |
|
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;
}