news 2026/10/13 13:29:45

BalanceBot 安卓遥控 App 深度解析:轮腿机器人的摇杆遥控、MJPG 图传与蓝牙配网实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
BalanceBot 安卓遥控 App 深度解析:轮腿机器人的摇杆遥控、MJPG 图传与蓝牙配网实现
  • 机器人
  • 嵌入式
  • 硬件开发
  • 移动开发

【免费下载链接】foc-wheel-legged-robot

Open source materials for a novel structured legged robot, including mechanical design, electronic design, algorithm simulation, and software development. | 一个新型结构的轮腿机器人开源资料,包含机械设计、电子设计、算法仿真、软件开发等材料

项目地址:https://gitcode.com/gh_mirrors/fo/foc-wheel-legged-robot
点击查看免费下载

本文基于 foc-wheel-legged-robot 仓库中android/子模块的官方文档(README_en.md),结合 MainActivity.java、CtrlView.java、ctrl-proxy.py 等源码,完整还原这套 Android 遥控应用的显示方案、图传连接时序与蓝牙配网协议。读完本文,你将理解如何仅用一个重写的 WebView 同时承载视频显示与触摸遥控,以及如何通过 BLE 文本协议让图传系统自动入网,掌握一套可直接借鉴的"App + Linux 图传 + 嵌入式主控"三级无线链路设计。

一、模块定位与三大功能

android/是轮腿机器人项目的子模块,基于 Android Studio 开发(工程名BalanceBot,applicationId 为com.skythinker.balancebot),提供三项核心功能:

  1. 读取用户触摸输入,向机器人发送遥控指令;
  2. 拉取图传系统(linux-fpv/ 模块)的视频流并显示在界面上;
  3. 配合图传系统进行蓝牙配网(Bluetooth pairing),使图传系统自动接入指定 WiFi。

项目同时提供了预编译安装包 balancebot.apk,可直接安装使用;如需二次开发,仓库保留了完整的 Gradle 工程(settings.gradle、build.gradle)。

二、显示方案:重写 WebView 实现"视频 + 控件"双层叠加

文档指出:由于图传系统输出的 MJPG 视频流可以直接在各种浏览器中访问,因此 App 直接利用一个 WebView 作为主显示视图,仅需少量 HTML + JavaScript 代码即可显示视频流;同时重写 WebView 的onDraw方法,在 HTML 页面上方叠加绘制摇杆、滑块等控件,并通过onTouchEvent方法监听用户触摸输入。

2.1 视频流页面 streamview.html

视频流页面位于 assets/streamview.html,其关键实现:

  • 通过setServerIP(ip)函数把视频源切换为http://<ip>:8090/camera.mjpg(即图传系统上 ffserver 的 MJPG 推流地址,与 linux-fpv/README_en.md 中camera.mjpg端点一致);
  • 使用<img>标签直接承载 MJPG 流,并在onerror时回退到本地 transparent.png 透明图,用于占位;
  • 每 3 秒定时刷新一次图片地址,并通过 WebView JS 接口webview.jsCallback("setVideoState", "true"/"false")向 Native 层反馈视频是否连通;
  • 页面内以ASPECT_RATIO = 4/3自适应缩放视频区域。

对应的 Native 侧代码在 CtrlView.java 中:构造函数里启用 JavaScript、加载本地页面并注册交互接口(L62-L64):

getSettings().setJavaScriptEnabled(true); //启用JavaScript loadUrl("file:///android_asset/streamview.html"); //加载本地HTML页面 addJavascriptInterface(this, "webview"); //创建WebView交互接口

jsCallback方法(L67-L76)收到setVideoState消息后更新videoConnected标志并invalidate()重绘,这就是界面上"视频状态灯"的数据来源。当配网拿到图传 IP 后,setServerIP方法(L226-L234)会执行loadUrl("javascript:setServerIP('<ip>')")反向调用 HTML 函数完成视频源切换。

2.2 叠加控件的绘制与触摸

CtrlView继承自WebView,onDraw先调用super.onDraw(canvas)绘制 WebView 内容,再依次叠加绘制(L80-L125):

  • 状态标识 + 配置按钮:左上角圆角矩形内显示蓝牙/视频连接状态图标(bt_on.png / video_off.png 等);
  • 摇杆:加载 rocker.png 底图,按rockerValueX/Y偏移绘制摇杆中心点;
  • 滑块:横向圆角轨道 + 圆形滑块,用于调节腿部长度;
  • 左下角文字提示:显示"正在扫描设备…"、"WiFi连接成功…"等状态文字。

