2025-01-03T13:45:33.png
2025-01-04T13:46:23.png

实时 GPS 定位系统:多设备管理与轨迹绘制

在现代物联网应用中,实时 GPS 定位系统是一个非常重要的工具。它不仅可以用于车辆追踪、物流管理,还可以应用于个人设备定位、户外活动监控等场景。本文将介绍一个基于 MQTT 和高德地图的实时 GPS 定位系统,支持多设备管理、轨迹绘制以及钉钉群消息推送等功能。

一、 系统功能概述

1. 多设备管理

系统支持同时管理多个 GPS 设备。每个设备都有一个唯一的名称(如 XinEr 或 CAR1),并且可以独立显示其位置和运动轨迹。

2. 实时位置更新

通过 MQTT 协议,设备可以实时将 GPS 数据(纬度和经度)发送到服务器。服务器将这些数据解析后,动态更新高德地图上的设备位置。

3. 轨迹绘制

系统会自动记录每个设备的运动轨迹,并在地图上绘制出清晰的路径。不同设备的轨迹用不同颜色区分,方便用户查看。

4. 设备上线通知

当设备启动并连接到系统时,会向钉钉群发送一条上线通知,提示用户设备已开机。通知内容包含设备名称和一个链接,用户可以直接点击链接查看设备的最新位置。

5. 历史轨迹查询

系统会保留每个设备的最后一次定位信息。用户可以通过设备选择器查看特定设备的历史轨迹,或者同时查看所有设备的轨迹。

二、 系统架构

1. 设备端

使用 4G 模块和 GPS 模块获取设备的实时位置。

通过 MQTT 协议将 GPS 数据发送到服务器。

设备启动时发送 online 消息,通知服务器设备已上线。

2. 服务器端

使用 MQTT Broker 接收设备发送的 GPS 数据。

解析 GPS 数据并将其存储到缓存中。

根据用户请求,向设备发送 request 消息,获取最后一次定位信息。

3. 客户端

使用高德地图 JS API 显示设备位置和轨迹。

提供设备选择器,支持查看单个设备或所有设备的位置和轨迹。

动态更新地图视图,确保设备位置和轨迹始终可见。

三、核心功能实现

1. 多设备管理

系统通过设备名称区分不同的 GPS 设备。每个设备的标记和轨迹用不同颜色表示,用户可以通过设备选择器切换查看模式。

设备选择器:用户可以选择查看所有设备,或者单独查看某个设备的位置和轨迹。

动态添加设备:当新设备上线时,系统会自动将其添加到设备选择器中。

2. 实时位置更新

设备通过 MQTT 协议将 GPS 数据发送到服务器。服务器解析数据后,更新地图上的设备位置。

WGS84 转 GCJ02:由于高德地图使用 GCJ02 坐标系,系统会将设备发送的 WGS84 坐标转换为 GCJ02 坐标。

动态更新标记:设备位置发生变化时,地图上的标记会实时更新。

3. 轨迹绘制

系统会记录每个设备的运动轨迹,并在地图上绘制出清晰的路径。

轨迹颜色区分:不同设备的轨迹用不同颜色表示,方便用户区分。

动态调整视野:当用户选择查看某个设备时,地图会自动调整视野,确保该设备的标记和轨迹完全可见。

4. 设备上线通知

当设备启动并连接到系统时,会向钉钉群发送一条上线通知。

通知内容:通知包含设备名称和一个链接,用户可以直接点击链接查看设备的最新位置。

实时推送:通过钉钉 Webhook 机器人实现消息的实时推送。

四、系统优势

多设备支持:系统可以同时管理多个设备,满足复杂场景下的需求。

实时性强:通过 MQTT 协议实现数据的实时传输和更新。

用户友好:提供直观的地图界面和设备选择器,方便用户查看设备位置和轨迹。

扩展性强:系统架构清晰,易于扩展更多功能,如历史轨迹回放、设备状态监控等。

五、应用场景

物流追踪:实时监控物流车辆的位置和行驶路线,提高运输效率。

户外活动监控:用于登山、骑行等户外活动,实时查看参与者的位置。

设备管理:监控重要设备的位置,防止丢失或被盗。

六、功能实现方法

1. 设备启动时发送 online 消息:

设备启动后,向 gps/lastLocation 主题发送一条 online 消息,通知服务器设备已上线。

消息格式为 JSON,包含设备名称和消息内容:

{"name":"XinEr","msg":"online"}

2. GPS 数据采集与发送:

通过 GPS 模块获取设备的实时定位数据。

将定位数据转换为十进制度格式,并通过 MQTT 协议发送到 gps/location 主题。

消息格式为 JSON,包含设备名称、纬度和经度:

{"name":"XinEr","lat":39.974545,"lon":116.123456}

3. MQTT 消息推送:

使用 4G 模块连接 MQTT Broker,并发布消息到指定主题。

支持调试模式,可通过串口监视器查看发送的指令和消息内容。

七、总结

本文介绍了一个基于 MQTT 和高德地图的实时 GPS 定位系统,支持多设备管理、轨迹绘制以及钉钉群消息推送等功能。通过该系统,用户可以实时查看设备的位置和运动轨迹,并在设备上线时收到通知。系统的架构清晰,功能强大,适用于多种应用场景。

附:代码如下:

1. gps模块arduino程序:

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

// 函数声明
void sendCommand(const char *cmd);
void parseGPSData(const char *data);
void sendMQTTMessage(float latitude, float longitude);
bool tiaoshi = false;

// 初始化
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=0\r\n"); // 暂时关闭 NMEA 数据输出
  delay(500);

  // 设置 4G MQTT 连接
  sendCommand("AT+MDISCONNECT\r\n"); // 断开已有 MQTT 连接
  delay(200);
  sendCommand("AT+MIPCLOSE\r\n");    // 关闭当前会话
  delay(200);
  sendCommand("AT+QICSGP=1,1,\"\",\"\",\"\"\r\n"); // 设置 APN
  delay(200);
  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(200);
  sendCommand("AT+MIPSTART=\"nbzch.cn\",1883,3\r\n"); // 连接 MQTT Broker
  delay(2000);
  sendCommand("AT+MCONNECT=0,60\r\n"); // 连接到 MQTT 服务器
  delay(200);
  sendCommand("AT+MGPSGET=ALL,1\r\n");
  delay(200);

  // 向 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);
  }
}

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 句子
        parseGPSData(gpsBuffer);
      }
    }
  }
}

// 发送 AT 指令并调试输出
void sendCommand(const char *cmd) {
  moduleSerial.print(cmd);
  if (tiaoshi)
    Serial.print("发送指令: ");
  if (tiaoshi)
    Serial.println(cmd);
  delay(500); // 等待模块响应
  if (tiaoshi)
    while (moduleSerial.available()) {
      Serial.write(moduleSerial.read()); // 打印模块的响应
    }
}

// 转换 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 gpsLat[15], gpsLon[15], status;

  token = strtok(data, ",");
  token = strtok(NULL, ","); // Latitude
  strcpy(gpsLat, token);

  token = strtok(NULL, ",");
  token = strtok(NULL, ","); // Longitude
  strcpy(gpsLon, token);

  token = strtok(NULL, ",");
  token = strtok(NULL, ",");
  token = strtok(NULL, ","); // Status
  status = *token;

  if (status == 'A') { // 如果定位有效
    float rawLatitude = atof(gpsLat);            // 原始纬度
    float rawLongitude = atof(gpsLon);          // 原始经度
    float latitude = convertToDecimalDegrees(rawLatitude);  // 转换纬度
    float longitude = convertToDecimalDegrees(rawLongitude); // 转换经度

    // 调试输出
    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[100]; // 增大缓冲区
  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);
  }
}

