# zyhk_sio_client

室内无人机任务自动化桥接程序。连接 **Car 平台（Socket.IO）**、**px4ctrl（WebSocket）** 和 **平台 HTTP/MQTT API**，完成从接收任务、下发飞行指令到上报状态的全流程。

C++ bridge application connecting **Socket.IO car platform**, **px4ctrl WebSocket**, and **platform HTTP/MQTT API** for indoor UAV mission automation.

---

## 目录

- [系统架构](#系统架构)
- [环境要求](#环境要求)
- [部署方式](#部署方式)
  - [离线部署（推荐生产环境）](#离线部署推荐生产环境)
  - [在线部署](#在线部署)
- [编译](#编译)
- [配置说明](#配置说明)
- [启动](#启动)
  - [生产环境](#生产环境)
  - [开发/测试环境](#开发测试环境)
- [版本管理](#版本管理)
- [部署与打包](#部署与打包)
  - [一键打包](#一键打包)
  - [安装部署](#安装部署)
  - [systemd 服务管理](#systemd-服务管理)
  - [卸载](#卸载)
- [调试网](#调试网)
- [测试流程](#测试流程)
  - [手动测试](#手动测试)
  - [自动测试脚本](#自动测试脚本)
- [目录结构](#目录结构)
- [常见问题](#常见问题)

---

## 系统架构

| 组件 | 协议 | 职责 |
|:---|:---|:---|
| `SioCarClient` | Socket.IO | 接收飞行任务、发送实时状态、上报任务进度 |
| `Px4ctrlWsClient` | WebSocket | 向 px4ctrl 发送 `zyhk` 指令、持续轮询无人机状态 |
| `PlatformHttpClient` | HTTP (POST/Form/File) | 上传飞行历史、状态历史、照片/日志 |
| `PlatformMqttClient` | MQTT | 与平台 MQTT Broker 进行 JSON 命令交互 |
| `ZyhkClientApp` | — | 主状态机：任务生命周期、状态转换、照片上传 |

**数据流：**

```
Car Platform (Socket.IO)
    │  car_uav_begin_mission
    ▼
zyhk_sio_client ──► px4ctrl (WebSocket: zyhk 指令)
    │                    │
    │◄───────────────────┘  state 推送
    │
    ├──► Platform HTTP API (flight history / photo upload)
    └──► Platform MQTT Broker (cmd request / response)
```

---

## 环境要求

- **操作系统**：Ubuntu 20.04（或其他兼容的 Debian/Ubuntu 版本）
- **编译器**：g++ 支持 C++14
- **构建工具**：CMake ≥ 2.8.3
- **系统依赖**：
  - `libmosquitto-dev` + `mosquitto`
  - `libwebsockets-dev`
  - `libcurl4-openssl-dev`
  - `libasio-dev`
  - `build-essential` / `pkg-config`
- **Python 测试环境**（可选，仅测试用）：
  - `python-socketio` (v4.x)
  - `aiohttp`
  - `websockets`

---

## 部署方式

### 离线部署（推荐生产环境）

适用于无外网访问权限的目标机器。

#### 步骤 1：在联网机器下载依赖

```bash
cd zyhk_sio_client
./scripts/download_offline_deps.sh
```

该脚本会下载：
- 系统 `.deb` 包到 `third_party/offline_deps/apt/`
- Python `wheel` 包到 `third_party/offline_deps/pip/`
- `socket.io-client-cpp` 源码到 `third_party/sioclient/`

> ⚠️ **重要**：下载机器与目标离线机器必须是 **相同 OS 版本**（如都是 Ubuntu 20.04）。

#### 步骤 2：复制到离线机器

将整个项目目录（含 `third_party/`）打包，复制到离线机器：

```bash
# 在联网机器上
tar czvf zyhk_sio_client_offline.tar.gz zyhk_sio_client/
# 传输到离线机器
scp zyhk_sio_client_offline.tar.gz user@offline-host:/opt/
```

#### 步骤 3：在离线机器安装依赖并编译

```bash
cd /opt/zyhk_sio_client
./setup_client_env.sh --offline
```

该脚本会：
1. 调用 `scripts/install_offline_deps.sh` 安装 `.deb` 和 Python wheel
2. 检测预编译的 `libsioclient.a` 是否与当前系统兼容
3. 如不兼容，自动从源码重新编译 `sioclient`
4. 编译项目，生成 `build/zyhk_sio_client`

编译完成后即可运行。

---

### 在线部署

适用于有外网访问权限的机器。

```bash
cd zyhk_sio_client
./setup_client_env.sh
```

该脚本会自动：
1. 检测并安装缺失的系统依赖（`apt-get install`）
2. 编译项目
3. 验证二进制文件和链接库

---

## 编译

如果已安装好依赖，也可以手动编译：

```bash
cd zyhk_sio_client
mkdir -p build && cd build
cmake ..
make -j$(nproc)
```

生成的二进制文件：`build/zyhk_sio_client`

---

## 配置说明

配置文件路径：`config/config.json`

关键字段：

| 字段 | 说明 | 默认值 |
|:---|:---|:---|
| `px4ctrl.host` / `port` | px4ctrl WebSocket 地址 | `127.0.0.1:53001` |
| `socketio.uri` | Car 平台 Socket.IO URI | `http://127.0.0.1:8080` |
| `socketio.access_key` | Socket.IO 认证密钥 | `1234-5678` |
| `mqtt.host` / `port` | MQTT Broker 地址 | `127.0.0.1:1883` |
| `platform.url` | 平台 HTTP API 基地址 | `http://127.0.0.1:8087` |
| `platform.site_id` | 站点标识 | `abcd-1234` |

命令行参数可覆盖配置文件中的值：

```bash
./zyhk_sio_client \
  --config config/config.json \
  --px4_host 192.168.1.100 \
  --px4_port 53001 \
  --sio_uri http://192.168.1.10:8081 \
  --access_key demo-key \
  --platform_url http://192.168.1.10:8087 \
  --site_id site-01
```

---

## 启动

### 生产环境

```bash
cd zyhk_sio_client
./build/zyhk_sio_client --config config/config.json
```

### 开发/测试环境

需要同时启动以下服务：

```bash
# 1. 启动 MQTT Broker（如未运行）
mosquitto -d -p 1883

# 2. 启动 px4ctrl 模拟器
cd service
python3 test_px4_server.py --host 127.0.0.1 --port 53001

# 3. 启动 Car 平台模拟器（交互式 CLI）
python3 test_car_server.py --host 127.0.0.1 --port 8081 --access_key 1234-5678

# 4. 启动客户端（新终端）
cd ..
./build/zyhk_sio_client --config config/config.json --sio_uri http://127.0.0.1:8081
```

> 注意：`test_car_server.py` 的 `--port 8081` 需要与 `config.json` 中 `socketio.uri` 的端口一致。

---

## 测试流程

### 手动测试

按上述**开发/测试环境**步骤启动所有服务后，`test_car_server.py` 会提供一个交互式 CLI：

```
========== Car Test Server CLI ==========
  Current altitude: 1.5m

  [Car -> Drone]  SocketIO Commands
    m  send car_uav_begin_mission (default, 2 waypoints + actions)
    n  send car_uav_begin_mission (minimal, 2 waypoints)
    1  send car_uav_begin_mission (single waypoint)
    2  send car_uav_begin_mission (three waypoints)
    3  send car_uav_begin_mission (with beforeTakeoffPhoto)
    e  send car_uav_begin_mission (malformed / edge case)
    r  send car_uav_can_reboot_uav

  [Config]
    h  set altitude (m)

  [Integration Test] 联动检测
    c  send mission + check PX4 WS command (zyhk) + status flow
    p  query PX4 status now
    s  show tracking summary
    x  reset tracking

    a  auto: mission + 3s + reboot
    q  quit
=========================================
```

**常用测试流程：**

1. **发送默认任务**：按 `m`，客户端会接收任务并向 px4ctrl 发送 `zyhk` 指令。
2. **修改起飞高度**：按 `h`，输入 `3.0`，后续所有任务的 `takeOffAltitude` 和航点 `z` 都会变为 `3.0`。
3. **联动检测**：按 `c`，先发送任务，然后轮询 px4ctrl HTTP 状态接口验证 `zyhk` 指令是否送达以及飞行状态流转。
4. **发送重启查询**：按 `r`，客户端在无人机空闲时会响应 `car_uav_can_reboot_uav`。

> **任务重叠保护**：客户端在 `has_task == true` 期间会忽略新的 `car_uav_begin_mission`（日志显示 `"Already has an active task, ignore new mission"`）。需等待当前任务完成（px4ctrl 回到 idle 状态）后再发送新任务。

---

### 自动测试脚本

#### 一键全链路测试

```bash
cd service
./run_full_test.sh
```

该脚本会自动：
1. 启动 `mosquitto`（如未运行）
2. 启动 `test_mqtt_server.py`
3. 启动 `test_px4_server.py`
4. 启动 `test_car_server.py`
5. 启动客户端 `zyhk_sio_client`
6. 通过 `send_mission.py` 发送测试任务
7. 等待任务执行完成

#### E2E 高度配置测试

```bash
cd scripts
python3 run_e2e_test.py
```

该脚本验证：**运行时修改高度配置后，任务 payload 中的 `takeOffAltitude` 和航点 `z` 是否正确更新**。

测试步骤：
1. 启动 px4ctrl 模拟器、car 模拟器、客户端
2. 等待自动任务完成（使用默认高度 `1.5m`）
3. 通过 car CLI 设置高度为 `3.0m`
4. 发送新任务
5. 验证 payload 中高度字段为 `3.0`
6. 验证客户端向 px4ctrl 发送了 `zyhk` 指令
7. 验证 px4ctrl 收到了指令

---

## 目录结构

```
zyhk_sio_client/
├── main.cpp                       # 入口 & 主状态机
├── px4ctrl_ws_client.{h,cpp}      # px4ctrl WebSocket 客户端
├── sio_car_client.{h,cpp}         # Car Socket.IO 客户端
├── platform_http_client.{h,cpp}   # 平台 HTTP 客户端
├── platform_mqtt_client.{h,cpp}   # 平台 MQTT 客户端
├── mission_context.h              # 任务共享状态
├── app_config.{h,cpp}             # 配置加载与解析
├── zyhk_types.h                   # 类型定义与状态码
├── config/
│   └── config.json                # 运行时配置
├── include/                       # sioclient 头文件
├── lib/                           # 预编译 sioclient 静态库
├── service/                       # Python 测试服务器
│   ├── test_car_server.py         # Car SocketIO 模拟器 + CLI
│   ├── test_px4_server.py         # px4ctrl WebSocket/HTTP 模拟器
│   ├── test_mqtt_server.py        # 平台 MQTT 模拟器
│   ├── test_platform_server.py    # 平台 HTTP API 模拟器
│   ├── run_full_test.sh           # 一键全链路测试
│   └── send_mission.py            # SocketIO 任务发送工具
├── scripts/                       # 部署脚本
│   ├── install.sh                 # 安装脚本（systemd + 用户创建）
│   ├── uninstall.sh               # 卸载脚本
│   ├── package.sh                 # 一键打包脚本
│   ├── zyhk-sio-client.service    # systemd 服务模板
│   ├── download_offline_deps.sh   # 联网下载离线依赖
│   ├── install_offline_deps.sh    # 离线安装依赖
│   └── run_e2e_test.py            # E2E 自动化测试
├── setup_client_env.sh            # 主环境配置脚本（支持 --offline）
├── third_party/
│   ├── offline_deps/              # 离线依赖包
│   │   ├── apt/                   # .deb 系统包
│   │   └── pip/                   # Python wheels
│   └── sioclient/                 # socket.io-client-cpp 源码
└── doc/                           # 接口文档 (PDF)
```

---

## 版本管理

版本号定义在 `CMakeLists.txt` 中：

```cmake
project(zyhk_sio_client VERSION 1.2.0)
```

程序启动时会打印版本信息：

```
zyhk_sio_client v1.2.0 (git: a1b2c3d, built: 2026-05-17 01:20:00)
```

构建时自动生成 `generated/version.h`，包含：
- `ZYHK_VERSION_STRING` — 完整版本号
- `ZYHK_GIT_HASH` — 当前 Git commit short hash
- `ZYHK_BUILD_TIME` — 编译时间戳

---

## 部署与打包

### 一键打包

```bash
cd zyhk_sio_client
./scripts/package.sh
```

输出：`dist/zyhk_sio_client-<version>-<arch>.tar.gz`

打包内容包含：
- 编译后的二进制文件
- `config/config.json` 默认配置
- `install.sh` / `uninstall.sh` 安装脚本
- `zyhk-sio-client.service` systemd 服务文件

### 安装部署

将 tar.gz 复制到目标机器后执行：

```bash
tar xzvf zyhk_sio_client-1.2.0-x86_64.tar.gz
cd zyhk_sio_client-1.2.0-x86_64
sudo ./install.sh
```

安装脚本会自动完成：
1. 创建系统用户 `zyhk`
2. 复制二进制到 `/opt/zyhk_sio_client/bin/`
3. 复制配置到 `/opt/zyhk_sio_client/config/`
4. 安装 systemd 服务
5. 设置文件权限

> 如果 `/opt/zyhk_sio_client/config/config.json` 已存在，安装脚本会保留旧配置，并将新配置保存为 `config.json.new`。

### systemd 服务管理

```bash
# 启动
sudo systemctl start zyhk-sio-client

# 停止
sudo systemctl stop zyhk-sio-client

# 查看状态
sudo systemctl status zyhk-sio-client

# 开机自启
sudo systemctl enable zyhk-sio-client

# 查看日志
sudo journalctl -u zyhk-sio-client -f
```

### 卸载

```bash
sudo ./scripts/uninstall.sh
```

---

## 调试网

客户端内置轻量 HTTP 调试面板，默认端口 `18080`，可通过 `config.json` 中的 `stats.enabled` 和 `stats.web_port` 配置。

启动后访问：

- **Dashboard**: http://localhost:18080/
- **JSON 统计**: http://localhost:18080/api/stats
- **任务历史**: http://localhost:18080/api/missions

面板显示内容：
- **运行状态**: 启动时间、运行时长、SIO/WS/MQTT 连接状态
- **任务统计**: 总任务数、成功/失败/中止/进行中数量
- **内部状态**（实时刷新）:
  - px4 fs、zyhk sub、zyhk idx
  - has_task、photos_uploaded、upload_started
  - photo_count、last_completed_idx
  - 任务 ID、策略 ID、电池%、高度、垂直速度
- **任务历史表格**: 最近 50 条任务记录

---

## 常见问题

### Q: 离线部署时 sioclient 重新编译失败？

A: 项目已移除 `third_party/sioclient/lib/asio/` 子模块以减小仓库体积。离线脚本 `install_offline_deps.sh` 会在重新编译前自动从系统 `libasio-dev`（已通过离线 `.deb` 包安装）复制 asio 头文件到 sioclient 子模块路径，确保编译正常。

### Q: 客户端提示 "Already has an active task, ignore new mission"？

A: 这是正常行为。客户端在一次任务完成前（从 `TAKEOFF` → `LANDING` → `FINISHED`）会拒绝新的任务请求。请等待当前任务完成，或重启客户端。

### Q: `libsioclient.a` 链接报错（undefined reference）？

A: 预编译的静态库可能与目标系统的 `libwebsockets` 版本不兼容。运行 `./setup_client_env.sh --offline`，脚本会自动检测兼容性并在需要时从源码重新编译 sioclient。

### Q: 如何单独运行 MQTT 测试？

A:
```bash
cd service
./test_mqtt_env.sh
```
该脚本会自动安装/启动 mosquitto，并运行 MQTT 测试服务器。
