← 返回目录

Jetwave RC-2000 遥控器应用 — 代码分析报告

项目路径: jetwave-rc-2000/
包名: com.szdbi.waverider
审查日期: 2026-05-25
审查视角: 嵌入式系统、AI 硬件链路、全栈/客户端、产品架构、代码审查(客观严谨,供问题定位)

非技术人员请先读: 交互架构与问题导读(含全流程图)
本文档偏技术细节;下图与口述稿在导读中更完整。


〇、整体交互架构(RC-2000)

这个 App 在系统里扮演什么角色?

遥控器是一块 Android 板子,里面的 waverider 应用相当于「驾驶舱屏幕 + 通信中转站」:

整体架构图(RC-2000:默认蓝牙)

flowchart TB
    subgraph 用户侧
        U[油门/档位/屏幕操作]
    end

    subgraph RC2000_App["RC-2000 App"]
        MA[MainActivity 主界面]
        HF[HomeFragment<br/>航行+记日志+故障弹窗]
        BS[BleService 蓝牙服务]
        DH[DataHandler 协议解析]
        UP[UploadSailingRecordUtil 上传]
    end

    subgraph 板子
        ECU[ECU]
        ESC[ESC 电机控制]
    end

    subgraph 云
        OSS[OSS 明细txt]
        WEB[后台 analysis/id]
    end

    U --> HF
    MA --> HF
    HF <-->|EventBus| DH
    HF -->|约1Hz写库| DB[(SQLite轨迹)]
    BS --> DH
    DH <-->|MAVLink| ECU
    ECU --> ESC
    HF --> UP
    DB --> UP
    UP --> OSS --> WEB

关键业务逻辑(四条线)

业务线 做什么 主要程序 出问题时的表现
连接 搜板子、连蓝牙 BleService、BluetoothUtil 断联、连上又掉
控制 油门 → ECU → 电机 CommandHelper、msg_throttleandgear 有油门无动力(失速 131072)
故障 心跳带 esc/ecu 码 → 弹窗+记库 HomeFragment、EscFaultMask 失速/过压/通信故障提示
上传 结束 → txt → OSS → 后台 UploadSailingRecordUtil 后台看不到日志或数据缺段

业务流程图 ①:从开机到连上板子

sequenceDiagram
    autonumber
    actor 用户
    participant Main as MainActivity
    participant Ble as BleService
    participant DH as DataHandler
    participant 板子 as ECU

    用户->>Main: 打开 App
    Main->>Ble: 启动蓝牙服务
    Ble->>板子: 扫描/连接 GATT
    板子-->>Ble: 连接成功
    Ble->>DH: 收到字节流
    DH->>DH: Parser 解析 MAVLink
    DH->>Main: EventBus「已连接」
    Main->>板子: 查询版本信息

    rect rgb(255, 230, 230)
    Note over Ble,DH: ⚠️【风险A】Parser 全局唯一<br/>连接抖动时可能解析错乱
    end

业务流程图 ②:航行中(与飞行日志字段一一对应)

flowchart TB
    T[Timer 每秒触发] --> R1[读油门 sysfs<br/>CommandHelper]
    R1 --> R2[读 ECU 心跳<br/>速度/电流/故障码]
    R2 --> R3[写入 SailingRecordItem<br/>含 escEventCode ecuEventCode]
    R3 --> R4[刷新仪表盘 UI]

    R2 --> F{故障码?}
    F -->|esc=131072| S[失速保护<br/>弹窗+埋点]
    F -->|esc=4194304| O[过压保护<br/>弹窗+埋点]
    F -->|ecu=1073741824| C[ESC通信故障<br/>弹窗+埋点]

    R1 --> OUT[发油门指令到 ECU]

    style S fill:#ffcdd2
    style O fill:#ffe0b2
    style C fill:#ffcdd2
    style R3 fill:#c8e6c9