2. html文件

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="initial-scale=1.0, user-scalable=no, width=device-width" />
    <title>高德地图 MQTT GPS 定位</title>
    <style>
      html, body, #container {
        width: 100%;
        height: 100%;
        margin: 0;
        padding: 0;
      }
      #device-selector {
        position: absolute;
        top: 10px;
        right: 10px;
        z-index: 1000;
        background: rgba(255, 255, 255, 0.8); /* 半透明背景 */
        padding: 10px;
        border-radius: 5px;
        box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
      }
      #device-selector select {
        width: 100%;
        padding: 5px;
        font-size: 14px;
        border: 1px solid #ccc;
        border-radius: 3px;
        background: rgba(255, 255, 255, 0.8); /* 半透明背景 */
      }
    </style>
  </head>
  <body>
    <div id="container"></div>

    <!-- 设备选择器 -->
    <div id="device-selector">
      <select id="device-list">
        <option value="all">显示所有设备</option>
        <!-- 设备选项将动态添加到这里 -->
      </select>
    </div>

    <!-- 安全密钥配置 -->
    <script type="text/javascript">
      window._AMapSecurityConfig = {
        securityJsCode: "你的安全密钥", // 替换为申请的安全密钥
      };
    </script>

    <!-- 引入高德地图 JS API Loader -->
    <script src="https://webapi.amap.com/loader.js"></script>
    <!-- 引入 MQTT.js -->
    <script src="https://unpkg.com/mqtt/dist/mqtt.min.js"></script>

    <script type="text/javascript">
      // WGS84 转 GCJ02 坐标转换函数
      const pi = 3.1415926535897932384626;
      const a = 6378245.0; // 椭球长半径
      const ee = 0.00669342162296594323; // 第一偏心率的平方

      function outOfChina(lat, lon) {
        return lon < 72.004 || lon > 137.8347 || lat < 0.8293 || lat > 55.8271;
      }

      function transformLat(x, y) {
        let ret = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * Math.sqrt(Math.abs(x));
        ret += (20.0 * Math.sin(6.0 * x * pi) + 20.0 * Math.sin(2.0 * x * pi)) * 2.0 / 3.0;
        ret += (20.0 * Math.sin(y * pi) + 40.0 * Math.sin(y / 3.0 * pi)) * 2.0 / 3.0;
        ret += (160.0 * Math.sin(y / 12.0 * pi) + 320 * Math.sin(y * pi / 30.0)) * 2.0 / 3.0;
        return ret;
      }

      function transformLon(x, y) {
        let ret = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * Math.sqrt(Math.abs(x));
        ret += (20.0 * Math.sin(6.0 * x * pi) + 20.0 * Math.sin(2.0 * x * pi)) * 2.0 / 3.0;
        ret += (20.0 * Math.sin(x * pi) + 40.0 * Math.sin(x / 3.0 * pi)) * 2.0 / 3.0;
        ret += (150.0 * Math.sin(x / 12.0 * pi) + 300.0 * Math.sin(x / 30.0 * pi)) * 2.0 / 3.0;
        return ret;
      }

      function wgs84ToGcj02(lat, lon) {
        if (outOfChina(lat, lon)) {
          return { lat, lon };
        }
        let dLat = transformLat(lon - 105.0, lat - 35.0);
        let dLon = transformLon(lon - 105.0, lat - 35.0);
        const radLat = lat / 180.0 * pi;
        let magic = Math.sin(radLat);
        magic = 1 - ee * magic * magic;
        const sqrtMagic = Math.sqrt(magic);
        dLat = (dLat * 180.0) / ((a * (1 - ee)) / (magic * sqrtMagic) * pi);
        dLon = (dLon * 180.0) / (a / sqrtMagic * Math.cos(radLat) * pi);
        const mgLat = lat + dLat;
        const mgLon = lon + dLon;
        return { lat: mgLat, lon: mgLon };
      }

      // 加载高德地图 JS API
      AMapLoader.load({
        key: "94af36b1b75bcda68807bd2684941e79", // 替换为申请的 Web 端 Key
        version: "2.0",
      })
        .then((AMap) => {
          // 1. 初始化高德地图
          const map = new AMap.Map("container", {
            zoom: 13, // 地图缩放级别
            center: [121.594, 29.8706], // 初始化中心位置
          });

          // 2. 存储设备标记、轨迹和颜色
          const markers = {};
          const paths = {}; // 存储每个设备的轨迹点
          const polylines = {}; // 存储每个设备的轨迹线
          const colors = { // 为不同设备分配不同颜色
            XIN: "#FF0000", // 红色
            CAR1: "#0000FF", // 蓝色
          };

          // 3. 设备选择器逻辑
          const deviceList = document.getElementById("device-list");

          // 显示所有设备
          deviceList.addEventListener("change", (event) => {
            const selectedDevice = event.target.value;

            // 隐藏所有标记和轨迹
            Object.values(markers).forEach(marker => marker.hide());
            Object.values(polylines).forEach(polyline => polyline.hide());

            if (selectedDevice === "all") {
              // 显示所有设备
              Object.values(markers).forEach(marker => marker.show());
              Object.values(polylines).forEach(polyline => polyline.show());
              adjustViewToAllDevices(); // 调整视野以包含所有设备
            } else {
              // 显示当前设备的标记和轨迹
              markers[selectedDevice].show();
              polylines[selectedDevice].show();
              // 调整视野以包含当前设备的标记和轨迹
              adjustViewToDevice(selectedDevice);
            }
          });

          // 动态添加设备选项
          function addDeviceOption(deviceName) {
            const option = document.createElement("option");
            option.value = deviceName;
            option.textContent = deviceName;
            deviceList.appendChild(option);
          }

          // 调整视野以包含所有设备
          function adjustViewToAllDevices() {
            const allMarkers = Object.values(markers);
            const allPolylines = Object.values(polylines);
            if (allMarkers.length > 0 || allPolylines.length > 0) {
              map.setFitView([...allMarkers, ...allPolylines]);
            }
          }

          // 调整视野以包含单个设备
          function adjustViewToDevice(deviceName) {
            if (markers[deviceName] && polylines[deviceName]) {
              map.setFitView([markers[deviceName], polylines[deviceName]]);
            }
          }

          // 4. MQTT 客户端连接
          const MQTT_BROKER = "wss://nbzch.cn:8084/mqtt"; // 替换为 MQTT Broker 地址
          const MQTT_TOPIC = "gps/location"; // 替换为主题
          const MQTT_LAST_LOCATION_TOPIC = "gps/lastLocation"; // 新增:请求最后位置的Topic

          const client = mqtt.connect(MQTT_BROKER);

          client.on("connect", function () {
            console.log("MQTT 已连接");
            client.subscribe(MQTT_TOPIC, function (err) {
              if (!err) {
                console.log("订阅主题成功:", MQTT_TOPIC);
                // 在连接成功后,立即请求最后位置
                client.publish(MQTT_LAST_LOCATION_TOPIC, '{"name":"edge","msg":"request"}', function (err) {
                  if (!err) {
                    console.log("已发送请求最后位置的消息");
                  } else {
                    console.error("发送请求最后位置的消息失败:", err);
                  }
                });
              } else {
                console.error("订阅主题失败:", err);
              }
            });
          });

          client.on("message", function (topic, message) {
            console.log(`收到消息 [${topic}]:`, message.toString());

            // 解析消息并更新地图位置
            try {
              const payload = JSON.parse(message.toString());
              const deviceName = payload.name; // 设备名称
              const rawLat = parseFloat(payload.lat); // WGS84 纬度
              const rawLon = parseFloat(payload.lon); // WGS84 经度

              // 进行 WGS84 到 GCJ02 转换
              const { lat, lon } = wgs84ToGcj02(rawLat, rawLon);

              if (!isNaN(lat) && !isNaN(lon)) {
                const position = [lon, lat];

                // 更新或创建标记
                if (markers[deviceName]) {
                  markers[deviceName].setPosition(position);
                } else {
                  const marker = new AMap.Marker({
                    position: position,
                    title: deviceName,
                    map: map,
                  });
                  markers[deviceName] = marker;
                  addDeviceOption(deviceName); // 添加设备选项
                  deviceList.value = "all"; // 切换到“显示所有设备”模式
                }

                // 更新轨迹
                if (!paths[deviceName]) {
                  paths[deviceName] = [];
                }
                paths[deviceName].push(position);

                // 绘制轨迹
                if (paths[deviceName].length >= 2) {
                  if (polylines[deviceName]) {
                    polylines[deviceName].setPath(paths[deviceName]);
                  } else {
                    const polyline = new AMap.Polyline({
                      path: paths[deviceName],
                      strokeColor: colors[deviceName] || "#00FF00", // 默认绿色
                      strokeWeight: 3,
                      map: map,
                    });
                    polylines[deviceName] = polyline;
                  }
                }

                // 根据当前模式调整视野
                if (deviceList.value === "all") {
                  // 显示所有设备的标记和轨迹
                  Object.values(markers).forEach(marker => marker.show());
                  Object.values(polylines).forEach(polyline => polyline.show());
                  adjustViewToAllDevices();
                } else if (deviceList.value === deviceName) {
                  adjustViewToDevice(deviceName);
                }
              } else {
                console.error("无效的经纬度数据");
              }
            } catch (e) {
              console.error("消息解析错误:", e);
            }
          });

          client.on("error", function (error) {
            console.error("MQTT 连接错误:", error);
          });
        })
        .catch((e) => {
          console.error("高德地图加载失败:", e); // 加载错误提示
        });
    </script>
  </body>
