app更新

This commit is contained in:
flowerstonezl
2026-01-20 17:24:15 +08:00
parent 182520f858
commit adc8f0569a
829 changed files with 421 additions and 78849 deletions

View File

@@ -1,108 +0,0 @@
# SkyLink 贡献指南
感谢您对 SkyLink 项目的关注!我们欢迎各种形式的贡献。
## 🐛 报告问题
如果您发现任何 bug请通过以下方式报告
1. **检查现有 Issue** - 确保该问题还没被报告
2. **创建新 Issue** - 提供详细的错误描述、复现步骤和环境信息
### Issue 模板
```
## 问题描述
清晰地描述您遇到的问题
## 复现步骤
1. ...
2. ...
3. ...
## 预期行为
应该发生什么
## 实际行为
实际上发生了什么
## 环境信息
- 系统: Linux / Windows / macOS
- 编译器: gcc / clang / MSVC
- 其他相关信息
```
## 📝 提交 Pull Request
1. **Fork** 本仓库
2. **创建特性分支** (`git checkout -b feature/amazing-feature`)
3. **提交更改** (`git commit -m 'Add amazing feature'`)
4. **推送分支** (`git push origin feature/amazing-feature`)
5. **创建 Pull Request**
### PR 检查清单
- [ ] 代码遵循项目的编码规范
- [ ] 添加了必要的注释和文档
- [ ] 更新了相关的 README
- [ ] 没有引入新的警告或错误
- [ ] 所有测试都通过了
## 📚 开发规范
### C++ 代码规范
- 使用 Google C++ Style Guide (改进版)
- 变量名: `snake_case`
- 类/结构体: `PascalCase`
- 常量: `UPPER_SNAKE_CASE`
- 成员变量: `snake_case_` (末尾下划线)
- 方法: `camelCase``snake_case`
### Kotlin 代码规范
- 遵循 Kotlin 官方编码规范
- 使用 Kotlin idioms
- 避免 Java 风格的代码
### 注释规范
- 使用中英文混用(优先中文)
- 关键逻辑必须有说明
- 使用 Doxygen 风格的文档注释
## 🧪 测试
在提交 PR 之前,请确保:
1. 编译不出错
2. 没有新的警告
3. 功能正常工作
4. 不会破坏现有功能
## 📖 改进文档
文档改进总是受欢迎的!您可以:
- 修正拼写错误
- 改进说明和示例
- 添加新的教程
- 翻译文档
## 🎨 改进设计
如果您有关于 UI/UX 的建议,请:
1. 创建一个 Issue 讨论
2. 提供设计草图或截图
3. 解释您的改进理由
## 💬 联系我们
- **邮件**: support@skylink.dev
- **讨论**: GitHub Discussions
- **问题**: GitHub Issues
---
**感谢您的贡献!** 🎉

View File

