好的,您的设备在信号不好的地方(如地下室)或进入信号不好的区域后,无法继续正常工作,必须重启后才能恢复。这是一个常见的问题,通常是因为在信号丢失后,设备没有正确地处理网络和MQTT连接的断开和重连。

我查看了您的代码,发现它在初始化后,没有处理网络和MQTT连接的状态变化。如果网络连接断开或者MQTT连接中断,代码没有尝试重新连接。因此,当设备进入信号不好的区域后,即使信号恢复,设备也不会自动重新连接网络和MQTT服务器。

为了解决这个问题,我们需要在代码中添加以下功能:

  1. 定期检查网络连接状态:如果网络连接断开,尝试重新连接网络。
  2. 定期检查MQTT连接状态:如果MQTT连接断开,尝试重新连接MQTT服务器。
  3. 监控GPS数据接收情况:如果长时间没有收到GPS数据,重新初始化GPS模块。
  4. 处理可能的模块异常情况:例如,在模块无法响应指令时,重启模块。

下面我将修改您的代码,添加上述功能。

#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);
}

说明:

  1. 定期检查网络和MQTT连接状态:在 loop() 函数中,添加了定期检查网络和MQTT连接状态的功能。如果网络连接断开,调用 reinitializeNetwork() 函数重新初始化网络。如果 MQTT 连接断开,调用 reinitializeMQTT() 函数重新连接 MQTT 服务器。
  2. 监控 GPS 数据接收情况:如果超过指定时间(例如60秒)未收到新的 GPS 数据,调用 reinitializeGPS() 函数重新初始化 GPS 模块。这可以防止由于 GPS 信号丢失导致的长期无数据问题。
  3. 增强的调试信息:将 tiaoshi 变量设置为 true,以启用调试信息输出。这有助于实时监控设备的运行状态,便于调试和问题排查。
  4. 改进的 AT 指令发送和响应处理:修改了 sendCommand() 函数,使其能够发送 AT 指令并获取模块的响应。在需要获取模块响应的地方,例如检查网络和 MQTT 状态时,使用该函数来发送指令并解析响应。
  5. 处理 GPS 数据解析中的 N/S 和 E/W 指示符:在解析 GPS 数据时,考虑了北纬/南纬(N/S)和东经/西经(E/W)指示符,以确保地理坐标的正确性。

注意事项:

  • 确保模块支持使用的 AT 指令,例如 AT+NETOPEN?AT+MSTATE?。如果模块的指令集有所不同,需要根据模块手册进行调整。
  • 测试代码时,建议在 tiaoshi 模式下运行,以便查看详细的日志信息,帮助调试。
  • 如果模块在长时间无响应的情况下,需要考虑使用硬件看门狗或软件定时器来重启模块或 MCU。

通过上述修改,设备应能够在进入信号不好的区域后,自动检测并恢复网络和 MQTT 连接,而无需人工重启。

希望这些修改能解决您的问题。如有任何疑问,请随时提出!

发表评论