setSize方法(L237-L250)根据屏幕宽高计算各控件矩形区域:摇杆位于右下角,滑块位于其上方,配置按钮固定在左上角 (100, 0)-(200, 100)。

onTouchEvent(L128-L223)实现了一套轻量级命中检测:

  • 按下时判断触点落在摇杆圆内、滑条内还是配置按钮内,分别置位rockerDown/sliderDown/configDown;
  • 拖动摇杆时做了越界收缩(把落点拉回圆内)并归一化到 -1.0~1.0;同时存在死区逻辑——当|rockerValueY/rockerValueX| < 0.4时把纵向分量清零(或横向清零),保证摇杆在"纯前进"或"纯转向"两个轴向上输出更干净;
  • 摇杆抬起即归零复位,滑块则保持松手位置;
  • 配置按钮在按下区域内侧松开才触发callback.onUserInput("click", "config"),交回MainActivity弹出操作菜单。

这套"HTML 画视频 + Canvas 画控件 + 统一 onTouchEvent 分发"的做法,避免了为视频帧做 Java 层解码,是文档所述显示方案的精髓。

三、图传连接方案:视频流 + UDP 遥控双链路

文档提示:图传系统的通信链路细节请先参阅 linux-fpv/README_en.md 中的 Connection Scheme 一节。该方案是"即插即用"的双场景设计:

  • 不使用图传:App 直接通过蓝牙与主控模块(ESP32,蓝牙名BalanceBot)收发遥控数据;
  • 使用图传:图传系统(NanoPI,蓝牙名NanoPI)一方面与 App 通过局域网交互,另一方面通过蓝牙连接主控模块,把 App 发来的遥控数据转发给主控,同时把视频流推给 App。此时 App 与机器人无需处于蓝牙范围内,只需同在一个局域网即可。

注:图中"上位机"表示本 App,"运动控制模块"表示机器人主控模块。

3.1 视频链路:App 直接 HTTP 拉流

视频不需要经过蓝牙或 UDP,App 中的 WebView 直接向http://<NanoPI IP>:8090/camera.mjpg发起 HTTP 请求获取 MJPG 流(对应 streamview.html 中setServerIP的实现)。推流端由 NanoPI 上的mjpg-ffserver服务提供(见 linux-fpv/scripts/ffserver.conf)。

3.2 UDP 遥控链路:10Hz 的 4 字节遥控帧

遥控数据走 UDP。App 侧由 WifiClient.java 封装:本地绑定端口15000,默认目标端口5000,内部用阻塞式DatagramSocket.receive线程轮询接收(L15-L17、L28-L46)。

MainActivity中创建一个独立发送线程(MainActivity.java L225-L256),以 100ms 周期(10Hz)构造遥控帧:

final byte BT_FRAME_HEADER = (byte) 0xAA; //遥控数据帧头 byte[] btData = new byte[]{ BT_FRAME_HEADER, (byte) ((ctrlView.rockerValueX + 1) * 100), (byte) ((ctrlView.rockerValueY + 1) * 100), (byte) ((ctrlView.sliderValue + 1) * 100) }; if(btCtrl.isConnected) { btCtrl.sendBytes(btData); //蓝牙模式:直发主控模块 } else { wifiClient.sendBytes(btData); //图传模式:发往NanoPI的UDP服务器 }

帧格式与主控端解析完全对应。ESP32 侧 main.cpp 的蓝牙写特征回调(L786-L795)按如下规则解码:

字节含义主控端解码(main.cpp)
data[0]帧头0xAA长度必须为 4,否则丢弃
data[1]摇杆 X(转向)target.yawSpeedCmd = -(data[1]/100.0f - 1) * 1.5f
data[2]摇杆 Y(前进)target.speedCmd = (data[2]/100.0f - 1) * 1.0f
data[3]滑块(腿长)target.legLength = (data[3]/200.0f) * 0.02f + 0.07f(约 0.07~0.09 m)

另外0xAB单字节帧表示"触发起立",App 在配置菜单中选择了"发送站立指令"后,会根据当前模式经 UDP 或蓝牙发出0xAB(MainActivity.java L56-L62),主控端则创建Ctrl_StandupPrepareTask任务执行站立流程(main.cpp L792-L795)。