</html>

3.服务器脚本程序

import json
import time
import paho.mqtt.client as mqtt
import requests
import hmac
import hashlib
import base64
import urllib.parse

# MQTT broker 地址和端口
BROKER_ADDRESS = "nbzch.cn"
BROKER_PORT = 1883

# Topics
TOPIC_LAST_LOCATION = "gps/lastLocation"
TOPIC_LOCATION = "gps/location"

# 钉钉 Webhook 地址和加签密钥
DINGTALK_WEBHOOK = "https://oapi.dingtalk.com/robot/send?access_token=59ed4edaa505e693dc69823acc29ec68d0e93ac7af62d344b24fd5b094e7eb9f"
DINGTALK_SECRET = "SEC2d9e084665b753d4024d95428fbc440bd1c787b59d9acb99e716a8ce0e0e29ef"

# 用于暂存不同 "name" 的最后定位消息
location_cache = {}

# 钉钉 Webhook 发送消息的函数
def send_dingtalk_message(device_name, message):
    # 生成加签
    timestamp = str(round(time.time() * 1000))
    secret_enc = DINGTALK_SECRET.encode('utf-8')
    string_to_sign = '{}\n{}'.format(timestamp, DINGTALK_SECRET)
    string_to_sign_enc = string_to_sign.encode('utf-8')
    hmac_code = hmac.new(secret_enc, string_to_sign_enc, digestmod=hashlib.sha256).digest()
    sign = urllib.parse.quote_plus(base64.b64encode(hmac_code))

    # 完整的 Webhook URL
    webhook_url_with_sign = f"{DINGTALK_WEBHOOK}&timestamp={timestamp}&sign={sign}"

    # 消息内容
    dingtalk_message = {
        "msgtype": "text",
        "text": {
            "content": f"{device_name}{message}"
        }
    }

    # 发送 POST 请求
    headers = {'Content-Type': 'application/json'}
    response = requests.post(webhook_url_with_sign, headers=headers, data=json.dumps(dingtalk_message))

    # 打印响应结果
    print(f"DingTalk response: {response.status_code}, {response.json()}")

