好的,您的设备在信号不好的地方(如地下室)或进入信号不好的区域后,无法继续正常工作,必须重启后才能恢复。这是一个常见的问题,通常是因为在信号丢失后,设备没有正确地处理网络和MQTT连接的断开和重连。
我查看了您的代码,发现它在初始化后,没有处理网络和MQTT连接的状态变化。如果网络连接断开或者MQTT连接中断,代码没有尝试重新连接。因此,当设备进入信号不好的区域后,即使信号恢复,设备也不会自动重新连接网络和MQTT服务器。
为了解决这个问题,我们需要在代码中添加以下功能:
- 定期检查网络连接状态:如果网络连接断开,尝试重新连接网络。
- 定期检查MQTT连接状态:如果MQTT连接断开,尝试重新连接MQTT服务器。
- 监控GPS数据接收情况:如果长时间没有收到GPS数据,重新初始化GPS模块。
- 处理可能的模块异常情况:例如,在模块无法响应指令时,重启模块。
下面我将修改您的代码,添加上述功能。
#include <SoftwareSerial.h>
#include <stdlib.h> // 用于 rand() 和 srand()
#include <time.h> // 用于 time()
const char *deviceName = "CAR1";//名称不能重复
// 定义软件串口用于 4G 模块通信(RX=10, TX=11)
SoftwareSerial moduleSerial(10, 11);
// GPS 数据缓冲区
char gpsBuffer[200]; // 存储 GPS 数据的缓冲区
bool gpsDataReady = false;
bool tiaoshi = true; // 开启调试信息
// 定义时间变量用于定期检查
unsigned long lastNetworkCheck = 0;
unsigned long networkCheckInterval = 60000; // 每60秒检查一次网络连接
unsigned long lastMQTTCheck = 0;
unsigned long mqttCheckInterval = 60000; // 每60秒检查一次MQTT连接
unsigned long lastGPSDataTime = 0;
unsigned long gpsTimeout = 60000; // 如果60秒内没有收到GPS数据,重新初始化GPS
// 函数声明
void sendCommand(const char *cmd);
bool sendCommand(const char *cmd, String &response);
void parseGPSData(const char *data);
void sendMQTTMessage(float latitude, float longitude);
bool checkNetworkStatus();
void reinitializeNetwork();
bool checkMQTTStatus();
void reinitializeMQTT();
void reinitializeGPS();
// 初始化
void setup() {
// 设置串口通信
if (tiaoshi)
Serial.begin(9600); // Arduino 串口调试
moduleSerial.begin(9600); // 模块串口通信
// 等待模块启动
delay(8000);
if (tiaoshi)
Serial.println("初始化模块...");
// 初始化 GPS 定位
sendCommand("AT+MGPSC=1\r\n"); // 开启 GPS 功能
delay(500);
sendCommand("AT+MGPSGET=ALL,1\r\n"); // 开启 NMEA 数据输出
delay(500);
// 设置 4G MQTT 连接
sendCommand("AT+MDISCONNECT\r\n"); // 断开已有 MQTT 连接
delay(500);
sendCommand("AT+MIPCLOSE\r\n"); // 关闭当前会话
delay(500);
sendCommand("AT+QICSGP=1,1,\"\",\"\",\"\"\r\n"); // 设置 APN
delay(500);
sendCommand("AT+NETOPEN\r\n"); // 打开网络
delay(3000);
// 配置 MQTT 连接
char command[100]; // 假设命令长度不会超过 100 个字符
srand(time(NULL)); // 初始化随机数种子
sprintf(command, "AT+MCONFIG=\"%s_%d\",\"\",\"\",0,0,0,\"1883\",\"2024\"\r\n", deviceName, rand() % 1000);
sendCommand(command);
delay(500);
sendCommand("AT+MIPSTART=\"nbzch.cn\",1883,3\r\n"); // 连接 MQTT Broker
delay(2000);
sendCommand("AT+MCONNECT=0,60\r\n"); // 连接到 MQTT 服务器
delay(500);
// 向 gps/lastLocation 发送 online 消息
char onlineMessage[100];
snprintf(onlineMessage, sizeof(onlineMessage), "{\"name\":\"%s\",\"msg\":\"online\"}", deviceName);
char mqttCommand[100];
snprintf(mqttCommand, sizeof(mqttCommand), "AT+MPUBEX=\"gps/lastLocation\",0,0,%d\r\n", strlen(onlineMessage));
if (tiaoshi) {
Serial.print("发送指令: ");
Serial.println(mqttCommand);
}
sendCommand(mqttCommand); // 发送 MQTT 发布指令
moduleSerial.print(onlineMessage); // 发送消息内容
if (tiaoshi) {
Serial.print("发送 MQTT 消息: ");
Serial.println(onlineMessage);
}
lastGPSDataTime = millis(); // 初始化最后一次收到GPS数据的时间
}
void loop() {
static int index = 0;
// 读取模块串口数据
while (moduleSerial.available()) {
char c = moduleSerial.read();
if (c == '\n') {
gpsBuffer[index] = '\0';
index = 0;
gpsDataReady = true;
} else if (c != '\r') {
gpsBuffer[index++] = c;
}
if (gpsDataReady) {
gpsDataReady = false;
if (strncmp(gpsBuffer, "$GNGLL", 6) == 0) {
if (tiaoshi)
Serial.println(gpsBuffer); // 打印完整的 $GNGLL 句子
lastGPSDataTime = millis(); // 更新最后一次收到 GPS 数据的时间
parseGPSData(gpsBuffer);
}
}
}
// 定期检查网络状态
if (millis() - lastNetworkCheck >= networkCheckInterval) {
lastNetworkCheck = millis();
if (!checkNetworkStatus()) {
// 网络未连接,重新初始化网络
reinitializeNetwork();
}
}
// 定期检查 MQTT 状态
if (millis() - lastMQTTCheck >= mqttCheckInterval) {
lastMQTTCheck = millis();
if (!checkMQTTStatus()) {
// MQTT 未连接,重新初始化 MQTT 连接
reinitializeMQTT();
}
}
// 检查是否长时间未收到 GPS 数据
if (millis() - lastGPSDataTime >= gpsTimeout) {
// 长时间未收到 GPS 数据,重新初始化 GPS
reinitializeGPS();
lastGPSDataTime = millis(); // 重置计时器
}
}
// 发送 AT 指令并调试输出
void sendCommand(const char *cmd) {
moduleSerial.print(cmd);
if (tiaoshi) {
Serial.print("发送指令: ");
Serial.println(cmd);
}
delay(500); // 等待模块响应
if (tiaoshi)
while (moduleSerial.available()) {
Serial.write(moduleSerial.read()); // 打印模块的响应
}
}
// 发送 AT 指令并获取响应
bool sendCommand(const char *cmd, String &response) {
moduleSerial.print(cmd);
if (tiaoshi) {
Serial.print("发送指令: ");
Serial.println(cmd);
}
response = "";
unsigned long startTime = millis();
while (millis() - startTime < 2000) { // 等待最多2秒
while (moduleSerial.available()) {
char c = moduleSerial.read();
response += c;
}
if (response.indexOf("OK") != -1 || response.indexOf("ERROR") != -1) {
break;
}
}
if (tiaoshi) {
Serial.print("模块响应: ");
Serial.println(response);
}
return response.indexOf("OK") != -1;
}
// 转换 GPS 数据中的纬度和经度格式
float convertToDecimalDegrees(float rawValue) {
int degrees = (int)(rawValue / 100); // 提取度部分
float minutes = rawValue - (degrees * 100); // 提取分部分
return degrees + (minutes / 60.0); // 转换为十进制度
}
// 解析 GPS 数据
void parseGPSData(const char *data) {
char *token;
char gpsData[100];
strncpy(gpsData, data, sizeof(gpsData));
char *saveptr;
token = strtok_r(gpsData, ",", &saveptr);
token = strtok_r(NULL, ",", &saveptr); // Latitude
char gpsLat[15];
strcpy(gpsLat, token);
token = strtok_r(NULL, ",", &saveptr); // N/S Indicator
char nsIndicator = *token;
token = strtok_r(NULL, ",", &saveptr); // Longitude
char gpsLon[15];
strcpy(gpsLon, token);
token = strtok_r(NULL, ",", &saveptr); // E/W Indicator
char ewIndicator = *token;
token = strtok_r(NULL, ",", &saveptr); // Status
token = strtok_r(NULL, ",", &saveptr);
char status = *token;
if (status == 'A') { // 如果定位有效
float rawLatitude = atof(gpsLat); // 原始纬度
float rawLongitude = atof(gpsLon); // 原始经度
float latitude = convertToDecimalDegrees(rawLatitude); // 转换纬度
float longitude = convertToDecimalDegrees(rawLongitude); // 转换经度
// 根据 N/S,E/W 指示符调整符号
if (nsIndicator == 'S') {
latitude = -latitude;
}
if (ewIndicator == 'W') {
longitude = -longitude;
}
// 调试输出
if (tiaoshi) {
Serial.print("纬度: ");
Serial.println(latitude, 6);
Serial.print("经度: ");
Serial.println(longitude, 6);
}
// 通过 MQTT 发送 GPS 数据
sendMQTTMessage(latitude, longitude);
} else {
if (tiaoshi)
Serial.println("无效定位");
}
}
// 发送 MQTT 消息
void sendMQTTMessage(float latitude, float longitude) {
char latBuffer[15]; // 用于存储纬度的字符串
char lonBuffer[15]; // 用于存储经度的字符串
// 转换浮点数为字符串
dtostrf(latitude, 0, 6, latBuffer); // 6 表示保留 6 位小数
dtostrf(longitude, 0, 6, lonBuffer);
char mqttMessage[150]; // 增大缓冲区
snprintf(mqttMessage, sizeof(mqttMessage), "{\"name\":\"%s\",\"lat\":%s,\"lon\":%s}", deviceName, latBuffer, lonBuffer);
// 调试打印
if (tiaoshi) {
Serial.print("调试: MQTT 消息 = ");
Serial.println(mqttMessage);
}
char mqttCommand[100];
snprintf(mqttCommand, sizeof(mqttCommand), "AT+MPUBEX=\"gps/location\",0,0,%d\r\n", strlen(mqttMessage));
// 调试打印
if (tiaoshi) {
Serial.print("发送指令: ");
Serial.println(mqttCommand);
}
sendCommand(mqttCommand); // 发送 MQTT 发布指令
moduleSerial.print(mqttMessage); // 发送消息内容
if (tiaoshi) {
Serial.print("发送 MQTT 消息: ");
Serial.println(mqttMessage);
}
}
// 检查网络状态
bool checkNetworkStatus() {
sendCommand("AT+NETOPEN?\r\n");
delay(500);
String response = "";
unsigned long startTime = millis();
while (millis() - startTime < 2000) { // 等待最多2秒
while (moduleSerial.available()) {
char c = moduleSerial.read();
response += c;
}
if (response.indexOf("+NETOPEN: 1") != -1) {
if (tiaoshi) Serial.println("网络已连接");
return true;
}
if (response.indexOf("+NETOPEN: 0") != -1) {
if (tiaoshi) Serial.println("网络未连接");
return false;
}
}
if (tiaoshi) Serial.println("网络状态检查超时");
return false;
}
// 重新初始化网络
void reinitializeNetwork() {
if (tiaoshi) Serial.println("重新初始化网络...");
sendCommand("AT+NETCLOSE\r\n");
delay(1000);
sendCommand("AT+QICSGP=1,1,\"\",\"\",\"\"\r\n"); // 设置 APN
delay(500);
sendCommand("AT+NETOPEN\r\n"); // 打开网络
delay(3000);
}
// 检查 MQTT 状态
bool checkMQTTStatus() {
sendCommand("AT+MSTATE?\r\n");
delay(500);
String response = "";
unsigned long startTime = millis();
while (millis() - startTime < 2000) { // 等待最多2秒
while (moduleSerial.available()) {
char c = moduleSerial.read();
response += c;
}
if (response.indexOf("+MSTATE: 0,1") != -1) {
if (tiaoshi) Serial.println("MQTT 已连接");
return true;
}
if (response.indexOf("+MSTATE: 0,0") != -1) {
if (tiaoshi) Serial.println("MQTT 未连接");
return false;
}
}
if (tiaoshi) Serial.println("MQTT 状态检查超时");
return false;
}
// 重新初始化 MQTT 连接
void reinitializeMQTT() {
if (tiaoshi) Serial.println("重新初始化 MQTT 连接...");
sendCommand("AT+MDISCONNECT\r\n"); // 断开 MQTT
delay(500);
sendCommand("AT+MIPCLOSE\r\n"); // 关闭当前会话
delay(500);
char command[100];
srand(time(NULL)); // 初始化随机数种子
sprintf(command, "AT+MCONFIG=\"%s_%d\",\"\",\"\",0,0,0,\"1883\",\"2024\"\r\n", deviceName, rand() % 1000);
sendCommand(command);
delay(500);
sendCommand("AT+MIPSTART=\"nbzch.cn\",1883,3\r\n"); // 连接 MQTT Broker
delay(2000);
sendCommand("AT+MCONNECT=0,60\r\n"); // 连接到 MQTT 服务器
delay(500);
}
// 重新初始化 GPS
void reinitializeGPS() {
if (tiaoshi) Serial.println("重新初始化 GPS...");
sendCommand("AT+MGPSC=0\r\n"); // 关闭 GPS
delay(500);
sendCommand("AT+MGPSC=1\r\n"); // 开启 GPS
delay(500);
sendCommand("AT+MGPSGET=ALL,1\r\n"); // 开启 NMEA 数据输出
delay(500);
}
说明:
- 定期检查网络和MQTT连接状态:在
loop()
函数中,添加了定期检查网络和MQTT连接状态的功能。如果网络连接断开,调用reinitializeNetwork()
函数重新初始化网络。如果 MQTT 连接断开,调用reinitializeMQTT()
函数重新连接 MQTT 服务器。 - 监控 GPS 数据接收情况:如果超过指定时间(例如60秒)未收到新的 GPS 数据,调用
reinitializeGPS()
函数重新初始化 GPS 模块。这可以防止由于 GPS 信号丢失导致的长期无数据问题。 - 增强的调试信息:将
tiaoshi
变量设置为true
,以启用调试信息输出。这有助于实时监控设备的运行状态,便于调试和问题排查。 - 改进的 AT 指令发送和响应处理:修改了
sendCommand()
函数,使其能够发送 AT 指令并获取模块的响应。在需要获取模块响应的地方,例如检查网络和 MQTT 状态时,使用该函数来发送指令并解析响应。 - 处理 GPS 数据解析中的 N/S 和 E/W 指示符:在解析 GPS 数据时,考虑了北纬/南纬(N/S)和东经/西经(E/W)指示符,以确保地理坐标的正确性。
注意事项:
- 确保模块支持使用的 AT 指令,例如
AT+NETOPEN?
和AT+MSTATE?
。如果模块的指令集有所不同,需要根据模块手册进行调整。 - 测试代码时,建议在
tiaoshi
模式下运行,以便查看详细的日志信息,帮助调试。 - 如果模块在长时间无响应的情况下,需要考虑使用硬件看门狗或软件定时器来重启模块或 MCU。
通过上述修改,设备应能够在进入信号不好的区域后,自动检测并恢复网络和 MQTT 连接,而无需人工重启。
希望这些修改能解决您的问题。如有任何疑问,请随时提出!