- Batch device CRUD: POST /api/devices/batch (create 500), PUT /api/devices/batch (update 500), POST /api/devices/batch-delete (delete 100) with WHERE IN bulk queries - Batch command: POST /api/commands/batch with model_validator mutual exclusion - API key auth (X-API-Key header, secrets.compare_digest timing-safe) - Rate limiting via SlowAPIMiddleware (60/min default, 30/min writes) - Real client IP extraction (X-Forwarded-For / CF-Connecting-IP) - Global exception handler (no stack trace leaks, passes HTTPException through) - CORS with auto-disable credentials on wildcard origins - Schema validation: IMEI pattern, lat/lon ranges, Literal enums, MAC/UUID patterns - Heartbeats router, per-ID endpoints for locations/attendance/bluetooth - Input dedup in batch create, result ordering preserved - Baidu reverse geocoding, Gaode map tiles with WGS84→GCJ02 conversion - Device detail panel with feature toggles and command controls - Side panel for location/beacon pages with auto-select active device via [HAPI](https://hapi.run) Co-Authored-By: HAPI <noreply@hapi.run>
26 KiB
26 KiB
Badge Admin - KKS P240/P241 蓝牙工牌管理系统
项目概览
KKS P240/P241 蓝牙工牌管理后台,基于 FastAPI + SQLAlchemy + asyncio TCP 服务器。 支持设备管理、实时定位、告警、考勤打卡、蓝牙记录、指令下发、TTS语音播报等功能。
项目结构
/home/gpsystem/
├── run.py # 启动脚本 (uvicorn)
├── .env.example # 环境变量模板 (复制为 .env 使用)
├── requirements.txt # Python 依赖
├── frpc.toml # FRP 客户端配置 (TCP隧道)
├── badge_admin.db # SQLite 数据库
├── server.log # 服务器日志
│
├── app/
│ ├── main.py # FastAPI 应用入口, 挂载静态文件, 启动TCP服务器
│ ├── config.py # 配置 (pydantic-settings, .env支持, 端口/API密钥/缓存/限流)
│ ├── database.py # SQLAlchemy async 数据库连接
│ ├── dependencies.py # FastAPI 依赖 (API Key 认证)
│ ├── extensions.py # 共享实例 (rate limiter, 真实IP提取)
│ ├── models.py # ORM 模型 (Device, LocationRecord, AlarmRecord, HeartbeatRecord, AttendanceRecord, BluetoothRecord, BeaconConfig, CommandLog)
│ ├── schemas.py # Pydantic 请求/响应模型
│ ├── tcp_server.py # TCP 服务器核心 (~2400行), 管理设备连接、协议处理、数据持久化
│ ├── geocoding.py # 地理编码服务 (基站/WiFi → 经纬度) + 逆地理编码 (经纬度 → 地址)
│ │
│ ├── protocol/
│ │ ├── constants.py # 协议常量 (协议号、告警类型snake_case、信号等级等)
│ │ ├── parser.py # 二进制协议解析器
│ │ ├── builder.py # 响应包构建器
│ │ └── crc.py # CRC-ITU (CRC-16/X-25, 多项式 0x8408)
│ │
│ ├── services/
│ │ ├── device_service.py # 设备 CRUD
│ │ ├── command_service.py # 指令日志 CRUD
│ │ ├── location_service.py # 位置记录查询
│ │ ├── beacon_service.py # 蓝牙信标 CRUD
│ │ └── tcp_command_service.py # TCP指令抽象层 (解耦routers↔tcp_server)
│ │
│ ├── routers/
│ │ ├── devices.py # /api/devices (含 /stats, /batch, /batch-delete)
│ │ ├── commands.py # /api/commands (含 /send, /message, /tts, /batch)
│ │ ├── locations.py # /api/locations (含 /latest, /track, /{id})
│ │ ├── alarms.py # /api/alarms (含 acknowledge)
│ │ ├── attendance.py # /api/attendance (含 /stats, /{id})
│ │ ├── bluetooth.py # /api/bluetooth (含 /{id})
│ │ ├── heartbeats.py # /api/heartbeats (心跳记录查询)
│ │ └── beacons.py # /api/beacons (信标管理 CRUD)
│ │
│ └── static/
│ └── admin.html # 管理后台 SPA (暗色主题, 8个页面)
│
└── doc/
└── KKS_Protocol_P240_P241.md # 协议文档 (从PDF转换)
架构设计
网络拓扑
[P241 工牌] --TCP--> [152.69.205.186:5001 (frps)]
|
FRP Tunnel
|
[Container:5000 (frpc)]
|
[TCP Server (asyncio)]
|
[FastAPI :8088] --CF Tunnel--> [Web 访问]
端口说明
- 8088: FastAPI HTTP API + 管理后台 UI
- 5000: TCP 服务器 (KKS 二进制协议)
- 5001: 远程服务器 FRP 映射端口 (152.69.205.186)
- 7000: FRP 服务端管理端口
- 7500: FRP Dashboard (admin/PassWord0325)
配置管理
app/config.py使用 pydantic-settings (BaseSettings),支持.env文件覆盖默认值.env.example提供所有可配置项模板,复制为.env使用- DATABASE_URL 使用绝对路径 (基于
__file__计算项目根目录) - 所有 API 密钥 (天地图/Google/Unwired/高德) 集中在
config.py,geocoding.py从settings导入 - 端口号有 pydantic 校验 (ge=1, le=65535)
启动服务
# 启动服务
cd /home/gpsystem
nohup python3 -m uvicorn app.main:app --host 0.0.0.0 --port 8088 > server.log 2>&1 &
# 启动 FRP 客户端 (TCP隧道)
nohup /tmp/frpc -c /home/gpsystem/frpc.toml > /tmp/frpc.log 2>&1 &
# 检查服务状态
curl http://localhost:8088/health
curl http://localhost:8088/api/devices
管理后台 UI
访问 /admin 路径,包含以下页面:
- 仪表盘 - 设备统计、告警概览、在线状态
- 设备管理 - 添加/编辑/删除设备
- 位置追踪 - Leaflet 地图、轨迹回放、地址显示
- 告警管理 - 告警列表、确认处理、位置/地址显示
- 考勤记录 - 上下班打卡记录 (0xB0/0xB1 GPS+LBS+WiFi)
- 蓝牙记录 - 蓝牙打卡(0xB2)/定位(0xB3)记录,含信标MAC/UUID/RSSI等
- 信标管理 - 蓝牙信标注册、位置配置(MAC/UUID/楼层/区域/经纬度)
- 指令管理 - 发送指令/留言/TTS语音
协议详情
KKS 二进制协议,详见 doc/KKS_Protocol_P240_P241.md
已支持协议号
| 协议号 | 名称 | 方向 |
|---|---|---|
| 0x01 | Login 登录 | 终端→服务器 |
| 0x13 | Heartbeat 心跳 | 双向 |
| 0x1F | Time Sync 时间同步 | 双向 |
| 0x22 | GPS 定位 | 终端→服务器 |
| 0x26 | Alarm ACK 告警确认 | 服务器→终端 |
| 0x28 | LBS 多基站 | 终端→服务器 |
| 0x2C | WiFi 定位 | 终端→服务器 |
| 0x36 | Heartbeat Extended 扩展心跳 | 双向 |
| 0x80 | Online Command 在线指令 | 服务器→终端 |
| 0x81 | Online Command Reply | 终端→服务器 |
| 0x82 | Message 留言 | 服务器→终端 |
| 0x94 | General Info (ICCID/IMSI等) | 双向 |
| 0xA0 | 4G GPS 定位 | 终端→服务器 |
| 0xA1 | 4G LBS 多基站 | 终端→服务器 |
| 0xA2 | 4G WiFi 定位 | 终端→服务器 |
| 0xA3-0xA5 | 4G 告警 (围栏/LBS) | 终端→服务器 |
| 0xA9 | WiFi 告警 | 终端→服务器 |
| 0xB0-0xB1 | 考勤打卡 | 双向 |
| 0xB2 | 蓝牙打卡 | 双向 |
| 0xB3 | 蓝牙定位 | 终端→服务器 |
数据包格式
[Start 0x7878/0x7979] [Length 1-2B] [Protocol 1B] [Content NB] [Serial 2B] [CRC 2B] [Stop 0x0D0A]
当前设备
| 字段 | 值 |
|---|---|
| IMEI | 868120334031363 |
| 型号 | P241 |
| 名称 | 测试 |
| DB ID | 1 |
| ICCID | 89860848102570005286 |
| IMSI | 460240388355286 |
| SIM卡 | 中国移动 IoT (MCC=460, MNC=0) |
| 位置 | 成都地区 (~30.605°N, 103.936°E) |
重要实现细节
IMEI 解析
- BCD 编码: 8字节 = 16 hex digits, 首位为填充 0
- 使用
raw_hex[1:]只移除首位填充0 (不用 lstrip 避免移除多个)
设备登录
- 登录时不覆盖用户设置的 device_type (避免被覆盖为 raw hex)
- 重连时先关闭旧连接再建立新连接
- 断开连接时检查 writer 是否匹配,避免误将新连接标记为 offline
告警处理
- 告警响应使用 0x26 ACK (非原始协议号回复)
- 告警类型常量使用 snake_case (如
sos,vibration,enter_fence) - 4G 告警包解析:
- 0xA3/0xA4 (围栏告警): datetime(6) + gps(12) + lbs_length(1) + MCC/MNC + LAC(4) + CellID(8) + terminal_info(1) + voltage_level(1字节) + gsm_signal(1) + alarm_code(1) + language(1)
- 0xA5 (LBS告警): 无datetime, 无GPS — 直接从 MCC 开始
- 0xA9 (WiFi告警): datetime(6) + MCC/MNC + cell_type(1) + cell_count(1) + 基站列表 + timing_advance(1) + WiFi列表 + alarm_code(1) + language(1)
- MCC 高位
0x8000标志: 表示 MNC 为 2 字节 (非默认1字节) - voltage_level 是 1字节 (0x00-0x06 等级),不是2字节毫伏值
- LBS/WiFi 报警自动执行前向地理编码(基站→经纬度)+ 逆地理编码(经纬度→地址)
位置数据与地理编码
- GPS 定位 (0x22/0xA0): 直接包含经纬度坐标,精度最高 (~10m)
- LBS 基站定位 (0x28/0xA1): 包含 MCC/MNC/LAC/CellID,需要地理编码转换为经纬度
- WiFi 定位 (0x2C/0xA2): 包含基站数据 + WiFi AP MAC地址列表,需要地理编码
- 前向地理编码 (
geocode_location): 基站/WiFi → 经纬度- 高德智能硬件定位 (待接入):
apilocate.amap.com/position,需企业认证,WiFi+基站混合定位精度 ~30m - Mylnikov.org (当前使用): 免费无需Key,中国基站精度较差 (~16km)
- Google Geolocation API / Unwired Labs API (备选,需配置Key)
- 高德智能硬件定位 (待接入):
- 逆地理编码 (
reverse_geocode): 经纬度 → 中文地址- 天地图 (已接入): 免费1万次/天,WGS84原生坐标系,无需坐标转换
- 缓存策略: 坐标四舍五入到3位小数 (~100m) 作为缓存key
- 地址格式:
省市区街道路门牌号 (附近POI)
- 内置缓存机制,避免重复请求相同基站/坐标
天地图 API
- 服务端 Key:
439fca3920a6f31584014424f89c3ecc(用于逆地理编码) - 浏览器端 Key:
1918548e81a5ae3ff0cb985537341146(用于前端地图瓦片,暂未使用) - API地址:
http://api.tianditu.gov.cn/geocoder?postStr={JSON}&type=geocode&tk={KEY} - 坐标系: WGS84 (与GPS/Leaflet一致,无需转换)
- 配额: 免费 10,000 次/天
- 注意: postStr 参数需使用双引号JSON并URL编码
API 认证与限流
- 认证: 设置
API_KEY环境变量后,所有/api/请求需携带X-API-Key请求头 - 限流: 全局 60/min (default_limits),写操作 30/min (
@limiter.limit) - 真实 IP: 从
X-Forwarded-For→CF-Connecting-IP→request.client.host提取 - CORS:
CORS_ORIGINS=*时自动禁用allow_credentials
批量操作 API
POST /api/devices/batch— 批量创建 (最多500),输入去重 + DB去重,结果按输入顺序PUT /api/devices/batch— 批量更新,单次 WHERE IN 查询 + 单次 flushPOST /api/devices/batch-delete— 批量删除 (最多100),通过 body 传 device_idsPOST /api/commands/batch— 批量发送指令 (最多100),model_validator互斥校验- 所有批量操作使用 WHERE IN 批量查询,避免 N+1
API 分页
- page_size 最大限制为 100 (schema 层校验)
- 前端设备选择器使用 page_size=100 (不能超过限制)
CRC 算法
- CRC-ITU / CRC-16/X-25
- Reflected polynomial: 0x8408
- Initial value: 0xFFFF
- Final XOR: 0xFFFF
TCP 指令下发
- 0x80 (Online Command): ASCII 指令 + 2字节语言字段 (
0x0001=中文) - 0x81 (Command Reply): 设备回复,格式 length(1) + server_flag(4) + content(n) + language(2)
- 0x82 (Message): UTF-16 BE 编码的文本消息 (非UTF-8)
- TTS: 通过 0x80 发送
TTS,<文本>格式 - 常用指令:
GPSON#开启GPS,BTON#开启蓝牙,BTSCAN,1#开启BLE扫描 - 已验证可用指令:
BTON#,BTSCAN,1#,GPSON#,MODE,1/3#,PARAM#,CHECK#,VERSION#,TIMER#,WHERE#,STATUS#,RESET# - 架构:
tcp_command_service.py作为抽象层解耦 routers↔tcp_server 的循环导入,通过 lazy import 访问 tcp_manager - 重要: TCP 层 (
send_command/send_message) 只负责发送,不创建 CommandLog。CommandLog 由 API 层 (commands.py) 管理 - 重要: 服务器启动时自动将所有设备标记为 offline,等待设备重新登录
蓝牙信标管理
- BeaconConfig 表: 注册蓝牙信标,配置 MAC/UUID/Major/Minor/楼层/区域/经纬度/地址
- 自动关联: 0xB2 打卡和 0xB3 定位时,根据 beacon_mac 查询 beacon_configs 表
- 位置写入: 将已注册信标的经纬度写入 BluetoothRecord 的 latitude/longitude
- 多信标定位: 0xB3 多信标场景,取 RSSI 信号最强的已注册信标位置
- 设备端配置: 需通过 0x80 指令发送
BTON#开启蓝牙、BTSCAN,1#开启扫描 - 已知有效指令:
BTON#(btON:1),BTSCAN,1#(btSCAN:1) — 设备确认设置成功 - 当前状态: ✅ 0xB2 蓝牙打卡已验证可用 (2026-03-18),设备成功检测信标并上报数据
- 已验证信标: MAC=
C3:00:00:34:43:5E, UUID=FDA50693-A4E2-4FB1-AFCF-C6EB07647825, Major=10001, Minor=19641 - 注意: 信标需配置经纬度/地址,否则打卡记录中位置为空
- 制造商: 几米物联 (Jimi IoT / jimiiot.com.cn),P240/P241 智能电子工牌系列
0x94 General Info 子协议
- 子协议 0x0A: IMEI(8字节) + IMSI(8字节) + ICCID(10字节)
- 子协议 0x09: GPS 卫星状态
- 子协议 0x00: ICCID(10字节)
前端字段映射 (admin.html)
- 设备信号:
d.gsm_signal(非d.signal_strength) - 指令响应:
c.response_content(非c.response) - 响应时间:
c.response_at || c.sent_at(非c.updated_at) - 位置地址:
l.address(天地图逆地理编码结果) - 卫星数:
l.gps_satellites(非l.accuracy) - 记录时间:
l.recorded_at(非l.timestamp) - 报警来源:
a.alarm_source(非a.source) - 报警信号:
a.gsm_signal(非a.signal_strength) - 报警类型: snake_case (如
vibration,power_cut,voice_alarm)
FRP 配置
# frpc.toml (容器端)
serverAddr = "152.69.205.186"
serverPort = 7000
auth.method = "token"
auth.token = "PassWord0325"
[[proxies]]
name = "badge-tcp"
type = "tcp"
localIP = "127.0.0.1"
localPort = 5000
remotePort = 5001
外部服务器
- IP: 152.69.205.186
- OS: Ubuntu 20.04, Python 3.11, 24GB RAM
- FRP Server: frps v0.62.1 (1panel Docker, network_mode: host)
- Config: /opt/1panel/apps/frps/frps/data/frps.toml
高德地图 API
已有 Key
- Web服务 Key:
a9f4e04f5c8e739e5efb07175333f30b - 安全密钥:
bfc4e002c49ab5f47df71e0aeaa086a5 - 账号类型: 个人认证开发者 (正在申请企业认证)
已验证可用的 API
- 反地理编码 (
restapi.amap.com/v3/geocode/regeo): 经纬度 → 地址文本 - 坐标转换 (
restapi.amap.com/v3/assistant/coordinate/convert): WGS-84 → GCJ-02
待企业认证后启用
- 智能硬件定位 (
apilocate.amap.com/position): WiFi+基站 → 经纬度 (需企业认证, 错误码 10012)- v1.0 GET:
apilocate.amap.com/position - v2.0 POST:
restapi.amap.com/v5/position/IoT - 参数格式:
bts=mcc,mnc,lac,cellid,signal/macs=mac,signal,ssid|mac,signal,ssid|
- v1.0 GET:
配额 (个人认证开发者)
- 基础LBS服务: 5,000 次/日 (反地理编码、坐标转换等)
- 在线定位: 50,000 次/日
接入步骤 (企业认证通过后)
- 在
app/geocoding.py中设置AMAP_KEY - 实现
_geocode_amap()函数调用智能硬件定位 API - 注意返回坐标为 GCJ-02,需转换为 WGS-84 用于 Leaflet 地图
- 高德数字签名: 参数按key排序拼接 + 安全密钥 → MD5 → sig 参数
百度地图 API
Key
- 服务端 AK:
nZ4AyCm7FTn85HbFuQjw0ItSYkgxEuhA
已接入服务
- ✅ 逆地理编码:
api.map.baidu.com/reverse_geocoding/v3— 经纬度 → 地址 (coordtype=wgs84ll, 无需坐标转换)- 优先级: 百度 > 天地图 (fallback)
- 配额: 5,000次/日 (个人开发者)
- 注意: 百度内部使用 BD-09 坐标系,但逆地理编码接口支持
coordtype=wgs84ll直接传入 WGS-84 坐标 - 百度无服务端基站/WiFi定位API,基站定位仍用 Mylnikov
已知限制
- IoT SIM 卡不支持 SMS - 144 号段的物联网卡无法收发短信,需通过平台或 TCP 连接配置设备
- Cloudflare Tunnel 仅代理 HTTP - TCP 流量必须通过 FRP 转发
- SQLite 单写 - 高并发场景需切换 PostgreSQL
- 设备最多 100 台列表 - 受 page_size 限制,超过需翻页查询
- 基站定位精度差 - 当前 Mylnikov API 中国基站精度 ~16km,待接入高德智能硬件定位后可达 ~30m
- 天地图逆地理编码使用 HTTP - API不支持HTTPS,Key在URL中明文传输 (低风险: 免费Key, 已降级为备选)
已修复的问题 (Bug Fix 记录)
数据链路修复
- IMEI 解析 - 修复 BCD 解码逻辑,确保正确提取 15 位 IMEI
- 设备类型覆盖 - 登录时不再覆盖用户设置的 device_type
- 设备重连 - 新连接建立前先关闭旧连接,断开时检查 writer 身份
- 告警类型提取 - 修正 alarm byte 位置,从 terminal_info+voltage+gsm_signal 之后读取
协议修复
- 0x80 指令包 - 添加缺失的 2 字节语言字段 (
0x0001) - 0x82 消息编码 - 从 UTF-8 改为 UTF-16 BE (协议要求)
- 0x94 通用信息 - 完善子协议 0x0A (IMEI+IMSI+ICCID) 和 0x09 (GPS卫星状态) 解析
前端修复
- 设备选择器为空 -
page_size=500超过 API 最大限制 100,返回 422 错误 - 位置页面崩溃 - API 返回
data: null时未做空值检查 - 轨迹查询失败 - 缺少必填的 start_time/end_time 参数
- 字段名不匹配 - 修复 signal_strength→gsm_signal, response→response_content, source→alarm_source 等
定位功能修复
- WiFi/LBS 无坐标 - 添加 wifi/wifi_4g 解析分支 (原代码缺失)
- 地理编码集成 - 集成 Mylnikov.org API,LBS/WiFi 定位数据自动转换为经纬度坐标
- 邻近基站和WiFi数据 - 存储到 LocationRecord 的 neighbor_cells 和 wifi_data 字段
告警功能修复
- 告警响应协议 - 改为使用 0x26 ACK 响应 (原来错误使用地址回复)
- voltage_level 解析 - 从2字节改为1字节 (0x00-0x06等级)
- 0xA5 LBS告警格式 - 移除错误的 datetime/lbs_length 前缀,直接从 MCC 开始
- 0xA9 WiFi告警 - 独立解析器 (非GPS格式),支持 cell_type/cell_count/WiFi AP 列表
- MNC 2字节标志 -
_parse_mcc_mnc_from_content正确处理 MCC 高位 0x8000 标志 - 告警类型常量 - 改为 snake_case,新增 voice_alarm(0x16), fake_base_station(0x17), cover_open(0x18), internal_low_battery(0x19)
- LBS/WiFi 报警定位 - 为无GPS的报警添加前向地理编码
逆地理编码
- 天地图集成 - 位置和报警记录自动获取中文地址 (天地图逆地理编码)
- 地址字段 - LocationRecord 新增 address 字段,前端位置表/报警表/地图弹窗显示地址
蓝牙与考勤功能
- 0xB0/0xB1 考勤解析 - 完整解析 GPS+LBS+WiFi 考勤包,含基站邻区、WiFi AP、前向+逆地理编码
- 0xB2 蓝牙打卡解析 - 解析 iBeacon 数据 (RSSI/MAC/UUID/Major/Minor/电池/打卡类型)
- 0xB3 蓝牙定位解析 - 解析多信标数据,每信标 30 字节,支持电压(V)/百分比(%)电池单位
- 考勤类型检测 - 从 terminal_info 字节 bits[5:2] 判断 clock_in(0001)/clock_out(0010)
- 信标管理系统 - BeaconConfig CRUD,注册信标物理位置,TCP 自动关联信标经纬度
指令发送修复
- server_flag_str 未定义 - send_command 中记录日志时变量未定义导致 NameError
- TCP 层重复创建日志 - 简化 send_command/send_message 只负责发送,CommandLog 由 API 层管理
- IMEI lstrip 问题 -
lstrip("0")可能删除多个前导零,改为raw_hex[1:] - 设备启动状态 - 服务器启动时重置所有设备为 offline,避免显示在线但实际未连接
- 0x81 回复解析 - 去除末尾 2 字节语言字段,正确提取回复文本
协议全面审计修复 (2026-03-16)
- 0x1F 时间同步格式 - 从6字节(YY MM DD HH MM SS)改为4字节Unix时间戳+2字节语言字段
- 地址回复完整格式 - 重写
_send_address_reply,实现完整格式: server_flag(4)+ADDRESS/ALARMSMS(7-8)+&&+UTF16BE地址+&&+电话(21)+## - 报警地址回复 - 报警包在发送0x26 ACK后,额外发送0x17地址回复(含ALARMSMS标记)
- 0x82 留言格式 - 添加 server_flag(4字节) + language(2字节) 字段
- 0x22 GPS Cell ID - 从2字节改为3字节解析 (2G基站CellID为3字节)
- 登录包时区/语言 - 解析 content[10:12] 提取时区(bit15-04)和语言(bit00),存入Device
- 0x94 不需回复 - 移除服务器响应,从 PROTOCOLS_REQUIRING_RESPONSE 中删除
- WiFi缓存+LRU淘汰 - 新增 LRUCache(OrderedDict, maxsize=10000),替换所有dict缓存,修复WiFi缓存未写入
- 前端4G筛选 - 位置类型筛选添加 gps_4g/wifi_4g/lbs_4g 选项
- DATA_REPORT_MODES - 修正所有模式名称匹配协议文档
架构优化 (2026-03-17)
- 配置集中化 - API密钥从 geocoding.py 硬编码移至 config.py (pydantic-settings),支持 .env 覆盖
- 数据库绝对路径 - DATABASE_URL 从相对路径改为基于
__file__的绝对路径,避免 CWD 依赖 - tcp_command_service 抽象层 - 新建 services/tcp_command_service.py,通过 lazy import 解耦 routers↔tcp_server 循环依赖
- commands.py 去重 - send_command/send_message/send_tts 提取
_send_to_device()通用函数 - 协议常量扩展 - constants.py 新增 DEFAULT_DEVICE_TYPE, SERVER_FLAG_BYTES, ATTENDANCE_TYPES, COURSE_BIT_*, MCC_MNC2_FLAG, VOLTAGE_LEVELS
- 前端侧边面板 - 位置追踪/信标管理页面添加左侧设备/信标列表面板,自动选中最近活跃设备
- 前端面板解耦 - 提取 PANEL_IDS 配置 + _initPanelRender 通用函数,toggleSidePanel 仅在 locations 页调用 invalidateSize
百度地图接入 & 连接修复 (2026-03-19)
- 百度逆地理编码 - 接入百度地图 reverse_geocoding/v3 API (coordtype=wgs84ll),优先于天地图
- PROTO_ADDRESS_REPLY_EN 未导入 - 0xA5 报警地址回复时 NameError,补充 import
- 心跳自动恢复 online - 心跳处理时自动将设备 status 设为 online + 重新注册 connections
- 高德地图瓦片 - 替换天地图瓦片为高德 (GCJ-02, 标准Mercator),添加 WGS84→GCJ02 坐标转换,可切换 provider (
MAP_PROVIDER变量)
API 安全加固 & 批量管理 (2026-03-20)
- API Key 认证 -
dependencies.py实现 X-API-Key 头认证,secrets.compare_digest防时序攻击 - CORS + 限流 -
SlowAPIMiddleware全局限流 (60/min),写操作独立限速 (30/min) - 限流器真实 IP -
extensions.py从X-Forwarded-For/CF-Connecting-IP提取真实客户端 IP - 全局异常处理 - 拦截未处理异常返回 500,不泄露堆栈;放行 HTTPException/ValidationError
- Schema 校验加强 - IMEI pattern、经纬度范围、Literal 枚举、command max_length、BeaconConfig MAC/UUID pattern
- Health 端点增强 -
/health检测数据库连通性 + TCP 连接设备数 - 批量设备创建 -
POST /api/devices/batch(最多500台),WHERE IN 单次查询去重,输入列表内 IMEI 去重 - 批量设备更新 -
PUT /api/devices/batch,单次查询 + 批量更新 + 单次 flush - 批量设备删除 -
POST /api/devices/batch-delete,通过 body 传递避免 URL 长度限制 - 批量指令发送 -
POST /api/commands/batch(最多100台),model_validator互斥校验 device_ids/imeis - Heartbeats 路由 - 新增
GET /api/heartbeats心跳记录查询 + 按 ID 获取 - 按 ID 查询端点 - locations/{id}, attendance/{id}, bluetooth/{id} 放在路由末尾避免冲突
- Beacons double-commit 修复 - 移除 router 层多余的 flush/refresh,依赖 service 层
0x94 子协议 0x04
- 设备配置上报:
ALM2=40;ALM4=E0;MODE=03;IMSI=460240388355286 - 在设备重连/重启后上报
待完成功能
- ⭐ 接入高德智能硬件定位 - 企业认证通过后,替换 Mylnikov,大幅提升 WiFi/基站定位精度
地图瓦片- ✅ 已切换为高德瓦片 (GCJ-02),支持 MAP_PROVIDER 切换 ('gaode'|'tianditu')- 心跳扩展模块解析 - 计步器、外部电压等模块未解析
蓝牙信标调试- ✅ 已完成 (2026-03-18),0xB2打卡数据正常上报,信标匹配成功
调试技巧
# 查看实时日志
tail -f /home/gpsystem/server.log | grep -aE "TCP|login|heartbeat|error|geocod|Tianditu" --line-buffered
# 检查数据库
python3 -c "
import sqlite3
conn = sqlite3.connect('/home/gpsystem/badge_admin.db')
cur = conn.cursor()
cur.execute('SELECT id, imei, device_type, status, battery_level FROM devices')
for row in cur.fetchall(): print(row)
cur.execute('SELECT id, location_type, latitude, longitude, address, recorded_at FROM location_records ORDER BY id DESC LIMIT 5')
for row in cur.fetchall(): print(row)
"
# 测试 API
curl -s http://localhost:8088/api/devices | python3 -m json.tool
curl -s http://localhost:8088/api/locations/latest/1 | python3 -m json.tool
curl -s http://localhost:8088/health
# 发送指令
curl -s -X POST http://localhost:8088/api/commands/send \
-H "Content-Type: application/json" \
-d '{"device_id":1,"command_type":"online_cmd","command_content":"GPSON#"}' | python3 -m json.tool
# 开启蓝牙扫描
curl -s -X POST http://localhost:8088/api/commands/send \
-H "Content-Type: application/json" \
-d '{"device_id":1,"command_type":"online_cmd","command_content":"BTON#"}' | python3 -m json.tool
# 管理信标
curl -s http://localhost:8088/api/beacons | python3 -m json.tool
curl -s -X POST http://localhost:8088/api/beacons \
-H "Content-Type: application/json" \
-d '{"beacon_mac":"AA:BB:CC:DD:EE:FF","name":"前台","floor":"1F","latitude":30.27,"longitude":120.15}' | python3 -m json.tool
# 检查 FRP 连接
ps aux | grep frpc
# FRP Dashboard: http://152.69.205.186:7500 (admin/PassWord0325)
# 检查端口
python3 -c "
with open('/proc/net/tcp') as f:
for line in f:
parts = line.split()
if len(parts)>=2 and ':' in parts[1]:
port = int(parts[1].split(':')[1], 16)
if port in (8088, 5000): print(f'Port {port}: listening')
"
# 测试地理编码
python3 -c "
import asyncio
from app.geocoding import geocode_location, reverse_geocode
async def test():
lat, lon = await geocode_location(mcc=460, mnc=0, lac=32775, cell_id=205098688)
print(f'lat={lat}, lon={lon}')
if lat: print(await reverse_geocode(lat, lon))
asyncio.run(test())
"