3.3 NanoPI 侧的 UDP↔蓝牙桥

在图传模式下,UDP 数据并不直接到达 ESP32,而是先到达 NanoPI 上运行的 ctrl-proxy.py。该脚本:

  • 绑定0.0.0.0:5000创建 UDP 服务器(L27-L33);
  • 收到 App 数据后进入"遥控数据转发模式"(is_proxy_mode = True),并顺带service mjpg-ffserver start启动视频推流(L112-L115);
  • 通过 bleak 扫描并连接名为BalanceBot的下位机(L141-L160),把 UDP 队列里的帧写入 ESP32 的 TX 特征(4c9a0002-...),并通过start_notify订阅其 RX 特征(4c9a0003-...)把下行数据经 UDP 转发回 App(L124-L127);
  • 超过 5 秒未收到 UDP 消息则退出转发模式并停止推流服务(L118-L121),实现"人不在、视频关"的省电逻辑。

因此,图传模式下整条链路为:App(UDP:15000) → NanoPI(UDP:5000) → BLE → ESP32;视频链路为App(WebView/HTTP) ← ffserver:8090 ← ffmpeg ← ov5640 摄像头。

四、蓝牙配网方案:BLE 文本协议驱动图传入网

文档定义:蓝牙配网指本程序先通过蓝牙直接连接到图传系统,向其发送网络信息,使其连接到指定的 WiFi 网络,此后即可通过网络连接到图传系统,整个过程自动完成。

4.1 双 GATT 客户端与两组 UUID

MainActivity中实际创建了两个BLE 客户端实例(Bluetooth.java 封装标准 GATT 客户端):

实例目标设备蓝牙名Service UUID写特征(App→设备)Notify 特征(设备→App)
btCtrlESP32 主控BalanceBot4c9a0001-fb2e-432e-92ec-c6f5316b46894c9a0002-...4c9a0003-...
btUpperNanoPI 图传NanoPI4c9a0011-fb2e-432e-92ec-c6f5316b46894c9a0013-...4c9a0012-...

这三组 UUID 与固件端定义一一对应:ESP32 侧见 main.cpp L25-L28 的UUID_SERVICE/UUID_RX_CHARA/UUID_TX_CHARA与BLE_NAME "BalanceBot";NanoPI 侧见 ctrl-proxy.py L21-L24(bless库创建的 GATT Server,notify 特征4c9a0012、write 特征4c9a0013)。

Bluetooth.java的实现细节值得借鉴:

  • 连接成功后调用setPreferredPhy(PHY_LE_2M, ...)协商 2M PHY(L64),降低遥控链路时延;
  • 服务发现后向00002902-...(CCCD)写入ENABLE_NOTIFICATION_VALUE开启订阅(L94-L97);
  • 首次扫描到设备后把 MAC 地址存入SharedPreferences(key 为"MAC " + 设备名,L133-L135),下次connectDevice直接按 MAC 连接而不再全量扫描(L158-L162);选择"进行连接配置"时会调用clearMacInfo()强制重新扫描(MainActivity L77-L80);
  • 连接断开后 1 秒自动重连(L75-L80),保证链路的自愈能力。

4.2 配网协议:一组约定的文本命令

App 与 NanoPI 之间的 BLE 数据是 UTF-8 文本命令,由btUpper的回调处理(MainActivity.java L114-L185),与 ctrl-proxy.py 的on_self_write(L228-L256)严格对应:

方向命令触发与含义
App → NanoPISCAN_WIFI请求扫描周边 WiFi;配网模式下连接成功即发送(L122-L124)
NanoPI → AppWIFI_LIST:<名1>\t<名2>...扫描结果,App 弹出列表让用户选择(L136-L154)
App → NanoPICONN_WIFI:<SSID>\t<密码>用户选定网络并输入密码后发送(L148)
NanoPI → AppCONN_WIFI_OK/CONN_WIFI_ERRWiFi 连接结果反馈;失败则 App 提示并重扫(L155-L165)
App → NanoPIGET_IP配网成功后延时 1 秒请求对端 IP(L157-L162)
NanoPI → AppSELF_IP:<ip>通过get_ip_address("wlan0")回报自身 IP(ctrl-proxy.py L233)

