以下是基于Uniapp开发Android应用与ROS 2 Python节点通过蓝牙通信的完整实现方案:
系统架构
Uniapp(Android) ↔ 蓝牙RFCOMM ↔ ROS 2 Python节点(运行在Linux设备上)
1. ROS 2 Python节点准备
蓝牙服务端代码 (bluetooth_ros_bridge.py
)
#!/usr/bin/env python3
import rclpy
from rclpy.node import Node
import bluetooth
import json
from threading import Thread
class BluetoothBridge(Node):
def __init__(self):
super().__init__('bluetooth_bridge')
# 初始化蓝牙服务
self.server_sock = bluetooth.BluetoothSocket(bluetooth.RFCOMM)
self.server_sock.bind(("", bluetooth.PORT_ANY))
self.server_sock.listen(1)
self.port = self.server_sock.getsockname()[1]
# 广播服务UUID
bluetooth.advertise_service(
self.server_sock,
"ROS2Bridge",
service_id=bluetooth.SERIAL_PORT_CLASS,
profiles=[bluetooth.SERIAL_PORT_PROFILE]
)
self.get_logger().info(f"等待蓝牙连接,端口: {self.port}")
self.client_sock = None
self.receive_thread = Thread(target=self.receive_data)
self.receive_thread.daemon = True
self.receive_thread.start()
def receive_data(self):
try:
self.client_sock, address = self.server_sock.accept()
self.get_logger().info(f"已连接: {address}")
while rclpy.ok():
data = self.client_sock.recv(1024)
if not data:
break
try:
msg = json.loads(data.decode())
self.handle_message(msg)
except json.JSONDecodeError:
self.get_logger().warn("收到非JSON数据")
except Exception as e:
self.get_logger().error(f"蓝牙错误: {str(e)}")
def handle_message(self, msg):
# 处理来自APP的消息
self.get_logger().info(f"收到消息: {msg}")
# 示例:发送ROS2主题
# self.publisher.publish(String(data=msg['command']))
# 回复消息
response = {"status": "OK", "data": "Received"}
self.client_sock.send(json.dumps(response).encode())
def main(args=None):
rclpy.init(args=args)
node = BluetoothBridge()
try:
rclpy.spin(node)
except KeyboardInterrupt:
pass
finally:
node.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
依赖安装
sudo apt install python3-bluez ros-${ROS_DISTRO}-rclpy
pip install pybluez2
2. Uniapp Android端实现
修改 manifest.json
{
"app-plus": {
"distribute": {
"android": {
"permissions": [
"android.permission.BLUETOOTH",
"android.permission.BLUETOOTH_ADMIN",
"android.permission.ACCESS_FINE_LOCATION"
]
}
}
}
}
蓝牙服务封装 (bluetooth-service.js
)
const BluetoothDevices = uni.requireNativePlugin('Bluetooth-Devices')
export default {
data() {
return {
connectedDevice: null,
socket: null
}
},
methods: {
// 搜索设备
scanDevices() {
return new Promise((resolve, reject) => {
BluetoothDevices.startDiscovery({
success: (res) => resolve(res.devices),
fail: (err) => reject(err)
})
})
},
// 连接ROS2设备
async connectToROS2(deviceName) {
const devices = await this.scanDevices()
const target = devices.find(d => d.name.includes(deviceName))
if (!target) throw new Error('设备未找到')
this.socket = await new Promise((resolve, reject) => {
BluetoothDevices.createSocket({
address: target.address,
type: 'rfcomm',
success: resolve,
fail: reject
})
})
this.connectedDevice = target
this.setupListeners()
return target
},
// 设置消息监听
setupListeners() {
BluetoothDevices.onSocketMessage((res) => {
try {
const msg = JSON.parse(res.data)
this.$emit('message', msg)
} catch (e) {
console.error('解析消息失败', e)
}
})
},
// 发送JSON数据
sendJson(data) {
return new Promise((resolve, reject) => {
BluetoothDevices.writeSocket({
data: JSON.stringify(data),
success: resolve,
fail: reject
})
})
},
// 断开连接
disconnect() {
if (this.socket) {
BluetoothDevices.closeSocket()
this.socket = null
}
}
}
}
3. 页面调用示例 (index.vue
)
<template>
<view>
<button @click="connect">连接ROS2设备</button>
<button @click="sendTestCommand">发送测试命令</button>
</view>
</template>
<script>
import BluetoothService from './bluetooth-service.js'
export default {
mixins: [BluetoothService],
methods: {
async connect() {
try {
await this.connectToROS2('ROS2_Bluetooth') // 设备名称前缀
uni.showToast({ title: '连接成功', icon: 'success' })
} catch (err) {
uni.showToast({ title: `连接失败: ${err}`, icon: 'none' })
}
},
async sendTestCommand() {
if (!this.connectedDevice) return
try {
const response = await this.sendJson({
command: 'move_forward',
speed: 0.5,
duration: 2
})
console.log('ROS2响应:', response)
} catch (err) {
console.error('发送失败', err)
}
}
}
}
</script>
4. 关键实现细节
自动连接流程
- Android端定期扫描蓝牙设备 (
startDiscovery
) - 识别包含特定前缀的设备名 (如
ROS2_Bluetooth_XX
) - 自动建立RFCOMM socket连接
- 维护心跳包机制检测连接状态
数据协议设计
{
"type": "command/respose/event",
"timestamp": 1630000000,
"payload": {
"topic": "/cmd_vel",
"data": {"linear": 0.1, "angular": 0.0}
}
}
ROS2消息转换
在Python端需要实现:
def handle_message(self, msg):
if msg['type'] == 'command':
# 示例:控制机器人移动
cmd = Twist()
cmd.linear.x = msg['payload']['data']['linear']
self.cmd_pub.publish(cmd)
# 发送响应
self.send_response({
"type": "response",
"status": "executed"
})
5. 注意事项
-
Android权限:
- 需要动态申请位置权限(Android 12+要求)
- 在
pages.json
中声明蓝牙权限
-
跨平台兼容:
- iOS需要使用不同的原生插件(如
uni-bluetooth
) - 建议使用
try-catch
包裹蓝牙操作
- iOS需要使用不同的原生插件(如
-
性能优化:
// 节流消息发送 import throttle from 'lodash.throttle' this.throttledSend = throttle(this.sendJson, 100)
-
调试工具:
- 使用
nRF Connect
等蓝牙调试APP测试基础连接 - ROS2端可以用
bluetoothctl
查看设备状态
- 使用
完整项目结构
project/
├── uniapp_project/ # Uniapp工程
│ ├── js_sdk/ # 蓝牙SDK封装
│ └── pages/
├── ros2_ws/ # ROS2工作空间
│ ├── bluetooth_bridge/ # Python蓝牙桥接包
│ └── launch/ # 启动文件
└── docs/ # 协议文档
通过以上实现,可以建立稳定的蓝牙通信通道,典型延迟在50-200ms之间,适合传输控制指令和传感器数据。对于大数据量传输建议改用Wi-Fi。