问题逻辑说明(失速不可控):

  1. 板子 ESC 进入 失速保护 → 心跳里 escEventCode = 131072
  2. App 原样写入轨迹 → 上传 OSS → 就是我们分析的 JSON
  3. 若此时用户仍推油门(throttleLeverQuantity > 0)但 motorSpeed = 0 → 「有指令无输出」
  4. 根因可能在 ESC 固件保护不恢复,也可能 ECU 与 ESC 通信断(ecu=1073741824),App 只负责如实记录

业务流程图 ③:结束航行与上传

flowchart LR
    STOP[用户点停止] --> PACK[打包 SQLite 轨迹点]
    PACK --> TXT[生成 txt 文件<br/>SN_1_用户ID_时间.txt]
    TXT --> OSS[上传阿里云 OSS]
    OSS --> API[调 TrajectoryApi 登记]
    API --> ID[后台生成日志 id]
    ID --> URL[可视化页面]

    PACK -.->|⚠️风险B isUpload 非线程安全| X1[可能重复上传]
    TXT -.->|⚠️风险C 成功即删本地文件| X2[失败难补传]

问题环节与程序对照表(RC-2000)

环节 可能问题 涉及程序/配置 严重度
蓝牙连接 断联、重连慢 BleService、BluetoothUtil 高
协议解析 多线程串包、状态错乱 DataHandler 静态 Parser 高
失速保护 131072 长时间不恢复 板子 ESC 固件 + HomeFragment 只弹窗不控恢复 高
故障判断 多个故障只显示一个 HomeFragment 用 == 比较 中
轨迹记录 漏秒、掉点 HomeFragment Timer 中
上传 卡死、重复、丢文件 UploadSailingRecordUtil 中
性能 卡顿、通信延迟 ThreadPoolUtil 最大200线程 中
异常 崩溃后状态不明 MyApplication 吞掉未捕获异常 中

0. 执行摘要

维度 评级 一句话
整体结构 B- 典型 Android 分层 + EventBus,模块清晰但 HomeFragment 过重
通信设计 B MAVLink + BLE/串口 AT 三通道,协议定义完整
交互逻辑 B- 航行状态机分散在 Fragment/Timer/EventBus
运动控制 B sysfs 油门 + MAVLink 下发,与 ECU 协同明确
日志上传 B OSS + API 双写,字段与后台轨迹一致
内存管理 C+ largeHeap 掩盖问题,轨迹 SQLite 无上限策略可见
线程安全 C 全局 Parser 静态、上传标志非原子、线程池过大
风险漏洞 C 明文 HTTP、异常吞没、密钥硬编码

与近一月飞行日志问题的直接关联: 本仓库 EscFaultMask / EcuFaultMask 与 OSS 轨迹 JSON 字段 完全一致(escEventCode=131072 失速、4194304 过压、ecuEventCode=1073741824 ESC 通信故障),问题定位应 App 展示/埋点 + ECU/ESC 固件 + 通信链路 联合排查。


1. 项目概览

项 内容
类型 Android 系统应用(sharedUserId=android.uid.system)
工程名 waverider-yoyo-rc2000
Java 源文件 307
Activity 23
默认通信 板载蓝牙 BLUETOOTH(MyApplication)
API 环境 生产 https://api.jetwave.ai
版本 versionName 0099 / versionCode 99

无 README / 架构文档,知识依赖代码逆向。


2. 整体结构

2.1 目录与分层

app/src/main/java/
├── com.szdbi.waverider/     # 业务层 (~250 文件)
│   ├── activity/            # 23 个页面
│   ├── fragments/           # 主流程 + BLE/BMS/OTA/校准
│   ├── service/             # BleService, SerialPortService, BmsBleService
│   ├── network/             # Retrofit API + DTO/VO
│   ├── helper/              # 业务辅助、OTA、油门
│   ├── utils/               # 串口、BLE、云、上传
│   ├── manager/             # 日志、Activity 栈
│   ├── task/                # 轨迹统计、安全区等
│   ├── dao/entity/          # OrmLite 持久化
│   ├── event/               # EventBus 消息 (~39)
│   └── tracking/            # 阿里云 SLS
└── com.mavlink/             # 协议栈 (~56 文件,生成代码)