# MQTT 客户端连接成功后的回调
def on_connect(client, userdata, flags, rc):
    if rc == 0:
        print("Connected to broker")
        # 订阅需要的主题
        client.subscribe(TOPIC_LAST_LOCATION)
        client.subscribe(TOPIC_LOCATION)
    else:
        print(f"Failed to connect to broker, return code {rc}")

# 接收到消息时的回调
def on_message(client, userdata, msg):
    global location_cache

    try:
        payload = msg.payload.decode("utf-8")
        if msg.topic == TOPIC_LOCATION:
            # 收到 gps/location 的定位信息
            message = json.loads(payload)
            if "name" in message:
                name = message["name"]
                location_cache[name] = message  # 将该 name 的消息暂存
                print(f"Updated cache for name {name}: {message}")

        elif msg.topic == TOPIC_LAST_LOCATION:
            # 解析 JSON 消息
            message = json.loads(payload)
            if "name" in message and "msg" in message:
                device_name = message["name"]
                msg_content = message["msg"]

                # 如果是 'online' 消息
                if msg_content.lower() == "online":
                    print(f"Received online message from device {device_name}")
                    # 发送钉钉消息
                    send_dingtalk_message(device_name, "的定位器开机啦,打开 https://nbzch.cn:5084/gps 看看在哪里?")

                elif msg_content.lower() == "request":
                    print("Received lastLocation request")
                    for name, stored_message in location_cache.items():
                        # 将暂存消息逐个发布到 gps/location
                        client.publish(TOPIC_LOCATION, json.dumps(stored_message))
                        print(f"Published last cached location for {name}: {stored_message}")

    except json.JSONDecodeError:
        print(f"Invalid JSON received: {msg.payload}")
    except Exception as e:
        print(f"Error processing message: {e}")

# 创建 MQTT 客户端并配置回调
mqtt_client = mqtt.Client()
mqtt_client.on_connect = on_connect
mqtt_client.on_message = on_message

# 连接到 MQTT Broker
mqtt_client.connect(BROKER_ADDRESS, BROKER_PORT)

# 开始监听事件
try:
    print("Starting MQTT listener...")
    mqtt_client.loop_forever()
except KeyboardInterrupt:
    print("Stopping script...")
    mqtt_client.disconnect()

发表评论