🛰️ OpenOrbitLink
基于 LoRa、网格中继和地面站网络的开源延迟容忍卫星消息系统
快速开始 •
架构 •
后端 API •
安卓应用 •
路线图














OpenOrbitLink 是一个用于通过 LoRa、业余数据包无线电、网格中继和地面站网络进行延迟容忍消息传递的开源研究技术栈。它并非一个能实现手机直连卫星发射的魔法工具。安卓手机本身无法发射任意 VHF/UHF/LoRa 射频信号,RTL-SDR 设备仅支持接收,且业余卫星通信路径受严格的许可和明文规则约束。本代码库现在将这些约束视为协议行为,而非脚注。
## 技术栈
 Python |
 Kotlin |
 FastAPI |
 SQLite |
 Docker |
 Android |
 C / NDK |
 Raspberry Pi |
 Gradle |
 gRPC |
 Protobuf |
 NumPy |
 Pytest |
 GitHub CI |
 LoRa SX1276 |
 TinyGS |
 TFLite |
 CMake |
 Codec2 |
 Lyra |
 C++ 17 |
 Rust |
**其他:** Jetpack Compose • Material3 • Signal Protocol • AES-256-GCM • BPv7/BPSec • SGP4 • CelesTrak • SatNOGS • Codec2 • Lyra/SoundStream • TFLite NNAPI • Golay FEC • RTL-SDR • Semtech LR2022 • Lacuna Space
## 架构
```
graph TB
subgraph "Android App - Kotlin"
A[Login Screen
JWT Auth Gate] --> B[Messaging Screen]
A --> C[Satellite Map]
A --> D[SOS Emergency]
A --> E[Ground Station UI]
A --> V[Voice Messaging]
B --> F[ApiClient
Bearer Token]
V --> VC[VoiceCodecManager]
end
subgraph "FastAPI Backend"
G["/api/v1/auth
Register / Login"] --> H["JWT Token
HS256 + bcrypt"]
I["/api/v1/send
Enqueue Message"] --> J["Duty Cycle Gate
1% ISM = 36s/hr"]
K["/api/v1/inbox
Poll Messages"] --> L["Per-User Queue
SQLite / PostgreSQL"]
M["/api/v1/status
Station Status"]
end
subgraph "Ground Station - RPi"
N[gRPC Server
Telemetry Stream] --> O[Relay Daemon
LoRa + SatNOGS]
O --> P[SX1276 Driver
868MHz LoRa]
O --> Q[TinyGS Client
Satellite Polling]
O --> PS[Pass Scheduler
Skyfield SGP4]
O --> DC[Doppler Comp
Freq Correction]
O --> IR[Inbox Router
Downlink Decode]
end
subgraph "Space Segment"
R[ISS APRS
145.825 MHz]
S[LoRa LEO Sats
FOSSA / Lacuna]
T[Carrier NTN
Starlink / Iridium]
end
F -->|HTTPS + JWT| G
F -->|HTTPS + JWT| I
F -->|HTTPS + JWT| K
VC -->|JNI| J
J -->|DTN Bundle| O
P -->|868 MHz ISM| S
Q -->|TinyGS API| S
N -.->|Future| T
style A fill:#1a1a2e,stroke:#4fc3f7,color:#fff
style G fill:#0d1117,stroke:#00bcd4,color:#fff
style O fill:#0d1117,stroke:#ff9800,color:#fff
style S fill:#0d1117,stroke:#ab47bc,color:#fff
```
### 混合语音编解码器
```
graph TB
subgraph "Android App"
MIC[Mic / AudioRecord]
PTT[PushToTalkEngine]
VCM[VoiceCodecManager
Kotlin]
SPK[Speaker / AudioTrack]
end
subgraph "Native Layer - C/C++ via JNI"
JNI[codec_bridge_jni.c]
subgraph "Codec Abstraction"
CI[codec_interface.h
OolCodecOps vtable]
C2W[codec2_wrapper.c
700C-3200]
LYW[lyra_codec_wrapper.cc
3200-9200]
end
subgraph "Enhancement"
NE[neural_enhancer.cc
SoundStream + LyraGAN]
TFL[tflite_runtime.h
Model management]
end
subgraph "Transport Prep"
PLR[packet_loss_recovery.h
PLC: repeat/interp/CNG]
FEC[codec2_fec.c
Golay + interleaving]
CHK[voice_chunker.h
80-byte LoRa chunks]
ATC[airtime_calculator.h
Duty cycle tracking]
end
end
subgraph "Protocol Layer - Python"
VT[voice_transport.py
Chunk/reassembly]
PKT[packet.py
VOICE_CHUNK type]
DTN[dtn.py
queue_voice]
end
subgraph "Physical Layer"
LORA[LoRa ISM 868MHz]
WIFI[WiFi / BLE]
NTN[Carrier NTN]
end
MIC --> PTT --> VCM
VCM --> JNI
JNI --> CI
CI --> C2W
CI --> LYW
LYW --> TFL
C2W --> FEC
FEC --> CHK --> ATC
CHK --> VT --> PKT --> DTN
DTN --> LORA
DTN --> WIFI
DTN --> NTN
LORA --> VT
VT --> PLR --> CI --> NE --> SPK
```
### 认证流程
```
sequenceDiagram
participant App as Android App
participant KS as Keystore
participant API as FastAPI
participant DB as Database
App->>API: POST /auth/register {username, password, invite_code}
API->>DB: Validate invite code
API->>DB: Create user (bcrypt hash)
API-->>App: {access_token, user_id}
App->>KS: Store JWT (AES-256-GCM)
Note over App,KS: All subsequent requests
App->>KS: Retrieve JWT
KS-->>App: Bearer token
App->>API: GET /inbox (Authorization: Bearer xxx)
API->>API: Decode JWT, verify expiry
API->>DB: Fetch user messages
API-->>App: [{message}, ...]
```
### 消息生命周期
```
stateDiagram-v2
[*] --> Compose: User types message
Compose --> Queued: POST /api/v1/send
Queued --> DutyCycleCheck: TX Worker polls
state DutyCycleCheck <
>
DutyCycleCheck --> Transmitting: Budget available
DutyCycleCheck --> Queued: Budget exhausted (wait)
Transmitting --> AwaitingACK: DTN bundle created + LoRa TX
AwaitingACK --> Delivered: ACK received
AwaitingACK --> Queued: Retry (max 5)
Transmitting --> Failed: TX error
Delivered --> [*]
Failed --> [*]
```
### 路线图
```
gantt
title OpenOrbitLink Production Roadmap
dateFormat YYYY-MM
axisFormat %b %Y
section Phase 1 - Core Infrastructure
Auth gate + FastAPI backend :done, p1a, 2026-05, 2026-06
ISM duty cycle enforcement :done, p1c, 2026-05, 2026-06
BPv7/BPSec protocol stack :done, p1e, 2026-05, 2026-06
Pass scheduler (Skyfield) :done, p1f, 2026-05, 2026-06
SX1276 LoRa driver :done, p1g, 2026-05, 2026-06
Doppler pre-compensation :done, p1h, 2026-05, 2026-06
DTN routing (spray-and-wait) :done, p1i, 2026-05, 2026-06
E2E simulation test suite :done, p1j, 2026-05, 2026-06
Offline sim CI validation :done, p1d, 2026-05, 2026-06
section Phase 2 - Hardware Deployment
RPi Zero 2W + RA-02 build :active, p2a, 2026-06, 2026-07
Field test (FOSSASAT-2E) :p2b, 2026-07, 2026-08
Lacuna LoneWhisper integration :p2c, 2026-07, 2026-09
Multi-gateway mesh :p2d, 2026-08, 2026-10
section Phase 3 - Regulatory + Scale
TRAI framework watch :p3a, 2026-07, 2026-12
Carrier NTN egress bridge :p3b, 2026-10, 2027-03
Scale to 50-200 users :p3c, 2026-12, 2027-06
```
## 当前可用功能
| 路径 | 状态 | 加密 | 许可证 | 备注 |
|:---|:---|:---:|:---:|:---|
| Android → FastAPI → LoRa 节点 → ISM 卫星/地面站 | **生产 MVP** | 是 | 仅限区域规定 | JWT 认证门禁,用户专属队列,占空比速率限制。 |
| 卫星过境调度(Skyfield SGP4) | **已实现** | 不适用 | 不适用 | 在 FOSSASAT-2E / ISS 过境期间自动调度 TX 突发。 |
| 多普勒预补偿 | **已实现** | 不适用 | 不适用 | 针对 LEO 卫星过境的实时频率校正。 |
| SX1276 LoRa 驱动程序(硬件 + 模拟) | **已实现** | 不适用 | 仅限区域规定 | 完整的异步 TX/RX,带模拟模式用于开发。 |
| DTN 路由(流行/喷雾与等待) | **已实现** | 不适用 | 不适用 | 带去重的多跳网格中继。 |
| BPv7/BPSec 组安全 | **已实现** | BIB+BCB | 频段感知 | 符合 RFC 9171/9172;在业余频段上 BCB 被阻止。 |
| APRS-IS 互联网网桥 | **已实现** | 否 | 需要业余发射许可 | 呼号验证,通行证计算,ISS 回退。 |
| TinyGS 兼容地面站接收/轮询 | 适配器已添加 | 不适用 | 接收无需 TX 许可证 | 使用现有站点基础设施。 |
| ISS APRS / 业余 AX.25 | 解码和帧辅助 | 否 | 需要业余发射许可 | 仅限有效的 APRS/AX.25 流量;非任意加密聊天。 |
| RTL-SDR V4 | 仅接收 | 不适用 | 接收通常免许可 | 适用于 NOAA/APRS 接收演示,非上行链路。 |
| 运营商 NTN(Pixel 9+、Galaxy S25+) | 融合目标 | 应用层 | 需要运营商套餐 | 封闭上行链路;OpenOrbitLink 将 DTN 桥接到 NTN 网关。 |
## 后端 API
FastAPI 后端为安卓应用提供经过身份验证的 REST 端点。
### 端点
| 方法 | 路径 | 认证 | 描述 |
|--------|------|:----:|-------------|
| `POST` | `/api/v1/auth/register` | — | 使用邀请码注册 |
| `POST` | `/api/v1/auth/login` | — | 登录 → JWT 令牌 |
| `GET` | `/api/v1/auth/me` | Bearer | 用户资料 |
| `POST` | `/api/v1/send` | Bearer | 将消息加入卫星发送队列 |
| `GET` | `/api/v1/inbox` | Bearer | 轮询接收的消息 |
| `GET` | `/api/v1/queue` | Bearer | 出站队列状态 |
| `GET` | `/api/v1/status` | Bearer | 站点状态 + 占空比 |
| `GET` | `/api/v1/passes` | Bearer | 即将到来的卫星过境(Skyfield) |
| `GET` | `/api/v1/passes/next` | Bearer | 下次过境预计时间 + 倒计时 |
| `GET` | `/api/v1/passes/duty` | Bearer | 当前占空比预算 |
| `GET` | `/api/v1/health` | — | 健康检查 |
### 快速开始(后端)
```
# 安装后端依赖
pip install -r backend/requirements.txt
# 运行 API 服务器
python -m uvicorn backend.main:app --reload --port 8000
# 注册用户
curl -X POST http://localhost:8000/api/v1/auth/register \
-H "Content-Type: application/json" \
-d '{"username":"pilot","password":"orbit2026!","invite_code":"BETA-OOL-2026"}'
# 发送消息
curl -X POST http://localhost:8000/api/v1/send \
-H "Authorization: Bearer " \
-H "Content-Type: application/json" \
-d '{"text":"Hello from orbit","destination":"ground-team","band":"ism"}'
```
### Docker 部署
```
cd docker
docker compose up --build -d
# 验证
curl http://localhost:8000/api/v1/health
```
## 安卓应用
安卓应用在访问任何 RF/DTN 功能之前需要进行身份验证。
- **登录界面** — 带有轨道动画的 JWT 认证,邀请码用于注册
- **令牌存储** — Android Keystore(EncryptedSharedPreferences, AES-256-GCM)
- **API 客户端** — 所有调用都包含 Bearer 令牌,401 错误时自动登出
- **认证门禁** — 没有有效令牌,无法访问 RF、消息或 DTN 界面
## 生产约束
| 约束 | 影响 |
|:---|:---|
| ISM 占空比(1%) | 每个频率约 36 秒/小时发送时间——由所有用户共享 |
| 577 bps 有效吞吐量 | 容量极低;不适合众多并发用户 |
| 语音仅限异步消息传递 | 通过 Codec2 700C 的 PTT 语音消息;非实时 VoIP |
| 印度监管状态 | 存在 TRAI 建议,但 D2D NTN 尚未面向消费者提供 |
| 硬件依赖 | 每个“用户”最终共享一个物理 LoRa 节点 |
| 最大 Beta 用户数 | ~10-20(一个 LoRa 节点,共享 ISM 占空比是上限) |
## 为什么 OpenOrbitLink 仍然与 NTN 并行重要
运营商 NTN(Starlink/T-Mobile, Skylo/Google/Verizon, 以及 Galaxy 运营商部署)已在旗舰手机上实现,但其受运营商控制、区域限制,并且首要优化用于紧急消息、短信和选定的低带宽应用。在印度,直接面向设备的运营商 NTN 仍是监管和运营商整合的目标:TRAI 于 2026-04-08 进行了开放式卫星网络授权磋商,并于 2026-05-15 发布了卫星频谱分配建议,但 OpenOrbitLink 不应假设消费者 D2D 服务已在该地区可用。
OpenOrbitLink 填补了这一空白:开放的 ISM 上行链路、任意 DTN 载荷、ISM 频段上的端到端加密、无运营商依赖,并且队列最终可以在运营商 NTN 网关可用时通过其出口。
## 核心功能
- **JWT 认证** —— 邀请码控制注册,bcrypt 密码,Keystore 令牌存储。
- **用户专属 DTN 队列** —— 消息在 SQLite 中排队,按优先级顺序传输。
- **ISM 占空比强制执行** —— 1% 占空比速率限制器,带有用户公平份额分配。
- **卫星过境调度器** —— Skyfield SGP4 预测 FOSSASAT-2E、ISS、Dream Big 过境。
- **多普勒预补偿** —— 使用距离变化率对 LEO 过境进行实时频率校正。
- **SX1276 LoRa 驱动程序** —— 异步 TX/RX 带模拟模式;RPi Zero 2W 上的 SPI 硬件。
- **DTN 路由** —— 流行和喷雾与等待策略,带去重。
- **BPv7/BPSec 协议栈** —— 符合 RFC 9171/9172 的包,使用 CBOR 编码。
- **APRS-IS 网桥** —— 通过互联网网关的 ISS APRS 回退,带呼号验证。
- **收件箱路由器** —— 自动下行链路解码、去重和用户专属消息路由。
- **混合语音编解码器** —— 自适应 Codec2/Lyra 编解码器栈,带神经网络增强。
- **法规遵从性日志记录** —— 每次发送都记录用户 ID、持续时间、频率以符合 ISM 规则。
- 带有 `TransmitBand` 和加密载荷保护的频段感知数据包格式。
- 阻止业余频段传输密文的安全策略。
- 用于呼号语法、操作员声明和业余发射的本地许可门控。
- LoRa/FOSSA 尺寸帧编码器,80 字节帧限制。
- TinyGS API 客户端脚手架,使用 Bearer 认证和 base64 TX 帧载荷。
- TLE 获取器,写入元数据 JSON 并在轨道数据过时时发出警告。
- 链路预算模拟器,提供真实的发送路径和有效吞吐量分析。
- 离线 `demo.py` 模拟,供无射频硬件的贡献者使用。
- **99 项测试套件**,涵盖 E2E 模拟、协议标准、语音管线和速率限制。
## 安全模型
OpenOrbitLink 将完整性与机密性分开:
| 频段 | 机密性 | 完整性/认证 | 原因 |
|:---|:---:|:---:|:---|
| 业余 | 阻止 | 允许 | 业余规则禁止混淆消息含义。 |
| ISM LoRa | 允许 | 允许 | 受区域 ISM 功率、占空比和设备规则约束。 |
| 已许可的私有/商用 | 允许 | 允许 | 必须遵守许可证或运营商协议。 |
| 运营商 NTN | 允许 | 允许 | 需要运营商服务和标准电话调制解调器支持。 |
| 仅接收 | 不适用 | 不适用 | 不存在发送路径。 |
Python 加密 API 现在需要指定频段:
```
crypto.encrypt(b"hello", key, band="ism") # allowed
crypto.encrypt(b"hello", key, band="amateur") # raises EncryptionPolicyError
```
## 链路预算现实
| 路径 | 具备发送能力 | 默认功率 | 频率 | 角色 |
|:---|:---:|:---:|:---:|:---|
| `LORA_ISM_UPLINK` | 是 | 100 mW | 868.1 MHz | 外部 LoRa 节点卫星/网格上行链路。 |
| `HAM_SDR_UPLINK` | 是 | 1 W | 145.825 MHz | 仅限已许可的业余电台。 |
| `HACKRF_EXPERIMENTAL` | 是 | 25 mW | 435 MHz | 需要滤波/放大/法律审查的实验室路径。 |
| `CARRIER_NTN` | 无开放上行链路 | 运营商管理 | NTN 频段 | 封闭的运营商路径,用于未来的 DTN 网关出口。 |
| `RTL_SDR_RX_ONLY` | 否 | 不适用 | VHF/UHF RX | 仅接收和解码。 |
在 700 bps 下,一个 256 字节的数据包,当前 21 字节的头部、32 字节的 FEC 字段和 2 字节的 CRC,在空中传输大约需要 3.55 秒。在计入任何额外开销之前,有效载荷速率约为 577 bps。
## 不应构建的功能
| 功能 | 原因 |
|:---|:---|
| **VoIP / 通话** | 在 577 bps 上进行 VoIP 是不可能的。语音仅限异步 PTD 消息。 |
| **手机直连射频** | 安卓手机无法发射 LoRa。RTL-SDR 仅限接收。这是硬件定律。 |
| **实时聊天** | DTN = 存储转发。分钟到数小时的延迟是一个 *特性*。将其定位为异步卫星消息传递。 |
| **实时语音流** | 改用按住即说的语音消息。Codec2 700C 在 700 bps 下,适用于最长约 19 秒的消息,符合 LoRa 占空比。 |
## 法律防护栏
- 业余频段发射需要有效的业余电台执照和电台标识。
- 业余频段载荷必须为明文;使用 BIB/完整性,而非 BCB/加密。
- APRS 支持针对有效的 AX.25/APRS 数据包,而非任意加密聊天。
- ISM 使用仍取决于国家/地区特定的频率、功率和占空比限制。
- 无法直接通过开放的 LoRa/APRS 卫星路径进行 PSTN/Jio/Airtel 通话。这需要互联网 VoIP 网桥和合法的 SIP/PSTN 中继线。
参见 [docs/regulatory-compliance.md](docs/regulatory-compliance.md) 和 [docs/ntn-comparison.md](docs/ntn-comparison.md)。
## 快速开始
```
python -m venv .venv
.venv\Scripts\activate # Windows PowerShell users can also use Activate.ps1
pip install -r requirements.txt
pip install -r backend/requirements.txt
# 运行测试
python -m pytest -q
# 运行离线模拟
python demo.py
python simulation/link_budget.py
python scripts/fetch_tle.py --all-openorbitlink --include-fossa
# 启动后端 API
python -m uvicorn backend.main:app --reload --port 8000
```
## 关键参考
- FCC 业余禁止传输:https://www.ecfr.gov/current/title-47/chapter-I/subchapter-D/part-97/subpart-B/section-97.113
- Android SatelliteManager API:https://developer.android.com/reference/android/telephony/satellite/SatelliteManager
- Google Pixel 卫星 SOS 可用性:https://support.google.com/pixelphone/answer/15254448
- T-Mobile T-Satellite 服务限制:https://www.t-mobile.com/coverage/satellite-phone-service
- TRAI 卫星频谱建议,2026-05-15:https://trai.gov.in/notifications/press-release/trai-releases-recommendations-terms-and-conditions-assignment-spectrum
- CelesTrak GP/TLE 查询格式:https://celestrak.org/NORAD/documentation/gp-data-formats.php
- TinyGS 编程 API 说明:https://github.com/tinygs/tinyGS/wiki/Programmatic-API
- FOSSA LoRa/ISM 常见问题解答:https://fossa.systems/frequently-asked-questions/
- Lacuna Space LoneWhisper®:https://lacuna.space/
- Semtech LR2022 第 4 代:https://www.semtech.com/
- Iridium NTN Direct:https://www.iridium.com/
## 许可证
GPLv3。这是研究软件;发射前请检查当地法律。