2.2 架构模式

flowchart TB
    UI[Activity / Fragment / 自定义 View]
    EB[EventBus]
    SVC[Service: BLE / SerialPort / BMS]
    DH[DataHandler + Parser]
    DEV[ECU / BMS / ESC 固件]
    CLOUD[api.jetwave.ai + OSS + SLS]

    UI --> EB
    EB --> UI
    SVC --> DH
    DH --> DEV
    DH --> EB
    UI --> CLOUD

特征: 事件驱动、Service 长连接、单例 Util/Manager、无清晰 Domain 层(业务逻辑大量落在 HomeFragment)。

2.3 与 RC-3000 结构差异

对比项 RC-2000 RC-3000
Java 文件数 307 361
Activity 23 26
默认通信 蓝牙 星闪
API 生产域名 测试域名
对讲/AI/NFC 无 有 Intercom、SSE、NFC

结论: 两项目为 同一代码基线的硬件变种,核心通信/协议/上传链路同源。


3. 通信设计

3.1 三通道模型

DataCommunicationMethodEnum:

模式 实现 物理路径
BLUETOOTH BleService → BluetoothUtil 板载 BLE GATT
WL_SERIAL_PORT SerialPortService + AT /dev/ttyS1 @ 921600
STAR_DODGES 同上,星闪 AT 集 串口模块

关键文件:

3.2 MAVLink 协议层

3.3 故障码定义(与飞行日志对齐)

ESC — com/mavlink/enums/EscFaultMask.java:

常量 值 hex 日志中命中
ESC_FAULT_STALL_PROTECTION 131072 0x20000 是(失速不可控主因)
ESC_FAULT_OVERVOLTAGE_PROTECTION 4194304 0x400000 是
ESC_FAULT_PROPELLER_OUT_OF_WATER 33554432 0x2000000 日志中高频待命名

ECU — com/mavlink/enums/EcuFaultMask.java:

常量 值 hex 日志中命中
FAULT_ESC_COMMUNICATION 1073741824 0x40000000 是(断联代理)
FAULT_IMU_COMMUNICATION 536870912 0x20000000 —
FAULT_BATTERY_COMMUNICATION 2147483648 — —

重要: 日志分析中的「ECU 断联代理」已在代码中命名为 FAULT_ESC_COMMUNICATION(ESC 通信故障),非独立「断联」枚举,UI 层在 HomeFragment 映射为 DataTrackingConstant.FAULT_ESC_COMMUNICATION。

3.4 通信设计评审意见

优点 风险
MAVLink 代码生成,与固件契约一致 Parser 为 static 单例,多线程读串口/BLE 时存在竞态
AT + GATT 双路径覆盖增强蓝牙/星闪 SerialPortService 状态机复杂,断线重连边界难测
组件 ID 区分 ECU/BMS/ESC 无应用层「连接质量」指标,只有故障码事后记录

4. 交互逻辑

4.1 主流程

MainActivity (LAUNCHER + HOME)
  → bind YoYo IDmInterface(硬件 SN/IMEI)
  → start BleService 或 SerialPortService
  → ViewPager2: HomeFragment / Stop / Complete
  → Timer 驱动航行采样、上传触发

4.2 故障处理 UX(HomeFragment)

产品侧注意: 若固件一次上报多个 fault bit,当前 == 判断 只会匹配单一码,可能漏报或覆盖。

4.3 交互逻辑风险

  1. God Fragment: HomeFragment 集成航行、故障、GPS、安全区、商业模式,维护成本高。
  2. EventBus 粘性事件: 页面重建可能重复消费。
  3. Timer 与线程混用: 航行定时器在子线程 run() 内再 runOnUiThread,异常路径需人工梳理。

5. 运动控制模块

5.1 数据流

