438 lines
20 KiB
Markdown
438 lines
20 KiB
Markdown
|
|
# Badge Admin - KKS P240/P241 蓝牙工牌管理系统
|
|||
|
|
|
|||
|
|
## 项目概览
|
|||
|
|
|
|||
|
|
KKS P240/P241 蓝牙工牌管理后台,基于 FastAPI + SQLAlchemy + asyncio TCP 服务器。
|
|||
|
|
支持设备管理、实时定位、告警、考勤打卡、蓝牙记录、指令下发、TTS语音播报等功能。
|
|||
|
|
|
|||
|
|
## 项目结构
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
/tmp/badge-admin/
|
|||
|
|
├── run.py # 启动脚本 (uvicorn)
|
|||
|
|
├── requirements.txt # Python 依赖
|
|||
|
|
├── frpc.toml # FRP 客户端配置 (TCP隧道)
|
|||
|
|
├── badge_admin.db # SQLite 数据库
|
|||
|
|
├── server.log # 服务器日志
|
|||
|
|
│
|
|||
|
|
├── app/
|
|||
|
|
│ ├── main.py # FastAPI 应用入口, 挂载静态文件, 启动TCP服务器
|
|||
|
|
│ ├── config.py # 配置 (端口8088 HTTP, 5000 TCP)
|
|||
|
|
│ ├── database.py # SQLAlchemy async 数据库连接
|
|||
|
|
│ ├── 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
|
|||
|
|
│ │
|
|||
|
|
│ ├── routers/
|
|||
|
|
│ │ ├── devices.py # /api/devices (含 /stats 统计接口)
|
|||
|
|
│ │ ├── commands.py # /api/commands (含 /send, /message, /tts)
|
|||
|
|
│ │ ├── locations.py # /api/locations (含 /latest, /track)
|
|||
|
|
│ │ ├── alarms.py # /api/alarms (含 acknowledge)
|
|||
|
|
│ │ ├── attendance.py # /api/attendance
|
|||
|
|
│ │ ├── bluetooth.py # /api/bluetooth
|
|||
|
|
│ │ └── 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)
|
|||
|
|
|
|||
|
|
## 启动服务
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
# 启动服务
|
|||
|
|
cd /tmp/badge-admin
|
|||
|
|
nohup python3 -m uvicorn app.main:app --host 0.0.0.0 --port 8088 > server.log 2>&1 &
|
|||
|
|
|
|||
|
|
# 启动 FRP 客户端 (TCP隧道)
|
|||
|
|
nohup /tmp/frpc -c /tmp/badge-admin/frpc.toml > /tmp/frpc.log 2>&1 &
|
|||
|
|
|
|||
|
|
# 检查服务状态
|
|||
|
|
curl http://localhost:8088/health
|
|||
|
|
curl http://localhost:8088/api/devices
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 管理后台 UI
|
|||
|
|
|
|||
|
|
访问 `/admin` 路径,包含以下页面:
|
|||
|
|
1. **仪表盘** - 设备统计、告警概览、在线状态
|
|||
|
|
2. **设备管理** - 添加/编辑/删除设备
|
|||
|
|
3. **位置追踪** - Leaflet 地图、轨迹回放、地址显示
|
|||
|
|
4. **告警管理** - 告警列表、确认处理、位置/地址显示
|
|||
|
|
5. **考勤记录** - 上下班打卡记录 (0xB0/0xB1 GPS+LBS+WiFi)
|
|||
|
|
6. **蓝牙记录** - 蓝牙打卡(0xB2)/定位(0xB3)记录,含信标MAC/UUID/RSSI等
|
|||
|
|
7. **信标管理** - 蓝牙信标注册、位置配置(MAC/UUID/楼层/区域/经纬度)
|
|||
|
|
8. **指令管理** - 发送指令/留言/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 分页
|
|||
|
|
- 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 层 (`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) — 设备确认设置成功
|
|||
|
|
- **当前状态**: 设备接受BT指令但尚未上报0xB2/0xB3数据,可能需要信标UUID匹配或通过Tracksolid平台配置
|
|||
|
|
- **制造商**: 几米物联 (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 配置
|
|||
|
|
|
|||
|
|
```toml
|
|||
|
|
# 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|`
|
|||
|
|
|
|||
|
|
### 配额 (个人认证开发者)
|
|||
|
|
- 基础LBS服务: 5,000 次/日 (反地理编码、坐标转换等)
|
|||
|
|
- 在线定位: 50,000 次/日
|
|||
|
|
|
|||
|
|
### 接入步骤 (企业认证通过后)
|
|||
|
|
1. 在 `app/geocoding.py` 中设置 `AMAP_KEY`
|
|||
|
|
2. 实现 `_geocode_amap()` 函数调用智能硬件定位 API
|
|||
|
|
3. 注意返回坐标为 GCJ-02,需转换为 WGS-84 用于 Leaflet 地图
|
|||
|
|
4. 高德数字签名: 参数按key排序拼接 + 安全密钥 → MD5 → sig 参数
|
|||
|
|
|
|||
|
|
## 已知限制
|
|||
|
|
|
|||
|
|
1. **IoT SIM 卡不支持 SMS** - 144 号段的物联网卡无法收发短信,需通过平台或 TCP 连接配置设备
|
|||
|
|
2. **Cloudflare Tunnel 仅代理 HTTP** - TCP 流量必须通过 FRP 转发
|
|||
|
|
3. **SQLite 单写** - 高并发场景需切换 PostgreSQL
|
|||
|
|
4. **设备最多 100 台列表** - 受 page_size 限制,超过需翻页查询
|
|||
|
|
5. **基站定位精度差** - 当前 Mylnikov API 中国基站精度 ~16km,待接入高德智能硬件定位后可达 ~30m
|
|||
|
|
6. **天地图逆地理编码使用 HTTP** - API不支持HTTPS,Key在URL中明文传输 (低风险: 免费Key)
|
|||
|
|
|
|||
|
|
## 已修复的问题 (Bug Fix 记录)
|
|||
|
|
|
|||
|
|
### 数据链路修复
|
|||
|
|
1. **IMEI 解析** - 修复 BCD 解码逻辑,确保正确提取 15 位 IMEI
|
|||
|
|
2. **设备类型覆盖** - 登录时不再覆盖用户设置的 device_type
|
|||
|
|
3. **设备重连** - 新连接建立前先关闭旧连接,断开时检查 writer 身份
|
|||
|
|
4. **告警类型提取** - 修正 alarm byte 位置,从 terminal_info+voltage+gsm_signal 之后读取
|
|||
|
|
|
|||
|
|
### 协议修复
|
|||
|
|
5. **0x80 指令包** - 添加缺失的 2 字节语言字段 (`0x0001`)
|
|||
|
|
6. **0x82 消息编码** - 从 UTF-8 改为 UTF-16 BE (协议要求)
|
|||
|
|
7. **0x94 通用信息** - 完善子协议 0x0A (IMEI+IMSI+ICCID) 和 0x09 (GPS卫星状态) 解析
|
|||
|
|
|
|||
|
|
### 前端修复
|
|||
|
|
8. **设备选择器为空** - `page_size=500` 超过 API 最大限制 100,返回 422 错误
|
|||
|
|
9. **位置页面崩溃** - API 返回 `data: null` 时未做空值检查
|
|||
|
|
10. **轨迹查询失败** - 缺少必填的 start_time/end_time 参数
|
|||
|
|
11. **字段名不匹配** - 修复 signal_strength→gsm_signal, response→response_content, source→alarm_source 等
|
|||
|
|
|
|||
|
|
### 定位功能修复
|
|||
|
|
12. **WiFi/LBS 无坐标** - 添加 wifi/wifi_4g 解析分支 (原代码缺失)
|
|||
|
|
13. **地理编码集成** - 集成 Mylnikov.org API,LBS/WiFi 定位数据自动转换为经纬度坐标
|
|||
|
|
14. **邻近基站和WiFi数据** - 存储到 LocationRecord 的 neighbor_cells 和 wifi_data 字段
|
|||
|
|
|
|||
|
|
### 告警功能修复
|
|||
|
|
15. **告警响应协议** - 改为使用 0x26 ACK 响应 (原来错误使用地址回复)
|
|||
|
|
16. **voltage_level 解析** - 从2字节改为1字节 (0x00-0x06等级)
|
|||
|
|
17. **0xA5 LBS告警格式** - 移除错误的 datetime/lbs_length 前缀,直接从 MCC 开始
|
|||
|
|
18. **0xA9 WiFi告警** - 独立解析器 (非GPS格式),支持 cell_type/cell_count/WiFi AP 列表
|
|||
|
|
19. **MNC 2字节标志** - `_parse_mcc_mnc_from_content` 正确处理 MCC 高位 0x8000 标志
|
|||
|
|
20. **告警类型常量** - 改为 snake_case,新增 voice_alarm(0x16), fake_base_station(0x17), cover_open(0x18), internal_low_battery(0x19)
|
|||
|
|
21. **LBS/WiFi 报警定位** - 为无GPS的报警添加前向地理编码
|
|||
|
|
|
|||
|
|
### 逆地理编码
|
|||
|
|
22. **天地图集成** - 位置和报警记录自动获取中文地址 (天地图逆地理编码)
|
|||
|
|
23. **地址字段** - LocationRecord 新增 address 字段,前端位置表/报警表/地图弹窗显示地址
|
|||
|
|
|
|||
|
|
### 蓝牙与考勤功能
|
|||
|
|
24. **0xB0/0xB1 考勤解析** - 完整解析 GPS+LBS+WiFi 考勤包,含基站邻区、WiFi AP、前向+逆地理编码
|
|||
|
|
25. **0xB2 蓝牙打卡解析** - 解析 iBeacon 数据 (RSSI/MAC/UUID/Major/Minor/电池/打卡类型)
|
|||
|
|
26. **0xB3 蓝牙定位解析** - 解析多信标数据,每信标 30 字节,支持电压(V)/百分比(%)电池单位
|
|||
|
|
27. **考勤类型检测** - 从 terminal_info 字节 bits[5:2] 判断 clock_in(0001)/clock_out(0010)
|
|||
|
|
28. **信标管理系统** - BeaconConfig CRUD,注册信标物理位置,TCP 自动关联信标经纬度
|
|||
|
|
|
|||
|
|
### 指令发送修复
|
|||
|
|
29. **server_flag_str 未定义** - send_command 中记录日志时变量未定义导致 NameError
|
|||
|
|
30. **TCP 层重复创建日志** - 简化 send_command/send_message 只负责发送,CommandLog 由 API 层管理
|
|||
|
|
31. **IMEI lstrip 问题** - `lstrip("0")` 可能删除多个前导零,改为 `raw_hex[1:]`
|
|||
|
|
32. **设备启动状态** - 服务器启动时重置所有设备为 offline,避免显示在线但实际未连接
|
|||
|
|
33. **0x81 回复解析** - 去除末尾 2 字节语言字段,正确提取回复文本
|
|||
|
|
|
|||
|
|
### 协议全面审计修复 (2026-03-16)
|
|||
|
|
34. **0x1F 时间同步格式** - 从6字节(YY MM DD HH MM SS)改为4字节Unix时间戳+2字节语言字段
|
|||
|
|
35. **地址回复完整格式** - 重写 `_send_address_reply`,实现完整格式: server_flag(4)+ADDRESS/ALARMSMS(7-8)+&&+UTF16BE地址+&&+电话(21)+##
|
|||
|
|
36. **报警地址回复** - 报警包在发送0x26 ACK后,额外发送0x17地址回复(含ALARMSMS标记)
|
|||
|
|
37. **0x82 留言格式** - 添加 server_flag(4字节) + language(2字节) 字段
|
|||
|
|
38. **0x22 GPS Cell ID** - 从2字节改为3字节解析 (2G基站CellID为3字节)
|
|||
|
|
39. **登录包时区/语言** - 解析 content[10:12] 提取时区(bit15-04)和语言(bit00),存入Device
|
|||
|
|
40. **0x94 不需回复** - 移除服务器响应,从 PROTOCOLS_REQUIRING_RESPONSE 中删除
|
|||
|
|
41. **WiFi缓存+LRU淘汰** - 新增 LRUCache(OrderedDict, maxsize=10000),替换所有dict缓存,修复WiFi缓存未写入
|
|||
|
|
42. **前端4G筛选** - 位置类型筛选添加 gps_4g/wifi_4g/lbs_4g 选项
|
|||
|
|
43. **DATA_REPORT_MODES** - 修正所有模式名称匹配协议文档
|
|||
|
|
|
|||
|
|
### 0x94 子协议 0x04
|
|||
|
|
- 设备配置上报: `ALM2=40;ALM4=E0;MODE=03;IMSI=460240388355286`
|
|||
|
|
- 在设备重连/重启后上报
|
|||
|
|
|
|||
|
|
## 待完成功能
|
|||
|
|
|
|||
|
|
1. **⭐ 接入高德智能硬件定位** - 企业认证通过后,替换 Mylnikov,大幅提升 WiFi/基站定位精度
|
|||
|
|
2. **天地图瓦片底图** - 使用浏览器端 Key 替换 OpenStreetMap 瓦片 (中国地区显示更准确)
|
|||
|
|
3. **心跳扩展模块解析** - 计步器、外部电压等模块未解析
|
|||
|
|
4. **蓝牙信标调试** - P241 接受 BTON/BTSCAN 但未上报BLE数据,需确认信标iBeacon广播格式及UUID匹配
|
|||
|
|
|
|||
|
|
## 调试技巧
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
# 查看实时日志
|
|||
|
|
tail -f /tmp/badge-admin/server.log | grep -aE "TCP|login|heartbeat|error|geocod|Tianditu" --line-buffered
|
|||
|
|
|
|||
|
|
# 检查数据库
|
|||
|
|
python3 -c "
|
|||
|
|
import sqlite3
|
|||
|
|
conn = sqlite3.connect('/tmp/badge-admin/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())
|
|||
|
|
"
|
|||
|
|
```
|