Skip to content

蓝牙通信基础

什么是蓝牙?

蓝牙(Bluetooth)是一种无线通信技术,用于在短距离内(通常 10-100 米)进行设备间的数据交换。它工作在 2.4 GHz ISM(工业、科学和医学)频段,与 WiFi 和其他无线设备共享这个频段。

蓝牙的主要特点

  1. 短距离通信 - 典型范围 10-100 米(取决于功率等级)
  2. 低功耗 - 特别是蓝牙低功耗(BLE)技术
  3. 即插即用 - 配对后自动连接
  4. 安全性 - 使用跳频展频技术防止干扰和截获
  5. 广泛支持 - 几乎所有现代设备都支持蓝牙
  6. 成本低 - 芯片成本相对较低

蓝牙版本演变

版本发布时间特点功耗
1.01999基础蓝牙
2.02004增加速率
3.02009支持 WiFi
4.02010低功耗蓝牙(BLE)很低
4.22014增强 BLE很低
5.02016更大范围和速度很低
5.12019方向寻向很低
5.32021多路设备支持很低

对于 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 --listen

Python BLE 编程

使用 PyBluez 库

PyBluez 是 Python 中最常用的蓝牙编程库。

安装 PyBluez

bash
sudo apt install python3-bluez
# 或使用 pip
pip install pybluez

BLE 服务器(运行在 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 bleak

Bleak 客户端示例

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_server

BLE 特征设计最佳实践

为 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 的具体需求设计蓝牙接口,并考虑功耗、延迟和数据带宽等因素。


推荐资源

由 LeBot 开发团队编写