sysfs 霍尔传感器 (CommandHelper 轮询)
  → 油门杆量 throttleLeverQuantity
  → msg_throttleandgear MAVLink 下发 ECU
  → ECU 控制 ESC/电机
  ← msg_ecuheatbeatdata 心跳(速度、故障、电流)

5.2 关键模块

文件 职责
helper/CommandHelper.java cat /sys/class/yyadc/hall_sensor,定时采样
helper/throttle/AdvancedThrottleUtil.java 油门曲线
ThrottleCurveView.java / ThrottleCurveFragment.java UI 配置
mavlink/JetWave/msg_throttleandgear.java 协议下发
mavlink/JetWave/msg_setmaxspeed.java 限速
utils/GearListener.java 档位物理键
config/DefaultConfig.java 默认限速区间

5.3 与日志字段对应

上传轨迹 JSON 中 throttleLeverQuantity、gear、mode、motorSpeed、hourSpeed 均在 HomeFragment 定时写入 SailingRecordItem,与 OSS 分析字段一致。

5.4 运动控制风险

风险 说明
油门 sysfs 与 MAVLink 双源 显示用 sysfs,控制用 MAVLink,不同步时会出现「有油门无输出」
失速后 UI 可能仍显示油门 日志已见 stall_with_throttle_no_motor,需在固件或 App 侧限制输出
校准值开机写入 RC-3000 有角度校准开机写 sysfs;RC-2000 无 此段逻辑

6. 日志上传模块

6.1 三层日志

层级 实现 路径
本地 logcat SystemLogManager / LogFileManager 单文件 10MB,总 100MB
埋点 SLS DataTrackingManager + AlibabaCloudLogUtil OrmLite 队列
航行轨迹 UploadSailingRecordUtil SQLite → JSON txt → OSS → API

6.2 轨迹上传链路(与 surf_motion_trajectory_history 对应)

// UploadSailingRecordUtil.java
String fileName = String.format("%s_1_%s_%s.txt",
    remoteSn, userId, startTime);  // 与 OSS object_key 一致
// 内容:SailingRecordDTO JSON,含 trajectoryList
// 每项含 escEventCode, ecuEventCode, bmsEventCode

写入频率: HomeFragment 航行 Timer 约 1Hz 插入 SailingRecordItem(与日志 3384 点/3384 秒吻合)。

6.3 上传模块风险

ID 风险 位置
L1 private static boolean isUpload 非 volatile,并发上传可重入 UploadSailingRecordUtil
L2 上传在 new Thread() 而非统一线程池,生命周期无绑定 同上
L3 上传失败本地文件已 delete,无断点续传 success 回调
L4 escEventCode 原样落库,无位掩码展开,后台分析需按位与 SailingRecordItem
L5 故障埋点与轨迹分离,关联靠时间戳手工对齐 SLS vs OSS

7. 内存管理

机制 配置/实现 评价
Manifest largeHeap=true 缓解 OOM,不解决泄漏
Gradle dex javaMaxHeapSize 2g 构建期
日志文件 10MB × N,上限 100MB 可控
地图 Mapbox 10.16 大头,Fragment 需关注 onDestroy
轨迹 DB OrmLite 全量 item 列表读入内存上传 长航线内存 spike
集合 CopyOnWriteArrayList、ConcurrentLinkedQueue 读多写少场景合理

未见: Native 内存池、对象复用、轨迹采样环形缓冲区。


8. 线程安全

8.1 线程模型

组件 方式
ThreadPoolUtil 核心 100,最大 200,队列 100000
串口读 SerialPortUtil 独立 Thread
BLE BluetoothUtil Timer + 连接线程
上传/埋点 new Thread()
UI runOnUiThread / RxJava main

8.2 关键问题

