云卓 G20 遥控器 Android 开发实录:从 USB 设备到读取摇杆/按键通道值
设备:云卓(Skydroid)G20 无人机地面遥控器
目标:在电脑上读取插上 USB 的"遥控器"的摇杆(遥杆)与按键值,并写一个 App 测试
前言
最近拿到一个"遥控器",插上电脑 USB 后 lsusb 显示的是 18d1:4e11 Google Inc. Nexus One------这不是手柄,而是 Android 设备的 adb 接口 。顺着这条线,我把这个设备从头摸了一遍:它其实是 云卓 G20 无人机遥控器(地面端),内置 Android 13 系统,本身就可以装 App、跑 SDK。
本文记录完整的探索过程与踩坑经验,包括:
- 如何确认设备身份;
- 为什么摇杆/按键没有注册成 Linux 输入设备;
- 如何找到云卓私有服务与串口链路;
- 如何用官方 RCSDK 读取 16 通道实时值(摇杆/按键/开关/波轮);
- 接收机网口(图传)在 SDK 中的对应 API。
一、插上 USB 的"遥控器"到底是什么?
先用 lsusb 看设备:
bash
$ lsusb
Bus 003 Device 009: ID 18d1:4e11 Google Inc. Nexus One
18d1:4e11 是经典 Android adb 设备的 VID/PID(Google 注册表里叫 Nexus One,实际是 adb 接口通用的名字)。所以它不是普通 HID 手柄,而是一台安卓设备。
再确认 adb:
bash
$ adb devices -l
[设备序列号] device usb:3-4 product:bengal_515 model:Bengal_for_arm64 device:bengal_515
二、先看说明书:这是云卓 G20
对应的是用户提供的手册《云卓-G20 使用说明书 V1.0》,提取 PDF 文本后确认:
| 项目 | 内容 |
|---|---|
| 产品 | 云卓 G20 遥控器(无人机地面端) |
| 系统 | Android 13(64 位),高通 6nm 8 核(Bengal) |
| 屏幕 | 7 寸 1920x1200 |
| 通道数 | 16 通道 |
| 输入 | 左摇杆 X1/Y1、右摇杆 X2/Y2、L1/L2/R1/R2/H/B1/B2、三档开关、波轮(云台俯仰)、SPAY 播撒键、CAMERA 拍照键 |
| 接口 | TYPE-C、USB(升级口)、SIM 卡槽 |
| 接收机 | GR01(网口×1、PWM02=通道911、SBUS+数传)或 G12 RX(网口×1、PWM01=通道910、SBUS+数传) |
关键点:手册第 8 页"设备助手→舵量查看"就是看实时通道值的,说明通道数据一定可以通过某种接口拿到。
三、确认设备身份(adb)
bash
adb -s [设备序列号] shell getprop | grep -iE 'ro.product|ro.build.version'
输出确认:
[ro.build.version.release]: [13]
[ro.build.version.sdk]: [33]
[ro.product.cpu.abi]: [arm64-v8a]
[ro.product.model]: [Bengal for arm64]
与手册完全一致:Android 13 / API 33 / arm64。
四、找摇杆数据:为什么 getevent 找不到?
一般游戏手柄/遥控器在 Android 里是 /dev/input/eventX 输入设备,可以直接 getevent。但这里:
bash
$ adb shell getevent -pl
add device 1: /dev/input/event4 "bengal-idp-snd-card Button Jack" (音量/媒体键)
add device 2: /dev/input/event3 "bengal-idp-snd-card Headset Jack" (耳机)
add device 3: /dev/input/event2 "SITRONIX" (触摸屏)
add device 4: /dev/input/event0 "qpnp_pon" (电源键)
add device 5: /dev/input/event1 "gpio-keys" (返回键)
没有摇杆、没有 L1/R1 等按键! 说明摇杆/按键由云卓私有链路处理,不是标准 input 子系统。
继续排查,发现了云卓的系统应用和服务:
bash
$ adb shell ps -A | grep -i sky
system com.skydroid.rc_daemon # 遥控器守护进程
system com.skydroid.rcservice # 遥控器服务 (RCService)
$ adb shell getprop | grep -i mcu
[vendor.mcu.rc_service]: [true]
五、逆向系统 App:找到 rcsdk 与串口线索
把三个云卓 APK 拉回电脑分析:
bash
adb pull /system/app/rcserivce/rcserivce.apk # RCService (210KB)
adb pull /system/app/rcdaemon/rcdaemon.apk # 守护进程 (62KB)
adb pull /data/app/.../com.skydroid.devicetool/base.apk # 设备助手 (19MB)
用 strings/javap 分析后得到关键信息:
- 设备助手(devicetool / Htool)内置了完整的 SDK :
com.skydroid.rcsdk,里面有comm/SerialPort、common/button/ButtonHelper、common/airlink/*(NetworkConfig、GLinkSkyDevice、ReceiverOptions 等),还有libsky_serialport.so(JNI,实现Java_com_skydroid_rcsdk_comm_SerialPort_open)------经典 android-serialport-api 模式。 - G20 通道参数 :
assets/params_g20/default.json里是 16 通道的映射表,min/mid/max = 105/150/195(舵量范围,单位不是 us,而是这个私有协议的数值)。 - rcservice 的 assets 里还有 Artosyn 图传链路相关二进制(
daemon、data_stream,ARP8030 图传芯片 SDK)。
六、动态抓日志:确认串口 /dev/ttyHS2
启动"设备助手"后抓 logcat(这就是网上说的"看日志比猜靠谱"):
bash
adb logcat -c
adb shell am start -n com.skydroid.devicetool/com.skydroid.h12ptool.ui.activity.MainActivity
adb shell "logcat -d | grep -iE 'serial|ttyHS|rcsdk'"
结果:
D serial_port: Opening serial port /dev/ttyHS2 with flags 0x2
D serial_port: open() fd = 86
D serial_port: Configuring serial port
D RCSDK_JNI: ar8030_helper_socket version:31 agrc 5
D RCSDK_JNI: -p 50002
结论:SDK 通过高通 HSUART /dev/ttyHS2 与遥控器 MCU 通信,读取 16 通道舵量 。/dev/ttyHS1、/dev/ttyHS2 在本机权限是 777,普通 App 也能打开(SELinux 为 permissive)。
七、柳暗花明:官方 RCSDK 开源 Demo
正好官方源码在 Gitee:
- 组织:https://gitee.com/skydroid
- RCSDK-Demo :https://gitee.com/skydroid/rcsdk-demo(支持 H12/H12Pro/H16/H30/H20/G12/G20/G30/G16)
集成要点(README 原文语义):
text
1) 导入 rcsdk-v1.9.2.aar
2) 权限: <uses-permission android:name="android.permission.INTERNET"/>
3) 初始化: RCSDKManager.initSDK(this, SDKManagerCallBack)
4) 连接: RCSDKManager.connectToRC()
5) 读通道: KeyManager.get(RemoteControllerKey.KeyChannels, callback) // int[16]
注意(官方说明):
- 摇杆杆量无法主动上报,GET 请求一次返回一次,推荐 ≥100ms 轮询一次;
- 使用前务必停止设备助手/地面站,否则串口端口被占用导致连接失败。
八、最小测试 App(纯 Java,无 AndroidX)
为了快速验证,新建了最小工程 g20test(纯 Java + 官方 aar,不引入 AndroidX,离线也能构建):
核心代码 (MainActivity.java 片段):
java
RCSDKManager.INSTANCE.initSDK(this, new SDKManagerCallBack() {
@Override public void onRcConnected() { /* 已连接 */ }
@Override public void onRcConnectFail(SkyException e) {}
@Override public void onRcDisconnect() {}
});
RCSDKManager.INSTANCE.setMainThreadCallBack(true);
RCSDKManager.INSTANCE.connectToRC();
// 每 100ms 轮询一次(在 Handler 中)
KeyManager.INSTANCE.get(RemoteControllerKey.INSTANCE.getKeyChannels(),
new CompletionCallbackWith<int[]>() {
@Override public void onSuccess(int[] value) {
// value 就是 16 个通道实时值
Log.i("G20Test", "CH: " + Arrays.toString(value));
}
@Override public void onFailure(SkyException e) {}
});
编译注意:RCSDK 是 Kotlin 写的,纯 Java 工程需手动引入
kotlin-stdlib;本机 Gradle 缓存有kotlin-stdlib-1.9.20.jar,直接files()引用即可。
九、实测结果:16 通道实时值
安装运行后,logcat 实时输出(实测):
CH: [1500, 1500, 1528, 1050, 1950, 1050, 1050, 1050, 1050, 1050, 1050, 1050, 1050, 1500, 1050, 1050]
CH04 1050 -> 1238 (Δ188) [摇杆/波轮]
CH04 1238 -> 1500 (Δ262) [摇杆/波轮]
CH04 1500 -> 1950 (Δ293) [摇杆/波轮]
- 通道值范围:1050 ~ 1950,中心 1500 (与
params_g20/default.json的 105/150/195 缩放一致,即放大 10 倍); - 摇杆/波轮:连续变化(如 CH04 从 1050 平滑到 1950);
- 按键/开关:跳变(±500 级别,如 1050 ↔ 1950),三档开关是 1050/1500/1950 三态;
- 截图确认界面正常显示 16 通道 + 事件日志。
结论:用一个 App + 官方 SDK,即可在 G20 上(或通过 adb 转发的 logcat 在电脑上)实时看到所有摇杆/按键/开关状态。
十、接收机与网口(图传)在 SDK 中的对应
10.1 手册怎么看
| GR01 | G12 RX | |
|---|---|---|
| 网络 | 网口×1(R±/T±) | 网口×1(ETH0 TX±/RX±) |
| PWM | PWM0~2 = 通道 9~11 | PWM0~1 = 通道 9~10 |
| 飞控 | SBUS 输出 + 数传 RX/TX | SBUS + UART 数传 |
| 对频 | 长按对频按钮 | 短接 GND+KEY |
网口只用来接相机(图传),不接飞控:
- 云卓相机(如 C10Pro)插接收机网口 → 设备助手"视频查看"直接看;
- 第三方相机必须设网段 192.168.144.XXX,再用 RTSP 拉流;
- 云卓相机默认
192.168.144.108,网关192.168.144.11; - 遥控器开 USB 网络共享后,电脑连
192.168.43.1,Mission Planner 可走 TCP5760(数传),VLC 可拉相机码流(图传)。
10.2 SDK 里对应的类/接口(rcsdk AAR 反编译确认)
| SDK 成员 | 说明 |
|---|---|
common.airlink.GLinkSkyDevice |
天空端设备枚举:GR01/GR02/GR03/TR01/YL20 |
common.airlink.NetworkConfig |
ip / gateway / mask 三字段(网口/网络配置) |
common.airlink.ReceiverOptions |
baudRate + mode(接收机输出:SBUS/PPM/CBUS) |
common.airlink.UartBaudRate |
B9600/B57600/B115200/B921600 |
AirLinkKey.KeyUart0BaudRate |
接收机串口 0 波特率(支持 G12/G20/G30/G16) |
AirLinkKey.KeySignalQuality / KeyRawSignalQuality |
接收机信号质量(0-100%)及原始 JSON |
AirLinkKey.KeyG12GetChan 等 |
G12/G20 信道/频率/功率查询与设置 |
PipelineManager.createG12G20Pipeline() |
G12/G20 专用数传管道(UDP,需系统≥20250110) |
PipelineManager.createUDPPipeline(本地端口,"192.168.144.10",远端端口) |
自定义 UDP 管道 |
PayloadManager.getUDPPayload(PayloadType.C10PRO, 5000, "192.168.144.108", 5000) |
云台相机控制(网口/UDP链路) |
PayloadManager.getTCPPayload(PayloadType.C10, "192.168.144.108", 5000) |
C10 相机 TCP 控制 |
即:接收机网口 = 相机高清图传/控制链路,对应 PayloadManager + FPV 视频流;接收机串口/SBUS = 飞控控制链路,对应 PipelineManager 数传管道。
十一、经验与踩坑总结
- 别把"遥控器"当 HID :云卓 G20 是安卓设备,先
adb devices确认身份;摇杆/按键走私有 MCU 串口(实测/dev/ttyHS2),不是/dev/input。 - 官方优先:有官方 SDK(基于 KeyManager 的 SET/GET/ACTION/LISTEN 模型)就不要自己读串口;
- GET 轮询 :
KeyChannels一次只能取一次值,轮询间隔 ≥100ms; - 端口互斥 :设备助手/地面站不退出,SDK 连不上(启动前
am force-stop com.skydroid.devicetool); - 纯 Java 工程 + Kotlin SDK :记得手动带
kotlin-stdlib; - 构建加速 :Gradle 仓库加阿里云镜像(
maven.aliyun.com/repository/public|google|gradle-plugin); - 安卓 13/API 33 :本机 SDK 没有 android-32,用
compileSdk 33+android.suppressUnsupportedCompileSdk=33即可; - 日志先行 :启动官方 App 抓
serial_port/RCSDK_JNI日志,比纯猜快得多。
附录:常用命令
bash
# 设备
adb devices -l
adb -s [设备序列号] shell getprop | grep -i model
# 输入设备(确认摇杆不在里面)
adb -s [设备序列号] shell getevent -pl
# 停止设备助手(释放串口)
adb -s [设备序列号] shell am force-stop com.skydroid.devicetool
# 安装/启动测试 App
adb -s [设备序列号] install -r app/build/outputs/apk/debug/app-debug.apk
adb -s [设备序列号] shell am start -n com.demo.g20test/.MainActivity
adb -s [设备序列号] logcat -s G20Test:*
# 官方 Demo
git clone https://gitee.com/skydroid/rcsdk-demo.git
本文基于云卓 G20 遥控器 + RCSDK v1.9.2 实测整理,仅供学习交流。