收到SELF_IP后,App 执行收尾动作(L166-L182):wifiClient.setServerAddress(ip, 5000)设置 UDP 目标、ctrlView.setServerIP(ip)切换视频源、断开 BLE,至此配网流程闭环,遥控帧与视频流均走网络。此外 NanoPI 端还实现了SET_AP命令(设置热点模式 SSID/密码,ctrl-proxy.py L250-L255),当前 App 版本未调用,属于预留能力。

非配网状态下,App 连接NanoPI后直接发送GET_IP获取 IP(L125-L128),即可快速进入图传模式——这正是"再次打开 App 无需重新配网"的机制。

五、主要文件清单与工程配置

文档列出的关键文件如下(已逐一与仓库核对):

文件职责
balancebot.apk预编译安装包,可直接安装
MainActivity.java主 Activity:创建各模块对象、处理用户菜单事件、10Hz 发送遥控帧
Bluetooth.javaBLE GATT 客户端:扫描、连接、MAC 缓存、特征读写与订阅
WifiClient.javaUDP 客户端:本地 15000 端口、阻塞接收线程
CtrlView.java主视图,重写自 WebView,负责界面叠加绘制与触摸分发
AndroidManifest.xml权限与 Activity 声明
res/drawable摇杆底图、状态图标等图片资源
streamview.htmlWebView 视频流页面

工程层面的可考证信息(app/build.gradle、gradle-wrapper.properties):

  • compileSdk 32、minSdk 26、targetSdk 32,Java 8 语法级别,Gradle 7.2(Android Studio 对应 2021 年之后的版本即可打开);
  • 依赖仅androidx.appcompat、material、constraintlayout三件套,没有引入任何第三方蓝牙/网络库——BLE 用系统 GATT API,网络用原生DatagramSocket,这一点在阅读源码时容易高估其复杂度,实际非常轻量。

AndroidManifest.xml 的权限声明体现了对 Android 版本差异的处理,与 MainActivity 中的运行时申请逻辑(L214-L222)配合:

  • Android 12(API 31)及以上:BLUETOOTH_SCAN(声明neverForLocation)+BLUETOOTH_CONNECT;
  • API 30 及以下:传统BLUETOOTH/BLUETOOTH_ADMIN(maxSdkVersion="30");
  • 蓝牙扫描所需的位置权限:ACCESS_COARSE_LOCATION/ACCESS_FINE_LOCATION(对应源码中 S 以下申请精确定位、Q 以下申请粗略定位的分支);
  • INTERNET+usesCleartextTraffic="true"(视频流为明文 HTTP);
  • MainActivity声明为横屏(screenOrientation="landscape"),界面即按横屏布局设计。

六、使用说明(继承原文档并补充)

  1. 给机器人上电,等待图传系统启动完成(约 1 min);
  2. 打开本程序,点击左上角的配置按钮,菜单包含四项:切换为蓝牙模式、切换为图传模式、进行连接配置、发送站立指令(见 MainActivity.java L53):
    • 不使用图传:点选"切换为蓝牙模式",等待连接成功(App 扫描并连接名为BalanceBot的 ESP32 设备);
    • 使用图传:点选"切换为图传模式",然后再次点击配置按钮选择"进行连接配置",按要求选择网络并输入密码,等待连接成功(即 4.2 节的完整配网流程);
  3. 左上角配置按钮由灰色变亮即表示连接成功(蓝牙模式亮蓝牙图标、图传模式亮视频图标),此时即可使用摇杆控制机器人。

补充两点源码可证实的使用细节:

  • 模式持久化:所选模式会写入SharedPreferences的useVideoMode键(L100),下次启动 App 时自动按上次模式连接对应设备(L260-L266),无需重复选择;
  • 站立指令:机器人趴倒后,可在菜单中选择"发送站立指令"发送0xAB帧,触发主控的站立准备任务;
  • 摇杆 X 轴控制转向(主控端按 1.5 倍增益换算),Y 轴控制前进后退,底部滑块实时调节腿部长度(映射为 0.07~0.09 m 的腿长目标值),松开摇杆即停止。

七、小结:可复用的设计要点