// DataHandler.java — 全局共享 Parser
private final static Parser parser = new Parser();
public static void handler(..., byte[] bytes) {
    for (byte aByte : bytes) {
        MAVLinkPacket mavLinkPacket = parser.mavlink_parse_char(aByte);

风险: BLE 与串口若并行调用 handler(切换通信模式边界),Parser 内部状态 非线程安全。

// ThreadPoolUtil — 嵌入式设备上过大
new ThreadPoolExecutor(100, 200, ...);

风险: 线程爆炸、调度延迟、加剧通信丢包。

// UploadSailingRecordUtil
private static boolean isUpload = false;

风险: 竞态导致重复上传或永久阻塞。

8.3 线程安全建议优先级

  1. Parser 改为 ThreadLocal 或每连接实例
  2. 缩小线程池(建议 core≤4,max≤8)
  3. isUpload 改为 AtomicBoolean
  4. DataHandler / DeviceInfoConstant 静态可变字段加同步或收敛到单线程 Looper

9. 风险与漏洞

等级 类别 描述 位置
高 稳定性 默认异常处理器仅 print,不退出/不上报完整栈 MyApplication.MyUncaughtExceptionHandler
高 并发 MAVLink Parser 静态共享 DataHandler
中 安全 cleartextTrafficPermitted=true network_security_config.xml
中 安全 Bugly AppId 硬编码 MyApplication
中 逻辑 故障码 == 单值比较,不支持多位同时置位 HomeFragment
中 运维 生产 API 固定,无 flavor 隔离文档 RequestManager
低 依赖 OrmLite/Fastjson 等老旧库 build.gradle

10. 与飞行日志故障数据的映射

日志现象 代码侧对应 建议排查
escEventCode=131072 主导 EscFaultMask.ESC_FAULT_STALL_PROTECTION ESC 固件失速保护阈值与恢复
escEventCode=4194304 ESC_FAULT_OVERVOLTAGE_PROTECTION 母线电压采样、电池内阻
ecuEventCode=1073741824 EcuFaultMask.FAULT_ESC_COMMUNICATION ECU-ESC 通信超时/CAN
油门有、电机 0 throttleLeverQuantity + motorSpeed 分源 CommandHelper vs 心跳
文件命名 SN_1_userId_time.txt UploadSailingRecordUtil 与 CSV object_key 一致

后台可视化: https://manager.jetwave.cn/motion/trajectoryHistory/analysis/{id}


11. 快速优化改进建议(RC-2000)

原则:先止血(连接/解析/故障判断)→ 再提质(上传/架构)→ 并行固件验证。下列方案按「改动量小、见效快」排序。

11.1 改进路线图(建议 4 周)

gantt
    title RC-2000 快速改进节奏
    dateFormat  YYYY-MM-DD
    section 第1周止血
    Parser单线程化           :a1, 2026-05-26, 3d
    故障码改位运算           :a2, 2026-05-26, 2d
    上传AtomicBoolean       :a3, 2026-05-29, 2d
    section 第2周可观测
    连接质量埋点             :b1, 2026-06-02, 4d
    失速会话现场日志包       :b2, 2026-06-02, 3d
    section 第3-4周结构
    通信层抽离+线程池缩容    :c1, 2026-06-09, 7d
    HomeFragment拆分一期     :c2, 2026-06-16, 7d

11.2 快速赢(1~3 天可落地,建议立即做)

# 改进项 怎么做 改哪里 预期效果
Q1 MAVLink 解析单线程化 所有 DataHandler.handler() 投递到 单线程 SerialExecutor;禁止 BLE/串口回调线程直接解析 DataHandler.java 消除串包/错乱,降低「假断联」
Q2 故障码改为位判断 if ((escEvent & EscFaultMask.ESC_FAULT_STALL_PROTECTION) != 0);ECU 同理;弹窗可显示 主故障+次故障 HomeFragment.java 复合故障不漏报,与日志分析一致
Q3 上传互斥改原子变量 AtomicBoolean + compareAndSet;失败 不删 本地 txt,标记 upload_pending UploadSailingRecordUtil.java 避免重复上传/永久卡死/丢数据
Q4 线程池缩容 core=2, max=4, queue=500;禁止 100~200 线程 ThreadPoolUtil.java 减轻调度延迟,通信更稳
Q5 连接切换时强制复位 切换蓝牙/星闪前:DataHandler.disconnect() + 停旧 Service + 500ms 后再启新 Service CommunicationSettingsActivity、各 Service 减少僵尸连接

Q2 示例(便于研发直接改):

// 原:if (escEvent == EscFaultMask.ESC_FAULT_STALL_PROTECTION)
// 改:
if ((escEvent & EscFaultMask.ESC_FAULT_STALL_PROTECTION) != 0) {
    handleStall(escEvent);
}
if ((ecuEvent & EcuFaultMask.FAULT_ESC_COMMUNICATION) != 0) {
    handleEscCommFault(ecuEvent);
}

11.3 短期改进(1~2 周,针对「失速/断联」)

# 改进项 方案说明 负责
S1 连接质量可观测 每秒记:rssi、距上次心跳间隔 heartbeat_gap_ms、重连次数;写入 SLS 与轨迹扩展字段 App
S2 失速后会话标记 轨迹内首次出现 131072 时打标 stall_session=true;上传后后台可直接筛 App + 后台
S3 「有油门无电机」规则 当 throttle>0 && motorSpeed==0 && esc含131072 连续 N 秒 → 弹 明确文案 + 引导松油门/重启 App 产品
S4 固件联合回归 对 Top 设备 5G002872、6A000193 做:蓝牙直连 vs 星闪(若可切换)对比 + CAN 抓包 固件+硬件
S5 异常不再静默 MyUncaughtExceptionHandler 上报 Bugly 完整栈,通信线程异常触发 安全降级(停发油门) App

11.4 中期架构改进(2~4 周)

# 改进项 建议方案
M1 拆分 HomeFragment SailingController(Timer/记库)、FaultPresenter(故障 UI)、GaugeBinder(仪表)
M2 通信层接口化 ILinkChannel:BleChannel / SerialChannel;DataHandler 只依赖接口
M3 轨迹写入环形缓冲 内存固定容量,上传时批量落库,避免长航线 OOM
M4 Build Flavor prod → api.jetwave.ai;staging → surfdev;避免环境混用
M5 上传断点续传 OSS 成功后删文件;失败保留并按 sailing_record_id 重试

11.5 固件 / 硬件侧(与 App 并行,不可省)

问题现象 建议固件动作 验证标准
131072 占会话 80%+ 审查失速触发条件、恢复条件、是否需用户松油门才恢复 保护后 10s 内可恢复驱动,或明确不可恢复原因
1073741824 与 stall 同现 查 ECU↔ESC 超时、线缆、干扰;蓝牙直连对比 通信故障率下降 50%+
4194304 仅 6 条 母线电压滤波、再生制动策略 大电流工况不频繁误触发

11.6 验证清单(改完必测)

场景 步骤 通过标准
蓝牙连接 开机 → 连板 → 航行 10min 无 Parser 错乱;心跳连续
失速复现 用问题 SN 或模拟故障码 弹窗正确;轨迹 esc=131072;位判断可叠加
上传 结束航行 → 断网 → 恢复网络 不丢 txt;不重复上传;后台 id 可查
切换通信 设置里切换模式(若支持) 旧连接释放;新连接可用
压力 连续航行 30min + 地图/对讲关 无 ANR;内存不持续增长

11.7 RC-2000 专属注意


12. 关键文件索引

模块 路径
Application app/.../MyApplication.java
主界面 app/.../activity/MainActivity.java
航行与采样 app/.../fragments/HomeFragment.java
MAVLink 分发 app/.../service/DataHandler.java
故障枚举 ESC app/.../mavlink/enums/EscFaultMask.java
故障枚举 ECU app/.../mavlink/enums/EcuFaultMask.java
轨迹上传 app/.../utils/UploadSailingRecordUtil.java
轨迹实体 app/.../entity/sql/SailingRecordItem.java
线程池 app/.../utils/ThreadPoolUtil.java
API app/.../network/RequestManager.java

本报告基于静态代码审查,未执行运行时插桩;结论需结合 RC2000 实机日志与固件版本验证。