蓝牙通信基础
什么是蓝牙?
蓝牙(Bluetooth)是一种无线通信技术,用于在短距离内(通常 10-100 米)进行设备间的数据交换。它工作在 2.4 GHz ISM(工业、科学和医学)频段,与 WiFi 和其他无线设备共享这个频段。
蓝牙的主要特点
- 短距离通信 - 典型范围 10-100 米(取决于功率等级)
- 低功耗 - 特别是蓝牙低功耗(BLE)技术
- 即插即用 - 配对后自动连接
- 安全性 - 使用跳频展频技术防止干扰和截获
- 广泛支持 - 几乎所有现代设备都支持蓝牙
- 成本低 - 芯片成本相对较低
蓝牙版本演变
| 版本 | 发布时间 | 特点 | 功耗 |
|---|---|---|---|
| 1.0 | 1999 | 基础蓝牙 | 高 |
| 2.0 | 2004 | 增加速率 | 中 |
| 3.0 | 2009 | 支持 WiFi | 中 |
| 4.0 | 2010 | 低功耗蓝牙(BLE) | 很低 |
| 4.2 | 2014 | 增强 BLE | 很低 |
| 5.0 | 2016 | 更大范围和速度 | 很低 |
| 5.1 | 2019 | 方向寻向 | 很低 |
| 5.3 | 2021 | 多路设备支持 | 很低 |
对于 LeBot,主要使用蓝牙 4.0+ 的 BLE 技术进行低功耗的传感器数据传输和远程控制。
LeBot 中的蓝牙应用
在 LeBot 中,蓝牙用于:
- 与手机或平板电脑进行无线通信
- 传输实时传感器数据(IMU、电机状态等)
- 接收远程控制指令
- 远程调试和日志记录
- 机器人间的近距离通信
蓝牙技术标准
经典蓝牙 vs 蓝牙低功耗(BLE)
经典蓝牙(Bluetooth Classic)
特点:
- 功耗较高
- 传输速率快(约 2-3 Mbps)
- 适合持续数据传输
- 支持多种声音编解码器
- 适合音频设备(耳机、音箱等)
应用场景:
- 无线音频播放
- 手机文件传输
- 连续高数据率应用蓝牙低功耗(BLE / 蓝牙 4.0+)
特点:
- 功耗极低(可持续运行数年靠纽扣电池)
- 传输速率较低(约 1 Mbps)
- 延迟较高(100-1000ms)
- 适合间断性数据传输
- 自动省电机制
应用场景:
- 健康监测设备
- 智能家居
- 物联网设备
- LeBot 等移动机器人对于 LeBot,推荐使用 BLE 因为:
- 功耗低,机器人电池续航能力强
- 足以传输传感器数据和控制命令
- 与现代智能手机兼容性好
蓝牙协议栈
应用层 用户应用程序、蓝牙 Profile(GATT、SPP 等)
|
主机层 L2CAP、RFCOMM、BNEP
|
控制器层 HCI(主机控制接口)
|
链路管理层 ACL、SCO 链路管理
|
基带层 频率跳跃、错误纠正
|
射频层 2.4 GHz 收发器蓝牙工作原理
频率跳跃展频(FHSS)
蓝牙使用频率跳跃来规避干扰:
时间 →
| CH37 | CH38 | CH39 | CH37 | CH38 |
|--------|--------|--------|--------|--------|
频率 ↑
2.4 GHz ┤ ● ○ ● ● ○
│
└─ 蓝牙跳频序列(●=数据,○=干扰不影响)蓝牙在 40 个频道中快速跳跃(每秒跳跃 1600 次),大大降低了干扰的影响。
配对和连接过程
配对过程:
┌─────────────────────────────────────────────┐
│ 1. 发现:搜索附近的蓝牙设备 │
│ 2. 配对:交换密钥和建立信任关系 │
│ 3. 绑定:保存配对信息供后续快速连接 │
└─────────────────────────────────────────────┘
↓
配对完成
↓
┌─────────────────────────────────────────────┐
│ 连接过程: │
│ 1. 发现:找到已配对的设备 │
│ 2. 连接:建立 ACL 链路 │
│ 3. 认证:验证设备身份 │
│ 4. 建立通道:可以进行数据通信 │
└─────────────────────────────────────────────┘BLE 广告和扫描
BLE 设备使用广告和扫描来发现彼此:
BLE 广播者(LeBot) BLE 扫描器(手机)
| |
|--- 广告包 (adv_ind) ---------->|
| |
|--- 广告包 (adv_ind) ---------->|
| | 发现设备
| |
|<---- 扫描请求 (scan_req) ------|
| |
|---- 扫描响应 (scan_rsp) ------>|
| |
|<-- 连接请求 (connect_req) -----|
| |
|======= 连接建立 ==============|
| |
|<----- 数据包 (app data) ------>|
| ↕ 双向通信BLE GATT 模型
GATT(Generic Attribute Profile)是 BLE 中最重要的应用层协议,定义了如何交换数据。
服务和特征
设备(LeBot)
|
└─ 服务 1(Battery Service)
| |
| └─ 特征 1.1:电池电量(读)
| |
| └─ 特征 1.2:电池状态(读、通知)
|
└─ 服务 2(运动控制服务)
| |
| └─ 特征 2.1:运动速度(写)
| |
| └─ 特征 2.2:运动状态(读、通知)
| |
| └─ 特征 2.3:电机状态(读、通知)
|
└─ 服务 3(传感器服务)
|
└─ 特征 3.1:IMU 数据(读、通知)
|
└─ 特征 3.2:距离传感器(读、通知)GATT 操作
GATT 客户端(手机) GATT 服务端(LeBot)
| |
|------- Read ------>| |
|<----- Read Response ----|
| |
|------ Write ------>| |
|<--- Write Response ----|
| |
|-- Enable Notify -->| |
|<--- Notification ------|
|<--- Notification ------|
|<--- Notification ------|Linux 上的蓝牙编程
BlueZ 框架
Linux 使用 BlueZ 作为蓝牙协议栈。它提供了内核驱动和用户空间库。
应用程序
|
LibBluetoothAPI (C库)
|
BlueZ User Space Tools (bluetoothctl, hcitool 等)
|
D-Bus Interface
|
BlueZ 内核驱动
|
硬件(蓝牙芯片)安装 BlueZ
bash
# Debian/Ubuntu
sudo apt install bluetooth bluez bluez-tools python3-bluez
# 检查蓝牙服务
sudo systemctl status bluetooth
# 启动蓝牙服务
sudo systemctl start bluetooth
# 开机启动
sudo systemctl enable bluetooth使用 bluetoothctl 命令行工具
bash
# 进入交互式蓝牙控制
bluetoothctl
# 常用命令:
show # 显示蓝牙适配器信息
power on # 启用蓝牙
power off # 禁用蓝牙
scan on # 开始扫描设备
scan off # 停止扫描
devices # 列出已发现的设备
pair <MAC_ADDRESS> # 配对设备
connect <MAC_ADDRESS> # 连接设备
disconnect <MAC_ADDRESS># 断开连接
remove <MAC_ADDRESS> # 移除设备
trust <MAC_ADDRESS> # 信任设备
untrust <MAC_ADDRESS> # 取消信任
# 例如:
scan on
# 发现设备,看到如下输出:
# [NEW] Device AA:BB:CC:DD:EE:FF LeBot_Bluetooth
pair AA:BB:CC:DD:EE:FF
connect AA:BB:CC:DD:EE:FF使用 hcitool 和 gatttool
bash
# 扫描设备
hciscan scan
# 查询设备特征
gatttool -b AA:BB:CC:DD:EE:FF --discover-characteristics
# 读取特征值
gatttool -b AA:BB:CC:DD:EE:FF --char-read --handle 0x0002
# 写入特征值
gatttool -b AA:BB:CC:DD:EE:FF --char-write-req --handle 0x0003 --value 0102
# 启用通知
gatttool -b AA:BB:CC:DD:EE:FF --char-write-req --handle 0x0004 --value 0100 --listenPython BLE 编程
使用 PyBluez 库
PyBluez 是 Python 中最常用的蓝牙编程库。
安装 PyBluez
bash
sudo apt install python3-bluez
# 或使用 pip
pip install pybluezBLE 服务器(运行在 LeBot 上)
python
#!/usr/bin/env python3
import bluetooth
import threading
import time
import json
class LeBotBLEServer:
def __init__(self, service_name="LeBot", port=1):
self.service_name = service_name
self.port = port
self.server_socket = None
self.robot_data = {
'battery': 85,
'temperature': 45,
'status': 'ready'
}
def start_server(self):
"""启动蓝牙 RFCOMM 服务器"""
self.server_socket = bluetooth.BluetoothSocket(bluetooth.RFCOMM)
self.server_socket.bind(("", self.port))
self.server_socket.listen(1)
# 注册 SDP 服务
bluetooth.advertise_service(
self.server_socket,
self.service_name,
service_id=None,
service_classes=[bluetooth.SERIAL_PORT_CLASS],
profiles=[bluetooth.SERIAL_PORT_PROFILE],
protocols=[bluetooth.OBEX_UUID],
description="LeBot Robot Control",
provider="LeBot",
port=self.port
)
print(f"蓝牙服务器启动: {self.service_name}")
print(f"等待客户端连接...")
try:
while True:
client_socket, client_info = self.server_socket.accept()
print(f"客户端连接: {client_info}")
# 为每个客户端创建处理线程
client_thread = threading.Thread(
target=self.handle_client,
args=(client_socket, client_info)
)
client_thread.daemon = True
client_thread.start()
except KeyboardInterrupt:
print("服务器关闭")
finally:
self.server_socket.close()
def handle_client(self, client_socket, client_info):
"""处理客户端连接"""
try:
while True:
# 接收数据
data = client_socket.recv(1024)
if not data:
break
# 解析命令
message = data.decode('utf-8')
print(f"收到命令: {message}")
# 处理命令
response = self.process_command(message)
# 发送响应
client_socket.send(response.encode('utf-8'))
except Exception as e:
print(f"错误: {e}")
finally:
client_socket.close()
print(f"客户端 {client_info} 已断开")
def process_command(self, command):
"""处理来自客户端的命令"""
try:
cmd = json.loads(command)
if cmd['type'] == 'get_status':
return json.dumps(self.robot_data)
elif cmd['type'] == 'move':
# 执行移动命令
speed = cmd.get('speed', 0.5)
direction = cmd.get('direction', 'forward')
print(f"执行运动: {direction} @ {speed}")
return json.dumps({'status': 'ok', 'message': 'Moving'})
elif cmd['type'] == 'stop':
print("停止运动")
return json.dumps({'status': 'ok', 'message': 'Stopped'})
else:
return json.dumps({'status': 'error', 'message': 'Unknown command'})
except json.JSONDecodeError:
return json.dumps({'status': 'error', 'message': 'Invalid JSON'})
if __name__ == '__main__':
server = LeBotBLEServer()
server.start_server()BLE 客户端(运行在手机或电脑上)
python
#!/usr/bin/env python3
import bluetooth
import json
import time
class LeBotBLEClient:
def __init__(self, target_address=None):
self.target_address = target_address
self.socket = None
def discover_devices(self):
"""发现附近的蓝牙设备"""
print("正在扫描蓝牙设备...")
devices = bluetooth.discover_devices(lookup_names=True)
print(f"找到 {len(devices)} 个设备:")
for addr, name in devices:
print(f" {name} ({addr})")
if 'LeBot' in name or 'lebot' in name.lower():
print(f" ↑ 找到 LeBot!")
return devices
def connect(self, target_address):
"""连接到 LeBot"""
self.socket = bluetooth.BluetoothSocket(bluetooth.RFCOMM)
try:
print(f"正在连接到 {target_address}...")
self.socket.connect((target_address, 1))
print("已连接到 LeBot")
return True
except bluetooth.btcommon.BluetoothError as e:
print(f"连接失败: {e}")
return False
def send_command(self, command):
"""发送命令到 LeBot"""
if not self.socket:
print("未连接到 LeBot")
return None
try:
# 发送命令
cmd_json = json.dumps(command)
self.socket.send(cmd_json)
# 接收响应
response = self.socket.recv(1024)
return json.loads(response.decode('utf-8'))
except Exception as e:
print(f"错误: {e}")
return None
def get_status(self):
"""获取 LeBot 状态"""
return self.send_command({'type': 'get_status'})
def move(self, direction='forward', speed=0.5):
"""让 LeBot 移动"""
return self.send_command({
'type': 'move',
'direction': direction,
'speed': speed
})
def stop(self):
"""停止 LeBot"""
return self.send_command({'type': 'stop'})
def disconnect(self):
"""断开连接"""
if self.socket:
self.socket.close()
print("已断开连接")
def interactive_control(self):
"""交互式控制 LeBot"""
while True:
print("\n命令选项:")
print("1. 获取状态")
print("2. 前进")
print("3. 后退")
print("4. 停止")
print("5. 退出")
choice = input("请选择 (1-5): ")
if choice == '1':
status = self.get_status()
if status:
print(f"LeBot 状态: {status}")
elif choice == '2':
self.move('forward', 0.5)
print("LeBot 前进中...")
elif choice == '3':
self.move('backward', 0.5)
print("LeBot 后退中...")
elif choice == '4':
self.stop()
print("LeBot 已停止")
elif choice == '5':
break
time.sleep(0.5)
if __name__ == '__main__':
client = LeBotBLEClient()
# 发现设备
devices = client.discover_devices()
# 连接到第一个 LeBot 设备
if devices:
lebot_addr = None
for addr, name in devices:
if 'LeBot' in name:
lebot_addr = addr
break
if lebot_addr and client.connect(lebot_addr):
# 交互式控制
client.interactive_control()
client.disconnect()使用 Bleak 库(跨平台 BLE)
Bleak 是一个跨平台的 BLE 库,支持 Linux、Windows 和 macOS。
安装 Bleak
bash
pip install bleakBleak 客户端示例
python
#!/usr/bin/env python3
import asyncio
from bleak import BleakClient, BleakScanner
class LeBotBLEClientAsync:
# BLE 特征 UUID(这些需要与 LeBot 设备一致)
CHAR_CONTROL = "00002a37-0000-1000-8000-00805f9b34fb"
CHAR_STATUS = "00002a19-0000-1000-8000-00805f9b34fb"
async def discover_devices(self):
"""发现附近的 BLE 设备"""
print("扫描 BLE 设备...")
devices = await BleakScanner.discover()
for device in devices:
print(f"{device.name} ({device.address})")
return devices
async def connect_and_control(self, device_address):
"""连接并控制 LeBot"""
async with BleakClient(device_address) as client:
print(f"已连接到 {device_address}")
# 读取设备特征
services = await client.get_services()
for service in services:
print(f"\n服务: {service.uuid}")
for char in service.characteristics:
print(f" 特征: {char.uuid}")
# 发送控制命令
command = b'{"type": "move", "speed": 0.5}'
await client.write_gatt_char(self.CHAR_CONTROL, command)
print("已发送移动命令")
# 读取状态
await asyncio.sleep(1)
status = await client.read_gatt_char(self.CHAR_STATUS)
print(f"状态: {status}")
# 停止
command = b'{"type": "stop"}'
await client.write_gatt_char(self.CHAR_CONTROL, command)
print("已停止")
async def main():
client = LeBotBLEClientAsync()
# 发现设备
devices = await client.discover_devices()
# 连接到第一个设备
if devices:
await client.connect_and_control(devices[0].address)
if __name__ == '__main__':
asyncio.run(main())C/C++ 中的蓝牙编程
使用 BlueZ 库
简单的 RFCOMM 服务器
c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <bluetooth/bluetooth.h>
#include <bluetooth/rfcomm.h>
int main(int argc, char **argv) {
struct sockaddr_rc loc_addr = { 0 }, rem_addr = { 0 };
char buf[1024] = { 0 };
int s, client, bytes_read;
socklen_t opt = sizeof(rem_addr);
// 创建 RFCOMM Socket
s = socket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM);
if (s < 0) {
perror("socket");
exit(1);
}
// 绑定到本地蓝牙适配器
loc_addr.rc_family = AF_BLUETOOTH;
bacpy(&loc_addr.rc_bdaddr, BDADDR_ANY);
loc_addr.rc_channel = (uint8_t) 1;
if (bind(s, (struct sockaddr *)&loc_addr, sizeof(loc_addr)) < 0) {
perror("bind");
close(s);
exit(1);
}
// 监听连接
listen(s, 1);
printf("等待连接...\n");
while (1) {
// 接受连接
client = accept(s, (struct sockaddr *)&rem_addr, &opt);
if (client < 0) {
perror("accept");
continue;
}
// 获取客户端地址
ba2str(&rem_addr.rc_bdaddr, buf);
printf("客户端连接: %s\n", buf);
memset(buf, 0, sizeof(buf));
// 接收数据
bytes_read = recv(client, buf, sizeof(buf), 0);
if (bytes_read > 0) {
printf("收到: %s\n", buf);
}
// 发送响应
send(client, "OK", 2, 0);
close(client);
}
close(s);
return 0;
}编译:
bash
gcc -o lebot_ble_server lebot_ble_server.c -lbluetooth
./lebot_ble_serverBLE 特征设计最佳实践
为 LeBot 设计蓝牙接口
LeBot 蓝牙服务设计
├─ Battery Service (标准服务 0x180F)
│ └─ Battery Level (0x2A19):电池百分比
│ 读:获取当前电池电量
│ 通知:电池电量变化时通知
│
├─ Device Information Service (标准服务 0x180A)
│ ├─ Manufacturer Name (0x2A29):制造商
│ ├─ Model Number (0x2A24):型号
│ └─ Software Revision (0x2A28):固件版本
│
├─ Robot Control Service (自定义服务)
│ ├─ Motor Command (0xFFE1)
│ │ 写:motor_id|speed|direction
│ │
│ ├─ Motor Status (0xFFE2)
│ │ 读/通知:motor_id|speed|current|temp
│ │
│ └─ Emergency Stop (0xFFE3)
│ 写:任何非零值触发紧急停止
│
└─ Sensor Data Service (自定义服务)
├─ IMU Data (0xFFE4)
│ 读/通知:accel_x|accel_y|accel_z|gyro_x|gyro_y|gyro_z
│
├─ Distance Sensors (0xFFE5)
│ 读/通知:front|left|right|back
│
└─ Battery Monitor (0xFFE6)
读/通知:voltage|current|temp蓝牙通信的常见问题
问题 1:连接不稳定
原因:
- 蓝牙信号干扰(2.4 GHz WiFi 频段冲突)
- 蓝牙芯片性能问题
- 应用层数据处理不当
解决:
- 增加蓝牙发射功率
- 实现重连机制
- 使用心跳包检测连接状态
问题 2:数据传输慢
原因:
- BLE 每个数据包只能 20 字节(加 4 字节头)
- 连接参数不优化
解决:
python
# 减少连接间隔以提高带宽
# 发现特征后调整连接参数
# Android: 通过 BluetoothGatt 的 requestConnectionPriority
# iOS: 自动优化问题 3:功耗过高
原因:
- 通知频率过高
- 连接参数不优化
- 持续数据传输
解决:
- 减少通知频率
- 使用适当的连接间隔
- 实现睡眠/唤醒机制
蓝牙安全考虑
配对和认证
python
# 配对后应该验证设备身份
def verify_device(expected_address):
"""验证连接的设备是否是预期的 LeBot"""
if client_address != expected_address:
print("警告:连接到陌生设备!")
return False
return True加密敏感数据
python
# 对发送的控制命令进行加密
import hashlib
import json
def create_secure_command(command, secret_key):
"""创建签名的命令"""
command_json = json.dumps(command)
signature = hashlib.sha256((command_json + secret_key).encode()).hexdigest()
return {
'command': command_json,
'signature': signature[:16] # 取前 16 字符
}总结
蓝牙是连接 LeBot 与移动设备的重要技术:
- BLE 功耗低,适合电池供电的机器人
- BlueZ 是 Linux 蓝牙的标准实现
- Python 的 PyBluez 和 Bleak 简化开发
- GATT 模型提供标准的特征设计
- 合理设计特征能提高通信效率和可靠性
在实际应用中,应该根据 LeBot 的具体需求设计蓝牙接口,并考虑功耗、延迟和数据带宽等因素。