最近在 Windows 系统上使用 Python 进行经典蓝牙设备扫描时,遇到了 PyBluez 库安装失败的问题。经过多次尝试,终于找到了一个可靠的解决方案。本文将详细介绍解决过程,并提供可直接运行的代码。

最终解决方案

1. 安装 PyBluez(Windows 适配版本)

# 步骤 1:设置合适的 setuptools 版本
pip install setuptools==68.0.0

# 步骤 2:从 GitHub 安装修复版 PyBluez
pip install git+https://github.com/pybluez/pybluez.git

# 步骤 3:恢复最新版 setuptools
pip install --upgrade setuptools

2. 扫描经典蓝牙设备的完整代码

import bluetooth
import time

def scan_classic_bluetooth(duration=8):
    """
    扫描经典蓝牙设备
    :param duration: 扫描持续时间(秒)
    :return: 设备列表,每个设备为 (地址, 名称) 元组
    """
    print(f"开始扫描经典蓝牙设备,持续时间 {duration} 秒...")
    print("-" * 50)

    try:
        # discover_devices 参数说明:
        # lookup_names=True: 查找设备名称
        # flush_cache=True: 清除缓存,重新扫描
        # lookup_class=False: 不查找设备类
        # duration: 扫描持续时间
        nearby_devices = bluetooth.discover_devices(
            lookup_names=True,
            flush_cache=True,
            duration=duration
        )

        if not nearby_devices:
            print("未发现任何蓝牙设备")
            return []

        print(f"发现 {len(nearby_devices)} 个设备:\n")
        for i, (addr, name) in enumerate(nearby_devices, 1):
            # 设备名称可能为 None
            device_name = name if name else "未知设备"
            print(f"{i:2d}. 地址: {addr}")
            print(f"    名称: {device_name}")

            # 可选:获取设备更多信息
            try:
                # 获取设备类(需要额外查询)
                device_class = bluetooth.lookup_class(addr)
                if device_class:
                    print(f"    类别: 0x{device_class:06x}")
            except:
                pass

            print()

        return nearby_devices

    except Exception as e:
        print(f"扫描失败: {e}")
        return []


def get_device_services(device_address):
    """
    获取设备的服务信息
    :param device_address: 蓝牙设备地址
    """
    print(f"\n查询设备 {device_address} 的服务...")
    try:
        services = bluetooth.find_service(address=device_address)

        if not services:
            print("未发现服务")
            return []

        print(f"发现 {len(services)} 个服务:")
        for i, service in enumerate(services, 1):
            print(f"\n服务 {i}:")
            print(f"  名称: {service.get('name', '未知')}")
            print(f"  协议: {service.get('protocol', '未知')}")
            print(f"  端口: {service.get('port', '未知')}")
            print(f"  服务ID: {service.get('service-id', '未知')}")

        return services
    except Exception as e:
        print(f"查询服务失败: {e}")
        return []


if __name__ == "__main__":
    # 扫描设备
    devices = scan_classic_bluetooth(duration=10)
    
    # 输出结果
    print("扫描结果:", devices)

问题解决全流程记录

1. 初始尝试:直接安装失败

一开始尝试使用 pip install pybluez 安装,遇到了以下错误:

error in PyBluez setup command: use_2to3 is invalid.

这个错误是因为 PyBluez-0.23 使用了已弃用的 use_2to3 参数,与新版本的 setuptools 不兼容。

2. 尝试 PyBluez2 也失败

转向 PyBluez2 分支版本:

pip install pybluez2

结果出现另一个错误:

error: package directory 'bluetooth\windows' does not exist

这表明 Windows 平台支持文件缺失。

3. 降级 setuptools 的尝试

根据网络上的建议,尝试降级 setuptools:

pip install setuptools==58.2.0
pip install pybluez

这虽然绕过了 use_2to3 错误,但又出现了新问题:

ModuleNotFoundError: No module named 'distutils'

这是因为旧版 setuptools 与当前 Python 环境的兼容性问题。

4. 从 GitHub 直接安装的成功方案

经过多次尝试,最终找到的解决方案是从 GitHub 仓库直接安装,配合特定版本的 setuptools:

# 1. 使用兼容的 setuptools 版本
pip install setuptools==68.0.0

# 2. 从 GitHub 安装修复版
pip install git+https://github.com/pybluez/pybluez.git

# 3. 恢复 setuptools
pip install --upgrade setuptools

这个方案的原理是:

  • 使用 68.0.0 版本的 setuptools 作为中间版本,既兼容 PyBluez 又不会导致 distutils 缺失
  • 从 GitHub 安装包含 Windows 支持文件的版本
  • 安装完成后恢复最新的 setuptools,避免影响其他包的依赖

5. 验证安装成功

安装完成后,可以运行以下代码验证:

import bluetooth
print("PyBluez 版本:", bluetooth.__version__)
print("本地蓝牙地址:", bluetooth.read_local_bd_addr())

代码使用说明

基本功能

# 1. 扫描附近蓝牙设备
devices = scan_classic_bluetooth(duration=10)