@@ -1,297 +0,0 @@
# SkyLink 安装和编译指南
## 系统需求
### 通用需求
- Git
- CMake 3.16+
- C++ 编译器 (GCC 9+ / Clang 10+ / MSVC 2019+)
### 机载端 (ROS 2)
- Ubuntu 22.04 LTS (推荐)
- ROS 2 Humble
- OpenCV 4.5+
- libjpeg-turbo
### PC 端 (Qt 6)
- Qt 6.2+
- OpenCV 4.5+
- CMake 3.24+
### 移动端 (Android)
- Android 12+ (API 31+)
- Android Studio Flamingo+
- Kotlin 1.9.20+
---
## 🔧 机载端编译 (ROS 2)
### 1. 安装 ROS 2 Humble
```bash
# 添加 ROS 2 源
sudo apt update
sudo curl -sSL https://raw.githubusercontent.com/ros/rosdistro/master/ros.asc | sudo apt-key add -
sudo sh -c 'echo "deb [arch=$(dpkg --print-architecture)] http://packages.ros.org/ros2/ubuntu $(. /etc/os-release && echo $VERSION_CODENAME) main" > /etc/apt/sources.list.d/ros2.list'
# 安装 ROS 2 Humble
sudo apt update
sudo apt install ros-humble-ros-core
```
### 2. 安装依赖
```bash
# 系统依赖
sudo apt install -y \
build-essential \
python3-dev \
python3-colcon-common \
libopencv-dev \
libjpeg-turbo-dev \
libmavros-dev
# ROS 2 依赖
sudo apt install -y \
ros-humble-rclcpp \
ros-humble-sensor-msgs \
ros-humble-cv-bridge \
ros-humble-image-transport \
ros-humble-mavros
```
### 3. 编译 SkyLink ROS 2 包
```bash
# 导入 ROS 2 环境
source /opt/ros/humble/setup.bash
# 进入项目目录
cd ~/skylink_ros2
# 运行编译脚本
./build.sh release
# 或手动编译
colcon build --symlink-install --cmake-args -DCMAKE_BUILD_TYPE=Release
```
### 4. 启动机载端
```bash
# 导入编译产物
source ~/skylink_ros2/install/setup.bash
# 编辑配置文件(可选)
nano src/skylink_bridge/config/params.yaml
# 启动节点
ros2 launch skylink_bridge bridge.launch.py
```
---
## 🖥️ PC 端编译 (Qt 6)
### 1. 安装 Qt 6
```bash
# Ubuntu
sudo apt install -y \
qt6-base-dev \
qt6-tools-dev \
qt6-network-dev \
qt6-concurrent-dev \
libopencv-dev
# Windows (使用 Qt Online Installer)
# https://www.qt.io/download-open-source
```
### 2. 编译 SkyLink 地面站
```bash
cd ~/skylink_qt_station
# 创建编译目录
mkdir build && cd build
# 配置
cmake .. -DCMAKE_BUILD_TYPE=Release
# 编译
make -j$(nproc)
# 运行
./skylink_station
```
### 3. 基于 Qt Creator 开发
```bash
# 用 Qt Creator 打开项目
qtcreator CMakeLists.txt
# 或 .pro 文件(如果存在)
qtcreator skylink_station.pro
```
---
## 📱 移动端编译 (Android)
### 1. 安装 Android Studio
从 [Android 官网](https://developer.android.com/studio) 下载并安装。
### 2. 打开项目
```bash
# 在 Android Studio 中:
File -> Open -> 选择 skylink_android_app
```
### 3. 配置 SDK
- 打开 `Tools -> SDK Manager`
- 安装 Android SDK 34
- 安装 NDK 26+(如需原生代码)
### 4. 编译 APK
```bash
# 使用 Gradle (Android Studio 内)
Build -> Build Bundle(s) / APK(s)
# 或命令行
cd skylink_android_app
./gradlew build
```
### 5. 安装到设备
```bash
# 使用 ADB
adb install -r app/build/outputs/apk/release/app-release.apk
# 或在 Android Studio 中
Run -> Run 'app'
```
---
## 🐳 Docker 编译 (ROS 2 机载端)
### 1. 安装 Docker
```bash
# Ubuntu
sudo apt install docker.io docker-compose
# 启动 Docker 服务
sudo systemctl start docker
sudo usermod -aG docker $USER
```
### 2. 构建镜像
```bash
cd ~/skylink
docker-compose build ros2-bridge
```
### 3. 运行容器
```bash
docker-compose up -d ros2-bridge
# 查看日志
docker-compose logs -f ros2-bridge
```
---
## ✅ 验证安装
### 机载端
```bash
# 检查节点是否运行
ros2 node list
# 查看发布的 Topic
ros2 topic list
# 检查参数
ros2 param list
```
### PC 端
```bash
# 启动地面站应用
./skylink_station
# 或调试模式
gdb ./skylink_station
```
### 移动端
```bash
# 检查 APK 签名
jarsigner -verify -verbose -certs app/build/outputs/apk/release/app-release.apk
# 运行应用
adb shell am start -n com.skylink.app/.MainActivity
```
---
## 🔍 故障排查
### 编译错误
#### CMake 找不到 Qt 6
```bash
# 设置 Qt 6 路径
cmake .. -DQt6_DIR=/path/to/Qt6/lib/cmake/Qt6
```
#### 缺少 OpenCV
```bash
# 重新安装 OpenCV
sudo apt install libopencv-dev
# 或从源代码编译
```
### 运行时错误
#### UDP 端口被占用
```bash
# 查找占用端口的进程
sudo lsof -i :9999
# 修改配置文件中的端口
# skylink_ros2/src/skylink_bridge/config/params.yaml
```
#### 相机设备找不到
```bash
# 列出可用的摄像头
v4l2-ctl --list-devices
# 检查权限
sudo chmod 666 /dev/video0
```
---
## 📚 参考资源
- [ROS 2 官方文档](https://docs.ros.org/en/humble/)
- [Qt 6 官方文档](https://doc.qt.io/)
- [Android 开发指南](https://developer.android.com/docs)
- [OpenCV 教程](https://docs.opencv.org/)
---
**需要帮助?** 请提交 Issue 或查看 [CONTRIBUTING.md](CONTRIBUTING.md)

View File

@@ -1,86 +1,94 @@
# SkyLink 项目总览
# SkyLink - 多平台实时视频传输系统
## 🎯 项目目标
一个分布式实时视频传输解决方案连接机载端ROS 2、PC 地面站Qt 6和移动端Android
SkyLink 是一个**多平台实时视频传输系统**,旨在为无人机、移动机器人和其他移动平台提供低延迟、高可靠性的实时视频流传输和地面控制能力。
## 🏗️ 系统架构
## 🌟 核心特性
| 特性 | 说明 |
|------|------|
| 📺 **实时传输** | 低延迟的 UDP 广播,支持高清视频实时显示 |
| 🌐 **多平台支持** | 机载端 (ROS 2)、PC 地面站 (Qt 6)、移动端 (Android) |
| 📍 **GPS 集成** | 实时位置标注和轨迹记录 |
| 📊 **智能压缩** | 自适应 JPEG 压缩,根据网络状况动态调整质量 |
| 📈 **完整统计** | FPS、丢包率、带宽等实时统计 |
| 💾 **数据记录** | 支持本地视频和 GPS 日志保存 |
| 🔄 **自动重组** | UDP 分片自动检测和重组 |
| 🎨 **现代化 UI** | Qt 6 美观界面 + Android Material Design |
---
```
┌─────────────────────┐
│ ROS 2 机载端 │
│ - 相机采集 │
│ - GPS 传感器 │
│ - UDP 发送广播 │
└──────────┬──────────┘
UDP 广播 (9999)
┌──────┴──────┐
│ │
┌───▼────────┐ ┌─▼──────────────┐
│ Qt PC 地面站 │ │ Android 移动端 │
│ - 解码显示 │ │ - 实时预览 │
│ - GPS 绘图 │ │ - 数据记录 │
│ - 丢包统计 │ │ - 远程控制 │
└────────────┘ └────────────────┘
```
## 📂 项目结构
```
SkyLink/
├── README.md # 项目说明
├── INSTALL.md # 安装编译指南
├── PROTOCOL.md # 协议规范
├── CONTRIBUTING.md # 贡献指南
├── LICENSE # MIT 许可证
├── README.md # 本文件 (架构图、启动命令)
├── docker-compose.yml # (可选) Docker 部署
├── protocol/ # [核心] 公共协议定义
│ ├── protocol.h # C++ 结构体定义
│ └── Protocol.kt # Kotlin 参考实现
├── skylink_ros2/ # [机载端] ROS 2 工程
│ ├── src/skylink_bridge/
│ │ ── include/ # 头文件
│ │ ├── src/ # 源文件
│ │ ├── launch/ # 启动脚本
│ │ ├── config/ # 参数配置
│ │ ├── CMakeLists.txt
│ │ └── package.xml
└── build.sh # 快速编译脚本
├── skylink_ros2/ # [机载端] ROS 2 C++ Workspace
│ ├── src/
│ │ ── skylink_bridge/ # ROS 2 Package
│ │ ├── package.xml
│ │ ├── CMakeLists.txt
│ │ ├── launch/
│ │ │ └── bridge.launch.py
│ │ ├── config/
└── params.yaml
│ │ ├── include/
│ │ │ └── skylink_bridge/
│ │ │ └── udp_sender_node.hpp
│ │ └── src/
│ │ └── udp_sender_node.cpp
│ └── build.sh
├── skylink_qt_station/ # [PC端] Qt 工程
│ ├── src/ # 源代码
│ │ ├── main.cpp
│ │ ├── MainWindow.*
│ │ ├── GroundReceiver.*
│ │ └── DataLogger.*
│ ├── forms/ # UI 文件
├── skylink_qt_station/ # [PC端] Qt 6 C++ 工程
│ ├── CMakeLists.txt
│ ├── resources.qrc
── CMakeLists.txt
── src/
│ │ ├── main.cpp
│ │ ├── MainWindow.h
│ │ ├── MainWindow.cpp
│ │ ├── GroundReceiver.h
│ │ ├── GroundReceiver.cpp
│ │ └── DataLogger.h
│ └── forms/
│ └── MainWindow.ui
── skylink_android_app/ # [移动端] Android 工程
├── app/
│ │ ├── build.gradle.kts
── src/main/
├── AndroidManifest.xml
── java/com/skylink/app/
├── MainActivity.kt
├── network/
│ │ ── utils/
└── res/
│ ├── build.gradle.kts
│ └── settings.gradle.kts
└── docker-compose.yml # Docker 部署
── skylink_android_app/ # [移动端] Android Studio 工程
├── build.gradle.kts
├── app/
── build.gradle.kts
└── src/
── main/
├── AndroidManifest.xml
├── java/com/skylink/app/
── MainActivity.kt
│ ├── network/
│ │ │ ├── UdpReceiver.kt
│ │ │ └── PacketDef.kt
│ │ └── utils/
│ └── BitmapUtils.kt
│ └── res/
│ ├── layout/
│ │ └── activity_main.xml
│ └── values/
│ └── strings.xml
└── settings.gradle.kts
```
---
## 🚀 快速启动
## 🚀 快速开始
### 1⃣ 克隆仓库
```bash
git clone https://github.com/skylink-team/skylink.git
cd skylink
```
### 2⃣ 机载端启动ROS 2
### 1⃣ 机载端ROS 2
```bash
cd skylink_ros2
@@ -89,195 +97,110 @@ source install/setup.bash
ros2 launch skylink_bridge bridge.launch.py
```
### 3⃣ PC 端启动Qt 地面站)
**参数配置** (`skylink_ros2/src/skylink_bridge/config/params.yaml`)
```yaml
/**:
ros__parameters:
target_ip: "255.255.255.255" # 广播地址
target_port: 9999 # UDP 端口
jpeg_quality: 70 # 图像压缩质量
camera_topic: "/camera/image_raw"
gps_topic: "/mavros/global_position/global"
```
### 2⃣ PC 地面站Qt 6
```bash
cd ../skylink_qt_station
cd skylink_qt_station
mkdir build && cd build
cmake ..
make
./skylink_station
```
### 4⃣ 移动端启动Android
**功能特性**
- 📺 实时视频显示(支持 JPEG 解码和重组)
- 📍 GPS 位置绘制
- 📊 实时丢包率、FPS 显示
- 💾 视频帧保存到本地
在 Android Studio 中打开 `skylink_android_app`,编译并安装 APK
### 3⃣ Android 移动端
在 Android Studio 中打开 `skylink_android_app`,然后:
```bash
Build > Build Bundle(s) / APK(s) > Build APK
Build > Build Bundle(s) / APK(s)
```
---
**权限要求**
- `android.permission.INTERNET` (网络连接)
- `android.permission.WRITE_EXTERNAL_STORAGE` (保存数据)
## 🔧 系统需求
## 📋 核心协议
| 平台 | 最低要求 | 推荐配置 |
|------|--------|--------|
| **ROS 2 机载** | Ubuntu 20.04, ROS 2 Foxy | Ubuntu 22.04, ROS 2 Humble |
| **Qt PC 地面站** | Windows 10 / Linux / macOS | Windows 11 + Qt 6.5+ |
| **Android 移动** | Android 11 (API 30) | Android 12+ (API 31+) |
---
## 📊 性能指标
### 机载端 (ROS 2)
- 🎥 **支持分辨率**: 320×240 ~ 1920×1080
-**帧率**: 15 ~ 30 FPS (取决于网络)
- 🗜️ **压缩方案**: JPEG (质量可调, 1-100)
- 📡 **传输**: UDP 广播 (端口 9999)
- 💾 **资源占用**: ~50 MB 内存, 20-30% CPU (树莓派 4)
### PC 端 (Qt)
- 📺 **解码**: 硬件加速 (OpenGL)
- ⏱️ **显示延迟**: < 100 ms (局域网)
- 🗺️ **地图支持**: GPS 实时标注和轨迹
- 📊 **统计显示**: FPS, 丢包率, 带宽
- 💾 **录制**: 支持批量保存帧和 CSV 日志
### 移动端 (Android)
- 📱 **适配**: 手机 + 平板
- 🖼️ **显示**: 实时预览 + 缩放
- 🔍 **性能**: 耗电量 < 10% (Snapdragon 8)
- 📍 **GPS**: 实时位置显示
- 💾 **存储**: 自动本地备份
---
## 🏗️ 核心架构
### 数据流
### 数据包结构 (`protocol/protocol.h`)
```
ROS 2 机载端
↓ (相机 + GPS)
编码为 JPEG + 分片
↓ (UDP 广播)
网络传输 (局域网 / WiFi)
接收端接收
↓ (收集分片)
重组 JPEG 数据
↓ (解码)
显示到屏幕
可选:保存文件或处理
+-------+----------+-------+-----------+---------+----------+-----+-----+-----+
| Magic | Frame ID | Chunks| ChunkIdx | DataLen | Timestamp| Lat | Lon | Alt |
|2 bytes| 4 bytes |2bytes | 2 bytes | 2 bytes |8 bytes |8 by |8 by |8 by|
+-------+----------+-------+-----------+---------+----------+-----+-----+-----+
```
### 核心组件
**关键字段**
- **Magic**: 0xAA55 (包头识别)
- **Frame ID**: 唯一帧编号
- **Chunks**: 总切片数(用于重组)
- **Timestamp**: GPS 时间戳
- **Lat/Lon/Alt**: 卫星定位数据
| 组件 | 说明 | 技术栈 |
|------|------|--------|
| **协议层** | 统一的数据包定义 | C++ Header / Kotlin Data Class |
| **机载端** | 图像采集 + 编码 | ROS 2 + OpenCV + JPEG |
| **PC 端** | UDP 接收 + 重组 + 显示 | Qt 6 + OpenGL |
| **移动端** | UDP 接收 + 解码 | Android + Coroutines |
## 🔧 开发工具链
---
| 端 | 技术栈 |
|---------|------------------------------|
| 机载端 | ROS 2, C++17, OpenCV, JPEG |
| PC 端 | Qt 6, C++17, OpenGL |
| 移动端 | Android 12+, Kotlin, Coroutines |
## 📚 文档导航
| 文档 | 内容 |
|------|------|
| [README.md](README.md) | 快速开始、架构、启动命令 |
| [INSTALL.md](INSTALL.md) | 详细的安装和编译步骤 |
| [PROTOCOL.md](PROTOCOL.md) | 数据包结构、协议规范 |
| [CONTRIBUTING.md](CONTRIBUTING.md) | 贡献指南、编码规范 |
---
## 🐛 故障排查
## 🎯 主要用途
### 机载端无法连接相机
```bash
### 1. 无人机遥控
```
ROS 2 自主控制器 + 摄像头
→ UDP 广播视频流
→ PC 地面站实时监看
→ 操作员远程决策
# 检查相机权限
v4l2-ctl --list-devices
# 检查相机帧率、分辨率、像素格式
v4l2-ctl --list-formats-ext
v4l2-ctl -d /dev/video0 --list-formats-ext
# 在终端 1 运行相机驱动
sudo apt install ros-humble-usb-cam # 如果还没装
ros2 run usb_cam usb_cam_node_exe --ros-args -p video_device:="/dev/video0"
ros2 topic hz /camera/image_raw
# 检查 ROS 2 节点
ros2 node list
ros2 topic list
```
### 2. 移动机器人监控
```
机器人搭载摄像头 + GPS
→ 通过 WiFi 传输视频
→ Android APP 实时接收
→ 云端存储日志
### PC 端收不到数据包
```bash
# 检查网络配置
ipconfig /all
# 使用 Wireshark 抓包检查 9999 端口
```
### 3. 户外勘测
### Android 连接超时
- 确保设备与 PC 在同一网段
- 检查防火墙是否开放 9999 端口
```
手持摄像设备 + ROS 节点
→ 实时采集视频和位置
→ PC 端地面站绘图
→ 生成任务地图
```
---
## 💡 创新亮点
1. **跨平台统一协议** - 单一协议支持所有端
2. **自适应压缩** - 根据网络状况自动调整质量
3. **无缝 GPS 集成** - 每帧数据都带有位置信息
4. **高效分片重组** - 支持 UDP 乱序和丢包
5. **模块化设计** - 易于扩展和集成
---
## 🔮 后续规划
### 短期 (v1.1)
- [ ] 启用 CRC16 校验和错误检测
- [ ] 支持录音轨道
- [ ] 改进 UI 响应速度
### 中期 (v2.0)
- [ ] 基于 QUIC 协议的改进
- [ ] 多路视频流支持
- [ ] 云端数据同步
- [ ] AI 目标检测集成
### 长期 (v3.0)
- [ ] 支持 5G 和边缘计算
- [ ] 实时 3D 地图构建
- [ ] 高精度 RTK-GNSS 定位
- [ ] WebRTC 网页客户端
---
## 📞 联系方式
- 📧 **邮件**: support@skylink.dev
- 🐙 **GitHub**: https://github.com/skylink-team/skylink
- 💬 **讨论**: GitHub Discussions
- 🔔 **问题**: GitHub Issues
---
## 📜 许可证
本项目采用 **MIT License** 开源。详见 [LICENSE](LICENSE)。
---
## 🙏 感谢
感谢所有贡献者的辛勤工作!
## Contributors
<a href="https://github.com/skylink-team/skylink/graphs/contributors">
<img src="https://contrib.rocks/image?repo=skylink-team/skylink" />
</a>
---
**⭐ 如果您觉得这个项目有帮助,请给个 Star**
**最后更新**: 2026-01-19 | **维护者**: flowzl

View File

@@ -234,27 +234,3 @@ if magic == 0xAA55:
---
## 📝 扩展计划
### 版本 1.1 (计划中)
- [ ] 启用 CRC16 校验
- [ ] 添加可选的数据加密
- [ ] 支持音频流
### 版本 2.0 (远期)
- [ ] 基于 QUIC 的改进
- [ ] 支持多路视频流
- [ ] 动态比特率自适应
---
## 📞 问题反馈
如有协议问题或改进建议,请:
- 创建 GitHub Issue
- 发送邮件至 protocol@skylink.dev
- 参与讨论
---
**最后更新**: 2026-01-19

386
README.md
View File

@@ -1,94 +1,81 @@
# SkyLink - 多平台实时视频传输系统
# SkyLink 项目总览
一个分布式实时视频传输解决方案连接机载端ROS 2、PC 地面站Qt 6和移动端Android
## 🎯 项目目标
## 🏗️ 系统架构
SkyLink 是一个**多平台实时视频传输系统**,旨在为无人机、移动机器人和其他移动平台提供低延迟、高可靠性的实时视频流传输和地面控制能力。
```
┌─────────────────────┐
│ ROS 2 机载端 │ (树莓派 / Jetson)
│ - 相机采集 │
│ - GPS 传感器 │
│ - UDP 发送广播 │
└──────────┬──────────┘
UDP 广播 (9999)
┌──────┴──────┐
│ │
┌───▼────────┐ ┌─▼──────────────┐
│ Qt PC 地面站 │ │ Android 移动端 │
│ - 解码显示 │ │ - 实时预览 │
│ - GPS 绘图 │ │ - 数据记录 │
│ - 丢包统计 │ │ - 远程控制 │
└────────────┘ └────────────────┘
```
## 🌟 核心特性
| 特性 | 说明 |
|------|------|
| 📺 **实时传输** | 低延迟的 UDP 广播,支持高清视频实时显示 |
| 🌐 **多平台支持** | 机载端 (ROS 2)、PC 地面站 (Qt 6)、移动端 (Android) |
| 📍 **GPS 集成** | 实时位置标注和轨迹记录 |
| 📊 **智能压缩** | 自适应 JPEG 压缩,根据网络状况动态调整质量 |
| 📈 **数据统计** | FPS、丢包率、带宽等实时统计 |
---
## 📂 项目结构
```
SkyLink/
├── README.md # 本文件 (架构图、启动命令)
├── docker-compose.yml # (可选) Docker 部署
├── README.md # 项目说明
├── INSTALL.md # 安装编译指南
├── PROTOCOL.md # 协议规范
├── protocol/ # [核心] 公共协议定义
│ ├── protocol.h # C++ 结构体定义
│ └── Protocol.kt # Kotlin 参考实现
├── skylink_ros2/ # [机载端] ROS 2 C++ Workspace
│ ├── src/
│ │ ── skylink_bridge/ # ROS 2 Package
│ │ ├── package.xml
│ │ ├── CMakeLists.txt
│ │ ├── launch/
│ │ │ └── bridge.launch.py
│ │ ├── config/
│ │ │ └── params.yaml
│ ├── include/
│ │ │ └── skylink_bridge/
│ │ │ └── udp_sender_node.hpp
│ │ └── src/
│ │ └── udp_sender_node.cpp
│ └── build.sh
├── skylink_ros2/ # [机载端] ROS 2 工程
│ ├── src/skylink_bridge/
│ │ ── include/ # 头文件
│ │ ├── src/ # 源文件
| | ├── test/ # 相机测试文件
│ │ ├── launch/ # 启动脚本
│ │ ├── config/ # 参数配置
│ │ ├── CMakeLists.txt
│ │ └── package.xml
└── build.sh # 快速编译脚本
├── skylink_qt_station/ # [PC端] Qt 6 C++ 工程
│ ├── CMakeLists.txt
│ ├── resources.qrc
│ ├── src/
├── skylink_qt_station/ # [PC端] Qt 工程
│ ├── src/ # 源代码
│ │ ├── main.cpp
│ │ ├── MainWindow.h
│ │ ├── MainWindow.cpp
│ │ ── GroundReceiver.h
│ ├── GroundReceiver.cpp
│ └── DataLogger.h
│ └── forms/
│ └── MainWindow.ui
│ │ ├── MainWindow.*
│ │ ├── GroundReceiver.*
│ │ ── DataLogger.*
│ ├── forms/ # UI 文件
├── resources.qrc
│ └── CMakeLists.txt
── skylink_android_app/ # [移动端] Android Studio 工程
├── build.gradle.kts
├── app/
── build.gradle.kts
└── src/
── main/
├── AndroidManifest.xml
├── java/com/skylink/app/
── MainActivity.kt
── network/
│ │ │ ├── UdpReceiver.kt
│ │ │ └── PacketDef.kt
│ │ └── utils/
│ │ └── BitmapUtils.kt
│ └── res/
│ ├── layout/
│ │ └── activity_main.xml
│ └── values/
│ └── strings.xml
└── settings.gradle.kts
── skylink_android_app/ # [移动端] Android 工程
├── app/
│ │ ├── build.gradle.kts
── src/main/
├── AndroidManifest.xml
── java/com/skylink/app/
├── MainActivity.kt
├── network/
── utils/
│ │ ── res/
│ ├── build.gradle.kts
│ └── settings.gradle.kts
```
## 🚀 快速启动
---
### 1⃣ 机载端ROS 2
## 🚀 快速开始
### 1⃣ 克隆仓库
```bash
git clone http://www.flowzl.top/flowzl/flyLink.git
cd skylink
```
### 2⃣ 机载端启动ROS 2
```bash
cd skylink_ros2
@@ -97,124 +84,167 @@ source install/setup.bash
ros2 launch skylink_bridge bridge.launch.py
```
**参数配置** (`skylink_ros2/src/skylink_bridge/config/params.yaml`)
```yaml
/**:
ros__parameters:
target_ip: "255.255.255.255" # 广播地址
target_port: 9999 # UDP 端口
jpeg_quality: 70 # 图像压缩质量
camera_topic: "/camera/image_raw"
gps_topic: "/mavros/global_position/global"
```
### 2⃣ PC 地面站Qt 6
### 3⃣ PC 端启动Qt 地面站)
```bash
cd skylink_qt_station
cd ../skylink_qt_station
mkdir build && cd build
cmake ..
make
./skylink_station
```
**功能特性**
- 📺 实时视频显示(支持 JPEG 解码和重组)
- 📍 GPS 位置绘制
- 📊 实时丢包率、FPS 显示
- 💾 视频帧保存到本地
### 4⃣ 移动端启动Android
### 3 Android 移动端
在 Android Studio 中打开 `skylink_android_app`,然后:
Android Studio 中打开 `skylink_android_app`,编译并安装 APK
```bash
Build > Build Bundle(s) / APK(s)
Build > Build Bundle(s) / APK(s) > Build APK
```
**权限要求**
- `android.permission.INTERNET` (网络连接)
- `android.permission.WRITE_EXTERNAL_STORAGE` (保存数据)
## 📋 核心协议
### 数据包结构 (`protocol/protocol.h`)
```
+-------+----------+-------+-----------+---------+----------+-----+-----+-----+
| Magic | Frame ID | Chunks| ChunkIdx | DataLen | Timestamp| Lat | Lon | Alt |
|2 bytes| 4 bytes |2bytes | 2 bytes | 2 bytes |8 bytes |8 by |8 by |8 by|
+-------+----------+-------+-----------+---------+----------+-----+-----+-----+
```
**关键字段**
- **Magic**: 0xAA55 (包头识别)
- **Frame ID**: 唯一帧编号
- **Chunks**: 总切片数(用于重组)
- **Timestamp**: GPS 时间戳
- **Lat/Lon/Alt**: 卫星定位数据
## 🔧 开发工具链
| 端 | 技术栈 |
|---------|------------------------------|
| 机载端 | ROS 2, C++17, OpenCV, JPEG |
| PC 端 | Qt 6, C++17, OpenGL |
| 移动端 | Android 12+, Kotlin, Coroutines |
## 📝 编码规范
- **C++**Google C++ Style Guide (改进版)
- **Kotlin**Kotlin Coding Conventions
- **命名**:驼峰法(成员变量用 `m_` 前缀)
- **注释**:中英混用,关键逻辑必须有说明
## 🐛 故障排查
### 机载端无法连接相机
```bash
# 检查相机权限
v4l2-ctl --list-devices
# 检查相机帧率、分辨率、像素格式
v4l2-ctl --list-formats-ext
v4l2-ctl -d /dev/video0 --list-formats-ext
# 在终端 1 运行相机驱动
sudo apt install ros-humble-usb-cam # 如果还没装
ros2 run usb_cam usb_cam_node_exe --ros-args -p video_device:="/dev/video0"
ros2 topic hz /camera/image_raw
# 检查 ROS 2 节点
ros2 node list
ros2 topic list
```
### PC 端收不到数据包
```bash
# 检查网络配置
ipconfig /all
# 使用 Wireshark 抓包检查 9999 端口
```
### Android 连接超时
- 确保设备与 PC 在同一网段
- 检查防火墙是否开放 9999 端口
## 📄 许可证
MIT License - 详见 LICENSE 文件
## 👥 贡献指南
欢迎提交 Issue 和 Pull Request
---
**最后更新**: 2026-01-19
## 🔧 系统需求
| 平台 | 最低要求 | 推荐配置 |
|------|--------|--------|
| **ROS 2 机载** | Ubuntu 20.04, ROS 2 Foxy | Ubuntu 22.04, ROS 2 Humble |
| **Qt PC 地面站** | Windows 10 / Linux / macOS | Windows 11 + Qt 6.5+ |
| **Android 移动** | Android 11 (API 30) | Android 12+ (API 31+) |
---
## 🏗️ 核心架构
### 数据流
```
ROS 2 机载端
↓ (相机 + GPS)
编码为 JPEG + 分片
↓ (UDP 广播)
网络传输 (局域网 / WiFi)
接收端接收
↓ (收集分片)
重组 JPEG 数据
↓ (解码)
显示到屏幕
可选:保存文件或处理
```
### 核心组件
| 组件 | 说明 | 技术栈 |
|------|------|--------|
| **协议层** | 统一的数据包定义 | C++ Header / Kotlin Data Class |
| **机载端** | 图像采集 + 编码 | ROS 2 + OpenCV + JPEG |
| **PC 端** | UDP 接收 + 重组 + 显示 | Qt 6 + OpenGL |
| **移动端** | UDP 接收 + 解码 | Android + Coroutines |
---
## 📚 文档导航
| 文档 | 内容 |
|------|------|
| [README.md](README.md) | 快速开始、架构、启动命令 |
| [PROTOCOL.md](PROTOCOL.md) | 数据包结构、协议规范 |
---
## 🎯 主要用途
### 1. 无人机遥控
```
ROS 2 自主控制器 + 摄像头
→ UDP 广播视频流
→ PC 地面站实时监看
→ 操作员远程决策
```
### 2. 移动机器人监控
```
机器人搭载摄像头 + GPS
→ 通过 WiFi 传输视频
→ Android APP 实时接收
→ 云端存储日志
```
### 3. 户外勘测
```
手持摄像设备 + ROS 节点
→ 实时采集视频和位置
→ PC 端地面站绘图
→ 生成任务地图
```
---
## 💡 创新亮点
1. **跨平台统一协议** - 单一协议支持所有端
2. **自适应压缩** - 根据网络状况自动调整质量
3. **无缝 GPS 集成** - 每帧数据都带有位置信息
4. **高效分片重组** - 支持 UDP 乱序和丢包
5. **模块化设计** - 易于扩展和集成
---
## 🔮 后续规划
### 短期 (v1.1)
- [ ] 启用 CRC16 校验和错误检测
- [ ] 支持录音轨道
- [ ] 改进 UI 响应速度
### 中期 (v2.0)
- [ ] 基于 QUIC 协议的改进
- [ ] 多路视频流支持
- [ ] 云端数据同步
- [ ] AI 目标检测集成
### 长期 (v3.0)
- [ ] 支持 5G 和边缘计算
- [ ] 实时 3D 地图构建
- [ ] 高精度 RTK-GNSS 定位
- [ ] WebRTC 网页客户端
---
## 📞 联系方式
- 📧 **邮件**: support@skylink.dev
- 🐙 **GitHub**: https://github.com/skylink-team/skylink
- 💬 **讨论**: GitHub Discussions
- 🔔 **问题**: GitHub Issues
---
## 📜 许可证
本项目采用 **MIT License** 开源。详见 [LICENSE](LICENSE)。
---
## 🙏 感谢
感谢所有贡献者的辛勤工作!
## Contributors
<a href="https://github.com/skylink-team/skylink/graphs/contributors">
<img src="https://contrib.rocks/image?repo=skylink-team/skylink" />
</a>
---
**⭐ 如果您觉得这个项目有帮助,请给个 Star**
**最后更新**: 2026-01-19 | **维护者**: flowzl

View File

@@ -1,2 +0,0 @@
#Mon Jan 19 13:51:26 CST 2026
gradle.version=9.0-milestone-1

View File

@@ -1,2 +0,0 @@
#Mon Jan 19 13:52:42 CST 2026
java.home=D\:\\app\\android studio\\studio\\jbr

View File

@@ -1,3 +0,0 @@
# Default ignored files
/shelf/
/workspace.xml

View File

@@ -1 +0,0 @@
SkyLink

View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AndroidProjectSystem">
<option name="providerId" value="com.android.tools.idea.GradleProjectSystem" />
</component>
</project>

View File

@@ -1,123 +0,0 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<JetCodeStyleSettings>
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</JetCodeStyleSettings>
<codeStyleSettings language="XML">
<option name="FORCE_REARRANGE_MODE" value="1" />
<indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="4" />
</indentOptions>
<arrangement>
<rules>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:android</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:id</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>style</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
<order>ANDROID_ATTRIBUTE_ORDER</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>.*</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
</rules>
</arrangement>
</codeStyleSettings>
<codeStyleSettings language="kotlin">
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</codeStyleSettings>
</code_scheme>
</component>

View File

@@ -1,5 +0,0 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
</state>
</component>

View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="21" />
</component>
</project>

View File

@@ -1,18 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="deploymentTargetSelector">
<selectionStates>
<SelectionState runConfigName="app">
<option name="selectionMode" value="DROPDOWN" />
<DropdownSelection timestamp="2026-01-19T06:23:10.188259200Z">
<Target type="DEFAULT_BOOT">
<handle>
<DeviceId pluginId="PhysicalDevice" identifier="serial=6fa5a382" />
</handle>
</Target>
</DropdownSelection>
<DialogSelection />
</SelectionState>
</selectionStates>
</component>
</project>

View File

@@ -1,13 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DeviceTable">
<option name="columnSorters">
<list>
<ColumnSorterState>
<option name="column" value="Name" />
<option name="order" value="ASCENDING" />
</ColumnSorterState>
</list>
</option>
</component>
</project>

View File

@@ -1,19 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleMigrationSettings" migrationVersion="1" />
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="testRunner" value="CHOOSE_PER_TEST" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/app" />
</set>
</option>
</GradleProjectSettings>
</option>
</component>
</project>

View File

@@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectMigrations">
<option name="MigrateToGradleLocalJavaHome">
<set>
<option value="$PROJECT_DIR$" />
</set>
</option>
</component>
</project>

View File

@@ -1,9 +0,0 @@
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
<option name="id" value="Android" />
</component>
</project>

View File

@@ -1,43 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="navEditor-manualLayoutAlgorithm2">
<option name="myPositions">
<map>
<entry key="nav_graph.xml">
<value>
<LayoutPositions>
<option name="myPositions">
<map>
<entry key="homeFragment">
<value>
<LayoutPositions>
<option name="myPosition">
<Point>
<option name="x" value="12" />
<option name="y" value="12" />
</Point>
</option>
</LayoutPositions>
</value>
</entry>
<entry key="settingsFragment">
<value>
<LayoutPositions>
<option name="myPosition">
<Point>
<option name="x" value="256" />
<option name="y" value="12" />
</Point>
</option>
</LayoutPositions>
</value>
</entry>
</map>
</option>
</LayoutPositions>
</value>
</entry>
</map>
</option>
</component>
</project>

View File

@@ -1,17 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RunConfigurationProducerService">
<option name="ignoredProducers">
<set>
<option value="com.intellij.execution.junit.AbstractAllInDirectoryConfigurationProducer" />
<option value="com.intellij.execution.junit.AllInPackageConfigurationProducer" />
<option value="com.intellij.execution.junit.PatternConfigurationProducer" />
<option value="com.intellij.execution.junit.TestInClassConfigurationProducer" />
<option value="com.intellij.execution.junit.UniqueIdConfigurationProducer" />
<option value="com.intellij.execution.junit.testDiscovery.JUnitTestDiscoveryConfigurationProducer" />
<option value="org.jetbrains.kotlin.idea.junit.KotlinJUnitRunConfigurationProducer" />
<option value="org.jetbrains.kotlin.idea.junit.KotlinPatternConfigurationProducer" />
</set>
</option>
</component>
</project>

View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
</component>
</project>

View File

@@ -1,310 +0,0 @@
# ✅ 项目完成清单
## 📦 所有新增/修改文件一览
### 🔵 Kotlin 源代码文件 (3 个)
**app/src/main/java/com/skylink/app/ui/HomeFragment.kt**
- 首页 Fragment显示视频、GPS、统计信息
- 175 行代码
- 地址: `d:\code\skyLink\skylink_android_app\app\src\main\java\com\skylink\app\ui\HomeFragment.kt`
**app/src/main/java/com/skylink/app/ui/SettingsFragment.kt**
- 设置 Fragment包含 Ping 诊断工具
- 220 行代码
- 地址: `d:\code\skyLink\skylink_android_app\app\src\main\java\com\skylink\app\ui\SettingsFragment.kt`
**app/src/main/java/com/skylink/app/utils/PingUtil.kt**
- 网络诊断工具类
- 130 行代码
- 地址: `d:\code\skyLink\skylink_android_app\app\src\main\java\com\skylink\app\utils\PingUtil.kt`
### 🔵 XML 布局文件 (2 个)
**app/src/main/res/layout/fragment_home.xml**
- 首页 Fragment 的布局文件
- 地址: `d:\code\skyLink\skylink_android_app\app\src\main\res\layout\fragment_home.xml`
**app/src/main/res/layout/fragment_settings.xml**
- 设置页面的布局文件(包含 Ping 诊断 UI
- 200 行 XML
- 地址: `d:\code\skyLink\skylink_android_app\app\src\main\res\layout\fragment_settings.xml`
### 🔵 Navigation 配置 (1 个)
**app/src/main/res/navigation/nav_graph.xml**
- Navigation 图表,定义 Fragment 路由
- 地址: `d:\code\skyLink\skylink_android_app\app\src\main\res\navigation\nav_graph.xml`
### 🔵 菜单定义 (2 个)
**app/src/main/res/menu/bottom_nav_menu.xml**
- 底部导航菜单(首页、设置)
- 地址: `d:\code\skyLink\skylink_android_app\app\src\main\res\menu\bottom_nav_menu.xml`
**app/src/main/res/menu/activity_main_menu.xml**
- 顶部菜单(设置选项)
- 地址: `d:\code\skyLink\skylink_android_app\app\src\main\res\menu\activity_main_menu.xml`
### 🔵 样式和资源 (2 个)
**app/src/main/res/drawable/settings_result_bg.xml**
- Ping 结果显示区的背景样式
- 地址: `d:\code\skyLink\skylink_android_app\app\src\main\res\drawable\settings_result_bg.xml`
**app/src/main/res/color/bottom_nav_color.xml**
- 底部导航栏颜色状态定义
- 地址: `d:\code\skyLink\skylink_android_app\app\src\main\res\color\bottom_nav_color.xml`
### 🟠 修改的现有文件 (3 个)
**app/build.gradle.kts**
- 添加 Navigation、Fragment 依赖
- 变更: 添加 3 个新的 implementation 依赖
- 地址: `d:\code\skyLink\skylink_android_app\app\build.gradle.kts`
**app/src/main/java/com/skylink/app/MainActivity.kt**
- 重构为 Navigation 容器
- 变更: 完全重写,从 Activity 改为 Navigation 管理器
- 地址: `d:\code\skyLink\skylink_android_app\app\src\main\java\com\skylink\app\MainActivity.kt`
**app/src/main/res/layout/activity_main.xml**
- 改为 Navigation 布局结构
- 变更: 添加 NavHostFragment 和 BottomNavigationView
- 地址: `d:\code\skyLink\skylink_android_app\app\src\main\res\layout\activity_main.xml`
### 🔵 文档文件 (7 个)
**QUICK_START.md** ⭐ 推荐首先阅读
- 5 分钟快速开始指南
- ~10 页
- 地址: `d:\code\skyLink\skylink_android_app\QUICK_START.md`
**BUILD_AND_RUN.md**
- 构建和运行详细指南
- ~12 页
- 地址: `d:\code\skyLink\skylink_android_app\BUILD_AND_RUN.md`
**NAVIGATION_SETTINGS_GUIDE.md**
- 详细功能说明和 API 参考
- ~15 页
- 地址: `d:\code\skyLink\skylink_android_app\NAVIGATION_SETTINGS_GUIDE.md`
**IMPLEMENTATION_SUMMARY.md**
- 实现总结和技术细节
- ~16 页
- 地址: `d:\code\skyLink\skylink_android_app\IMPLEMENTATION_SUMMARY.md`
**FILE_MANIFEST.md**
- 完整文件清单和索引
- ~14 页
- 地址: `d:\code\skyLink\skylink_android_app\FILE_MANIFEST.md`
**FINAL_REPORT.md**
- 项目完成报告
- ~18 页
- 地址: `d:\code\skyLink\skylink_android_app\FINAL_REPORT.md`
**INDEX.md** 🗺️ 文档导航
- 文档索引和导航指南
- ~8 页
- 地址: `d:\code\skyLink\skylink_android_app\INDEX.md`
### 🔵 项目总结文件 (2 个)
**PROJECT_COMPLETION_SUMMARY.md**
- 项目完成总结
- 地址: `d:\code\skyLink\skylink_android_app\PROJECT_COMPLETION_SUMMARY.md`
**ANDROID_APP_UPDATE.md** (在主项目目录)
- Android 应用更新说明
- 地址: `d:\code\skyLink\ANDROID_APP_UPDATE.md`
---
## 📊 统计数据
### 代码统计
| 类型 | 数量 | 行数 |
|------|------|------|
| Kotlin 文件 | 3 | 525 |
| XML 布局 | 2 | 280 |
| XML 配置 | 7 | 80 |
| **总代码** | **12** | **~900** |
### 文档统计
| 类型 | 数量 | 页数 | 字数 |
|------|------|------|------|
| 功能指南 | 7 | ~90 | ~8000 |
| **总文档** | **7** | **~90** | **~8000** |
### 文件总计
| 类别 | 数量 |
|------|------|
| 新增 Kotlin | 3 |
| 新增 XML | 9 |
| 新增文档 | 9 |
| 修改现有 | 3 |
| **总计** | **24** |
---
## 🎯 快速检查表
### ✅ 功能实现
- [x] Navigation 导航系统
- [x] 首页 Fragment
- [x] 设置 Fragment
- [x] 网络 Ping 工具
- [x] 单个 Ping 功能
- [x] 批量 Ping 功能
- [x] 结果显示
- [x] UI 布局
### ✅ 技术实现
- [x] Coroutines 异步处理
- [x] Fragment 管理
- [x] Navigation 框架
- [x] Material Design
- [x] 异常处理
- [x] 内存管理
### ✅ 文档完整性
- [x] 快速开始指南
- [x] 详细功能说明
- [x] 构建运行指南
- [x] API 参考文档
- [x] 实现总结报告
- [x] 文件清单索引
- [x] 项目完成报告
- [x] 文档导航索引
### ✅ 质量保证
- [x] 代码规范
- [x] 无编译错误
- [x] 无运行异常
- [x] 功能测试完成
- [x] 文档完整
### ✅ 交付准备
- [x] 所有文件创建
- [x] 所有文件修改
- [x] 所有文档编写
- [x] 所有测试完成
- [x] 可投入使用
---
## 📂 文件位置快速导航
### Kotlin 源代码
```
d:\code\skyLink\skylink_android_app\app\src\main\java\com\skylink\app\
├── ui\HomeFragment.kt
├── ui\SettingsFragment.kt
└── utils\PingUtil.kt
```
### XML 布局和配置
```
d:\code\skyLink\skylink_android_app\app\src\main\res\
├── layout\
│ ├── fragment_home.xml
│ └── fragment_settings.xml
├── navigation\
│ └── nav_graph.xml
├── menu\
│ ├── bottom_nav_menu.xml
│ └── activity_main_menu.xml
├── drawable\
│ └── settings_result_bg.xml
└── color\
└── bottom_nav_color.xml
```
### 文档
```
d:\code\skyLink\skylink_android_app\
├── QUICK_START.md ⭐
├── BUILD_AND_RUN.md
├── NAVIGATION_SETTINGS_GUIDE.md
├── IMPLEMENTATION_SUMMARY.md
├── FILE_MANIFEST.md
├── FINAL_REPORT.md
├── INDEX.md 🗺️
└── PROJECT_COMPLETION_SUMMARY.md
```
---
## 🚀 下一步操作
### 立即使用
1. 👉 打开 Android Studio
2. 👉 打开项目:`d:\code\skyLink\skylink_android_app`
3. 👉 点击 Sync Now
4. 👉 连接设备或启动模拟器
5. 👉 点击 Run
### 学习文档
1. 👉 从 `QUICK_START.md` 开始
2. 👉 查看 `INDEX.md` 了解所有文档
3. 👉 根据需要阅读其他文档
### 探索代码
1. 👉 查看 `HomeFragment.kt`
2. 👉 查看 `SettingsFragment.kt`
3. 👉 查看 `PingUtil.kt`
4. 👉 查看 `nav_graph.xml`
---
## 📞 帮助资源
### 快速问题查阅
| 问题 | 查看文档 |
|------|---------|
| 如何快速上手? | QUICK_START.md |
| 如何构建应用? | BUILD_AND_RUN.md |
| Ping 如何使用? | NAVIGATION_SETTINGS_GUIDE.md |
| 文件在哪里? | FILE_MANIFEST.md |
| 如何故障排查? | BUILD_AND_RUN.md |
| 文档有哪些? | INDEX.md |
---
## ✨ 项目成就
| 项 | 成果 |
|----|------|
| 功能完成度 | ✅ 100% |
| 代码质量 | ✅ 优秀 |
| 文档完整度 | ✅ 100% |
| 测试覆盖 | ✅ 完整 |
| 生产就绪 | ✅ 完全就绪 |
---
## 🎊 完成信息
| 项 | 信息 |
|----|------|
| 项目完成日期 | 2026-01-19 |
| 版本号 | 1.1.0 |
| 开发状态 | ✅ 完成 |
| 测试状态 | ✅ 全部通过 |
| 文档状态 | ✅ 完整 |
| 交付状态 | ✅ 完全就绪 |
---
**所有文件均已创建和配置完成。**
**应用已可投入生产使用。** 🚀
**祝您使用愉快!** 🎉
---
*最后更新: 2026-01-19*
*项目状态: 完全就绪*

View File

@@ -1,81 +0,0 @@
// Generated by view binder compiler. Do not edit!
package com.skylink.app.databinding;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.FragmentContainerView;
import androidx.viewbinding.ViewBinding;
import androidx.viewbinding.ViewBindings;
import com.google.android.material.bottomnavigation.BottomNavigationView;
import com.skylink.app.R;
import java.lang.NullPointerException;
import java.lang.Override;
import java.lang.String;
public final class ActivityMainBinding implements ViewBinding {
@NonNull
private final LinearLayout rootView;
@NonNull
public final BottomNavigationView bottomNavigation;
@NonNull
public final FragmentContainerView navHostFragment;
private ActivityMainBinding(@NonNull LinearLayout rootView,
@NonNull BottomNavigationView bottomNavigation,
@NonNull FragmentContainerView navHostFragment) {
this.rootView = rootView;
this.bottomNavigation = bottomNavigation;
this.navHostFragment = navHostFragment;
}
@Override
@NonNull
public LinearLayout getRoot() {
return rootView;
}
@NonNull
public static ActivityMainBinding inflate(@NonNull LayoutInflater inflater) {
return inflate(inflater, null, false);
}
@NonNull
public static ActivityMainBinding inflate(@NonNull LayoutInflater inflater,
@Nullable ViewGroup parent, boolean attachToParent) {
View root = inflater.inflate(R.layout.activity_main, parent, false);
if (attachToParent) {
parent.addView(root);
}
return bind(root);
}
@NonNull
public static ActivityMainBinding bind(@NonNull View rootView) {
// The body of this method is generated in a way you would not otherwise write.
// This is done to optimize the compiled bytecode for size and performance.
int id;
missingId: {
id = R.id.bottom_navigation;
BottomNavigationView bottomNavigation = ViewBindings.findChildViewById(rootView, id);
if (bottomNavigation == null) {
break missingId;
}
id = R.id.nav_host_fragment;
FragmentContainerView navHostFragment = ViewBindings.findChildViewById(rootView, id);
if (navHostFragment == null) {
break missingId;
}
return new ActivityMainBinding((LinearLayout) rootView, bottomNavigation, navHostFragment);
}
String missingId = rootView.getResources().getResourceName(id);
throw new NullPointerException("Missing required view with ID: ".concat(missingId));
}
}

View File

@@ -1,123 +0,0 @@
// Generated by view binder compiler. Do not edit!
package com.skylink.app.databinding;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.viewbinding.ViewBinding;
import androidx.viewbinding.ViewBindings;
import com.skylink.app.R;
import java.lang.NullPointerException;
import java.lang.Override;
import java.lang.String;
public final class FragmentHomeBinding implements ViewBinding {
@NonNull
private final LinearLayout rootView;
@NonNull
public final TextView gpsText;
@NonNull
public final Button recordButton;
@NonNull
public final Button saveButton;
@NonNull
public final TextView statsText;
@NonNull
public final TextView statusText;
@NonNull
public final ImageView videoImage;
private FragmentHomeBinding(@NonNull LinearLayout rootView, @NonNull TextView gpsText,
@NonNull Button recordButton, @NonNull Button saveButton, @NonNull TextView statsText,
@NonNull TextView statusText, @NonNull ImageView videoImage) {
this.rootView = rootView;
this.gpsText = gpsText;
this.recordButton = recordButton;
this.saveButton = saveButton;
this.statsText = statsText;
this.statusText = statusText;
this.videoImage = videoImage;
}
@Override
@NonNull
public LinearLayout getRoot() {
return rootView;
}
@NonNull
public static FragmentHomeBinding inflate(@NonNull LayoutInflater inflater) {
return inflate(inflater, null, false);
}
@NonNull
public static FragmentHomeBinding inflate(@NonNull LayoutInflater inflater,
@Nullable ViewGroup parent, boolean attachToParent) {
View root = inflater.inflate(R.layout.fragment_home, parent, false);
if (attachToParent) {
parent.addView(root);
}
return bind(root);
}
@NonNull
public static FragmentHomeBinding bind(@NonNull View rootView) {
// The body of this method is generated in a way you would not otherwise write.
// This is done to optimize the compiled bytecode for size and performance.
int id;
missingId: {
id = R.id.gps_text;
TextView gpsText = ViewBindings.findChildViewById(rootView, id);
if (gpsText == null) {
break missingId;
}
id = R.id.record_button;
Button recordButton = ViewBindings.findChildViewById(rootView, id);
if (recordButton == null) {
break missingId;
}
id = R.id.save_button;
Button saveButton = ViewBindings.findChildViewById(rootView, id);
if (saveButton == null) {
break missingId;
}
id = R.id.stats_text;
TextView statsText = ViewBindings.findChildViewById(rootView, id);
if (statsText == null) {
break missingId;
}
id = R.id.status_text;
TextView statusText = ViewBindings.findChildViewById(rootView, id);
if (statusText == null) {
break missingId;
}
id = R.id.video_image;
ImageView videoImage = ViewBindings.findChildViewById(rootView, id);
if (videoImage == null) {
break missingId;
}
return new FragmentHomeBinding((LinearLayout) rootView, gpsText, recordButton, saveButton,
statsText, statusText, videoImage);
}
String missingId = rootView.getResources().getResourceName(id);
throw new NullPointerException("Missing required view with ID: ".concat(missingId));
}
}

View File

@@ -1,158 +0,0 @@
// Generated by view binder compiler. Do not edit!
package com.skylink.app.databinding;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ProgressBar;
import android.widget.ScrollView;
import android.widget.Spinner;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.viewbinding.ViewBinding;
import androidx.viewbinding.ViewBindings;
import com.skylink.app.R;
import java.lang.NullPointerException;
import java.lang.Override;
import java.lang.String;
public final class FragmentSettingsBinding implements ViewBinding {
@NonNull
private final ScrollView rootView;
@NonNull
public final Button clearButton;
@NonNull
public final EditText hostInput;
@NonNull
public final Button multiplePingButton;
@NonNull
public final Spinner pingCountSpinner;
@NonNull
public final ProgressBar pingProgress;
@NonNull
public final TextView resultText;
@NonNull
public final Button singlePingButton;
@NonNull
public final Spinner timeoutSpinner;
@NonNull
public final EditText udpPortInput;
private FragmentSettingsBinding(@NonNull ScrollView rootView, @NonNull Button clearButton,
@NonNull EditText hostInput, @NonNull Button multiplePingButton,
@NonNull Spinner pingCountSpinner, @NonNull ProgressBar pingProgress,
@NonNull TextView resultText, @NonNull Button singlePingButton,
@NonNull Spinner timeoutSpinner, @NonNull EditText udpPortInput) {
this.rootView = rootView;
this.clearButton = clearButton;
this.hostInput = hostInput;
this.multiplePingButton = multiplePingButton;
this.pingCountSpinner = pingCountSpinner;
this.pingProgress = pingProgress;
this.resultText = resultText;
this.singlePingButton = singlePingButton;
this.timeoutSpinner = timeoutSpinner;
this.udpPortInput = udpPortInput;
}
@Override
@NonNull
public ScrollView getRoot() {
return rootView;
}
@NonNull
public static FragmentSettingsBinding inflate(@NonNull LayoutInflater inflater) {
return inflate(inflater, null, false);
}
@NonNull
public static FragmentSettingsBinding inflate(@NonNull LayoutInflater inflater,
@Nullable ViewGroup parent, boolean attachToParent) {
View root = inflater.inflate(R.layout.fragment_settings, parent, false);
if (attachToParent) {
parent.addView(root);
}
return bind(root);
}
@NonNull
public static FragmentSettingsBinding bind(@NonNull View rootView) {
// The body of this method is generated in a way you would not otherwise write.
// This is done to optimize the compiled bytecode for size and performance.
int id;
missingId: {
id = R.id.clear_button;
Button clearButton = ViewBindings.findChildViewById(rootView, id);
if (clearButton == null) {
break missingId;
}
id = R.id.host_input;
EditText hostInput = ViewBindings.findChildViewById(rootView, id);
if (hostInput == null) {
break missingId;
}
id = R.id.multiple_ping_button;
Button multiplePingButton = ViewBindings.findChildViewById(rootView, id);
if (multiplePingButton == null) {
break missingId;
}
id = R.id.ping_count_spinner;
Spinner pingCountSpinner = ViewBindings.findChildViewById(rootView, id);
if (pingCountSpinner == null) {
break missingId;
}
id = R.id.ping_progress;
ProgressBar pingProgress = ViewBindings.findChildViewById(rootView, id);
if (pingProgress == null) {
break missingId;
}
id = R.id.result_text;
TextView resultText = ViewBindings.findChildViewById(rootView, id);
if (resultText == null) {
break missingId;
}
id = R.id.single_ping_button;
Button singlePingButton = ViewBindings.findChildViewById(rootView, id);
if (singlePingButton == null) {
break missingId;
}
id = R.id.timeout_spinner;
Spinner timeoutSpinner = ViewBindings.findChildViewById(rootView, id);
if (timeoutSpinner == null) {
break missingId;
}
id = R.id.udp_port_input;
EditText udpPortInput = ViewBindings.findChildViewById(rootView, id);
if (udpPortInput == null) {
break missingId;
}
return new FragmentSettingsBinding((ScrollView) rootView, clearButton, hostInput,
multiplePingButton, pingCountSpinner, pingProgress, resultText, singlePingButton,
timeoutSpinner, udpPortInput);
}
String missingId = rootView.getResources().getResourceName(id);
throw new NullPointerException("Missing required view with ID: ".concat(missingId));
}
}

View File

@@ -1,20 +0,0 @@
{
"version": 3,
"artifactType": {
"type": "APK",
"kind": "Directory"
},
"applicationId": "com.skylink.app",
"variantName": "debug",
"elements": [
{
"type": "SINGLE",
"filters": [],
"attributes": [],
"versionCode": 1,
"versionName": "1.0.0",
"outputFile": "app-debug.apk"
}
],
"elementType": "File"
}

View File

@@ -1,2 +0,0 @@
#- File Locator -
listingFile=../../apk/debug/output-metadata.json

View File

@@ -1,2 +0,0 @@
appMetadataVersion=1.1
androidGradlePluginVersion=8.2.0

View File

@@ -1,10 +0,0 @@
{
"version": 3,
"artifactType": {
"type": "COMPATIBLE_SCREEN_MANIFEST",
"kind": "Directory"
},
"applicationId": "com.skylink.app",
"variantName": "debug",
"elements": []
}

View File

@@ -1 +0,0 @@
<?xml version="1.0" encoding="utf-8" standalone="yes"?><Layout layout="activity_main" modulePackage="com.skylink.app" filePath="app\src\main\res\layout\activity_main.xml" directory="layout" isMerge="false" isBindingData="false" rootNodeType="android.widget.LinearLayout"><Targets><Target tag="layout/activity_main_0" view="LinearLayout"><Expressions/><location startLine="1" startOffset="0" endLine="34" endOffset="14"/></Target><Target id="@+id/nav_host_fragment" view="androidx.fragment.app.FragmentContainerView"><Expressions/><location startLine="15" startOffset="8" endLine="21" endOffset="50"/></Target><Target id="@+id/bottom_navigation" view="com.google.android.material.bottomnavigation.BottomNavigationView"><Expressions/><location startLine="25" startOffset="4" endLine="32" endOffset="42"/></Target></Targets></Layout>

View File

@@ -1 +0,0 @@
<?xml version="1.0" encoding="utf-8" standalone="yes"?><Layout layout="fragment_home" modulePackage="com.skylink.app" filePath="app\src\main\res\layout\fragment_home.xml" directory="layout" isMerge="false" isBindingData="false" rootNodeType="android.widget.LinearLayout"><Targets><Target tag="layout/fragment_home_0" view="LinearLayout"><Expressions/><location startLine="1" startOffset="0" endLine="74" endOffset="14"/></Target><Target id="@+id/status_text" view="TextView"><Expressions/><location startLine="10" startOffset="4" endLine="17" endOffset="43"/></Target><Target id="@+id/video_image" view="ImageView"><Expressions/><location startLine="20" startOffset="4" endLine="27" endOffset="55"/></Target><Target id="@+id/gps_text" view="TextView"><Expressions/><location startLine="30" startOffset="4" endLine="37" endOffset="43"/></Target><Target id="@+id/stats_text" view="TextView"><Expressions/><location startLine="40" startOffset="4" endLine="46" endOffset="43"/></Target><Target id="@+id/save_button" view="Button"><Expressions/><location startLine="56" startOffset="8" endLine="62" endOffset="43"/></Target><Target id="@+id/record_button" view="Button"><Expressions/><location startLine="64" startOffset="8" endLine="70" endOffset="45"/></Target></Targets></Layout>

View File

@@ -1 +0,0 @@
<?xml version="1.0" encoding="utf-8" standalone="yes"?><Layout layout="fragment_settings" modulePackage="com.skylink.app" filePath="app\src\main\res\layout\fragment_settings.xml" directory="layout" isMerge="false" isBindingData="false" rootNodeType="android.widget.ScrollView"><Targets><Target tag="layout/fragment_settings_0" view="ScrollView"><Expressions/><location startLine="1" startOffset="0" endLine="213" endOffset="12"/></Target><Target id="@+id/udp_port_input" view="EditText"><Expressions/><location startLine="38" startOffset="12" endLine="45" endOffset="75"/></Target><Target id="@+id/host_input" view="EditText"><Expressions/><location startLine="79" startOffset="12" endLine="86" endOffset="75"/></Target><Target id="@+id/ping_count_spinner" view="Spinner"><Expressions/><location startLine="103" startOffset="12" endLine="107" endOffset="75"/></Target><Target id="@+id/timeout_spinner" view="Spinner"><Expressions/><location startLine="124" startOffset="12" endLine="128" endOffset="75"/></Target><Target id="@+id/single_ping_button" view="Button"><Expressions/><location startLine="140" startOffset="12" endLine="147" endOffset="73"/></Target><Target id="@+id/multiple_ping_button" view="Button"><Expressions/><location startLine="149" startOffset="12" endLine="156" endOffset="74"/></Target><Target id="@+id/ping_progress" view="ProgressBar"><Expressions/><location startLine="160" startOffset="8" endLine="165" endOffset="42"/></Target><Target id="@+id/clear_button" view="Button"><Expressions/><location startLine="191" startOffset="16" endLine="197" endOffset="43"/></Target><Target id="@+id/result_text" view="TextView"><Expressions/><location startLine="200" startOffset="12" endLine="208" endOffset="53"/></Target></Targets></Layout>

Some files were not shown because too many files have changed in this diff Show More