从这份实现中可以提炼出对类似"机器人远程调试 App"项目有参考价值的几条设计决策:

  1. 显示零成本化:把 MJPG 推流端点设计成标准 HTTP 资源,App 用 WebView 直接当视频容器,HTML 只写 60 行以内的代码,Native 层不碰任何视频解码;
  2. 单帧定长协议:遥控数据压缩为 1 字节帧头 + 3 字节量化参数((值+1)*100的无符号化技巧让 float 区间 [-1,1] 落入一个字节),10Hz 发送,UDP 与 BLE 两条通道共用同一帧格式,主控端解析逻辑零分支差异;
  3. 配网即协议:BLE 通道被复用为"带外管理通道",用SCAN_WIFI / CONN_WIFI / GET_IP这类简单文本命令完成入网与 IP 发现,避免了 App 与图传系统之间需要人工输入 IP 的繁琐步骤;
  4. 链路自愈:BLE 断线 1 秒重连、HTML 页 3 秒心跳刷新视频源、UDP 5 秒超时自动退出转发模式,三处自愈逻辑共同降低了现场调试成本。

以上全部行为均可在仓库中按文件路径逐一验证:App 端四个 Java 源文件 + 一个 HTML 页面,Linux 端一个 Python 脚本,固件端main.cpp中的蓝牙解码回调,共同构成了这套轮腿机器人的完整远程操控链路。

  • 机器人
  • 嵌入式
  • 硬件开发
  • 移动开发

【免费下载链接】foc-wheel-legged-robot

Open source materials for a novel structured legged robot, including mechanical design, electronic design, algorithm simulation, and software development. | 一个新型结构的轮腿机器人开源资料,包含机械设计、电子设计、算法仿真、软件开发等材料

项目地址:https://gitcode.com/gh_mirrors/fo/foc-wheel-legged-robot
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/13 13:29:34

网络加速的真相:能力边界与成本陷阱

作为一个常年和“网络加速”打交道的人&#xff0c;我看到这个标题的第一反应是点头。这类需求在项目里太常见了&#xff1a;业务方说用户反馈“慢”&#xff0c;老板拍板要“加速”&#xff0c;于是买带宽、上CDN、换协议栈&#xff0c;一轮操作下来钱花了不少&#xff0c;可后…

作者头像 李华
网站建设 2026/10/13 13:29:29

OPC UA SDK 实战:从连接、订阅到避坑的工业通信指南

简介&#xff1a;这份资源面向使用 C 进行工业自动化与嵌入式开发的工程师&#xff0c;提供 OPC UA 通信开发所需的 SDK 工具集合&#xff0c;帮助开发者快速搭建客户端与服务器端应用&#xff0c;解决跨平台设备间安全数据交换的问题。压缩包共 6 个文件&#xff0c;约 3.18MB…

作者头像 李华
网站建设 2026/10/13 13:28:01

孩子近视怎么办?远视储备、眼轴与离焦镜片防控全解析

孩子一旦查出近视&#xff0c;很多家长的第一反应不是“怎么办”&#xff0c;而是“完了”。这种焦虑完全可以理解&#xff0c;我自己带娃跑过几次眼科以后才明白&#xff0c;近视防控拼的从来不是谁的血压高&#xff0c;而是谁的方法更科学、动作更到位。与其盯着验光单上的数…

作者头像 李华
网站建设 2026/10/13 13:27:39

PodBot 2.6 源码解析:编译、导航与机器人行为定制

简介&#xff1a;这份资源是Podbot 2.6的完整源代码&#xff0c;基于Botman框架构建&#xff0c;面向希望深入研究Counter-Strike游戏机器人开发与游戏AI设计的开发者。它解决了从零理解智能游戏代理实现逻辑的问题&#xff0c;适合具备一定C基础、想学习路径规划、决策制定与多…

作者头像 李华
网站建设 2026/10/13 13:26:34

SpringBoot集成Hadoop实战:生产级WordCount服务构建

1. 项目概述&#xff1a;为什么一个“复杂版WordCount”值得花一整节来拆解&#xff1f;在大数据初学者的自学路径里&#xff0c;“WordCount”几乎是绕不开的第一个程序——它像编程世界的“Hello World”&#xff0c;简单、直观、能快速验证环境是否跑通。但现实中的生产级Ha…

作者头像 李华
网站建设 2026/10/13 13:25:05

OpenMontage开源图片拼接工具:命令行批量拼图实战指南

第一次看到OpenMontage这个名字&#xff0c;我第一反应是又遇到一个AI绘画或者视频特效类的开源项目。等把文档翻完才发现&#xff0c;它比我想象中的要接地气得多——这个工具解决的是几乎所有内容创作者、电商运营和普通用户都会遇到的一个问题&#xff1a;把一堆零散的图片&…

作者头像 李华