# 2. 扫描结果格式
# 返回列表,每个元素是 (MAC地址, 设备名称) 的元组
# 例如: [('AA:BB:CC:DD:EE:FF', 'MyDevice'), ...]

# 3. 查询设备服务
if devices:
    addr, name = devices[0]  # 取第一个设备
    services = get_device_services(addr)

增强功能

如果需要更详细的扫描信息,可以添加以下功能:

def enhanced_scan(duration=12):
    """
    增强版蓝牙扫描
    """
    devices_found = []
    start_time = time.time()
    
    print(f"【蓝牙扫描】开始时间: {time.strftime('%H:%M:%S')}")
    print("=" * 60)
    
    # 执行扫描
    devices = bluetooth.discover_devices(
        lookup_names=True,
        lookup_class=True,  # 获取设备类别
        flush_cache=True,
        duration=duration
    )
    
    scan_time = time.time() - start_time
    
    for addr, name, device_class in devices:
        # 解析设备类别
        major_class = (device_class >> 8) & 0xFF
        minor_class = (device_class >> 2) & 0x3F
        
        device_info = {
            'address': addr,
            'name': name or "未知设备",
            'class': device_class,
            'major_class': major_class,
            'minor_class': minor_class,
            'scan_time': scan_time
        }
        devices_found.append(device_info)
        
        # 设备类别说明
        class_names = {
            0x01: "计算机",
            0x02: "手机",
            0x03: "网络设备",
            0x04: "音频/视频设备",
            0x05: "外设",
            0x06: "成像设备",
            0x07: "可穿戴设备",
            0x08: "玩具",
            0x1F: "未分类"
        }
        
        class_name = class_names.get(major_class, "未知类型")
        
        print(f"设备: {name or '未知设备'}")
        print(f"地址: {addr}")
        print(f"类别: {class_name} (0x{device_class:06x})")
        print("-" * 40)
    
    print(f"【扫描完成】共发现 {len(devices_found)} 个设备")
    print(f"扫描耗时: {scan_time:.2f} 秒")
    
    return devices_found

常见问题与解决方案

1. 扫描不到设备

  • 检查蓝牙适配器:确保 Windows 蓝牙已开启
  • 设备可见性:目标设备需处于可发现模式
  • 管理员权限:尝试以管理员身份运行
  • 延长扫描时间:增加到 10-15 秒

2. 权限错误

# 以管理员身份运行
# 或在代码开始时请求权限提升
import ctypes
import sys

def is_admin():
    try:
        return ctypes.windll.shell32.IsUserAnAdmin()
    except:
        return False

if not is_admin():
    ctypes.windll.shell32.ShellExecuteW(
        None, "runas", sys.executable, " ".join(sys.argv), None, 1
    )
    sys.exit()

3. 连接特定设备

def connect_to_device(address, port=1):
    """
    连接蓝牙设备
    """
    try:
        # 创建蓝牙套接字
        sock = bluetooth.BluetoothSocket(bluetooth.RFCOMM)
        
        # 连接设备
        sock.connect((address, port))
        print(f"成功连接到 {address}:{port}")
        
        # 发送数据示例
        sock.send(b"Hello Bluetooth!")
        
        # 接收数据
        data = sock.recv(1024)
        print(f"收到数据: {data}")
        
        # 关闭连接
        sock.close()
        return True
        
    except Exception as e:
        print(f"连接失败: {e}")
        return False

替代方案对比

方案优点缺点适用场景
本文方案支持经典蓝牙,代码简单Windows 安装复杂Windows 经典蓝牙
Bleak 库安装简单,支持 BLE仅 BLE,不兼容经典蓝牙低功耗蓝牙设备
PowerShell无需额外安装依赖 Windows 系统简单扫描和信息获取
PySerial通过虚拟串口,兼容性好需设备配对为串口蓝牙串口设备

总结

在 Windows 上使用 Python 进行蓝牙开发确实存在一些挑战,特别是对于经典蓝牙设备。通过从 GitHub 安装修复版的 PyBluez 库,配合特定版本的 setuptools,我们可以成功地在 Windows 上实现蓝牙设备扫描。

关键点总结

  1. PyBluez 官方版本在 Windows 上支持有限
  2. 需要从 GitHub 安装包含 Windows 支持文件的版本
  3. 使用 setuptools==68.0.0 作为过渡版本可解决兼容性问题
  4. 安装完成后恢复最新版 setuptools 避免依赖冲突

这个方案为在 Windows 上进行经典蓝牙开发提供了一个稳定的基础,可以在此基础上实现设备连接、数据传输等更复杂的功能。

注意:如果主要使用蓝牙低功耗(BLE)设备,建议直接使用 bleak 库,它在 Windows 上安装简单且功能完整。对于经典蓝牙设备,本文提供的方案是当前可用的最佳方案之一。

Logo

这里是“一人公司”的成长家园。我们提供从产品曝光、技术变现到法律财税的全栈内容,并连接云服务、办公空间等稀缺资源,助你专注创造,无忧运营。

更多推荐