前言

因为需要对蓝牙信息进行获取,但是找了一些python模块,比如bluepy只支持Linux、bleson项目文档又太烂。因此这里干脆就直接通过命令行获取,然后对信息进行处理了。

步骤

分析命令行

Mac获取蓝牙信息可以使用下面的命令:

system_profiler SPBluetoothDataType

使用python的os模块即可,执行完成之后如下。

Bluetooth:

      Apple Bluetooth Software Version: 7.0.0f8
      Hardware, Features, and Settings:
          Name: Zheyi的MacBook Pro
          Address: F0-18-98-0B-B7-35
          Bluetooth Low Energy Supported: Yes
          Handoff Supported: Yes
          Instant Hot Spot Supported: Yes
          Manufacturer: Broadcom
          Transport: UART
          Chipset: 4364B0
          Firmware Version: v83 c4405
          Bluetooth Power: On
          Discoverable: Off
          Connectable: Yes
          Auto Seek Pointing: On
          Remote wake: On
          Vendor ID: 0x05AC
          Product ID: 0x007B
          Bluetooth Core Spec: 5.0 (0x9)
          HCI Revision: 0x1135
          LMP Version: 5.0 (0x9)
          LMP Subversion: 0x2053
          Device Type (Major): Computer
          Device Type (Complete): Mac Portable
          Composite Class Of Device: 0x38010C
          Device Class (Major): 0x01
          Device Class (Minor): 0x03
          Service Class: 0x1C0
          Auto Seek Keyboard: On
      Devices (Paired, Configured, etc.):
          BlueBlueSky’s Beats Solo³:
              Address: D4-90-9C-3A-C6-41
              Major Type: Audio
              Minor Type: Headphones
              Services: Handsfree, AAP Server, SPP Server, AVRCP Controller, Audio Sink, Wireless iAP, AVRCP Target
              Paired: Yes
              Configured: Yes
              Connected: Yes
              Manufacturer: Apple (0x6, 0x03)
              Bluetooth Core Spec: 4.0
              Firmware Version: 0x0772
              Vendor ID: 0x004C
              Product ID: 0x2006
              Class of Device: 0x04 0x06 0x240418
              RSSI: -32
              Role: Master
              Connection Mode: Sniff Mode
              Interval: 441.25 ms
              EDR Supported: Yes
              eSCO Supported: Yes
              SSP Supported: Yes
......

剩下的一些输出信息就不写了,我们的目标其实是获取Devices那一部分后面的设备信息。其实有很明显的层级结构,第一行Bluetooth是第0级,Devices (Paired, Configured, etc.)与其他一些信息都是第1级,设备的名称处于第2级,设备的具体信息处于第3级。

还有一个很明显的特征,键值是通过:分割的,和json数据很相似,python里面我们就可以使用字典dict来表示。如果我们将这些字符串信息转化成字典dict那么就可以很方便的获取相关信息了,比如这样dict["Bluetooth"]["Devices"]就可以获取所有设备相关信息的数组。

步骤

简单描述一下代码思路

执行命令行并获取层级信息

获取每行的信息,主要是根据缩进判定该行的层级,我们将行的信息从字符串转化成如下结构, 篇幅限制我就写前两行:

[{'name': 'Bluetooth', 'value': '', 'level': 0}, 
{'name': 'Apple Bluetooth Software Version', 'value': '7.0.0f8', 'level': 1.0},  
......]

这里需要注意的是,命令行里面的缩进并不是制表符,第0级是0个空格缩进,第1级是6个空格缩进,第2级是10个空格缩进,第n级是4n+2个缩进(n≠0)。

根据层级信息生成字典

这里就根据上面的层级信息生成字典数据。因为我们生成的层级信息上下之间是有联系的,比如层级依次是这样的01222122,前三个2是属于第一个1的,后面两个2是属于第一个1的。因此下一层级的所有数据可以作为一个整体list成为上一级的值,这里递归就可以根据层级结构较为方便地生成字典结构。

代码

具体代码如下:

import os


def bluetooth_rssi_get():
    cmd = "system_profiler SPBluetoothDataType"
    devices = get_bluetooth_devices(cmd)

    print(devices)
    for device_name, device_info in devices.items():

        for key, value in device_info.items():
            if key == 'Connected':
                if value == 'Yes':
                    print(device_name, "连接中")
                else:
                    print(device_name, "未连接")
            if key == 'RSSI':
                print('RSSI:', value)


def get_bluetooth_devices(cmd):
    '''
    执行cmd命令获取所有蓝牙相关信息
    :param cmd: 命令行
    :return: 返回所有连接中或连接过的蓝牙设备
    '''
    r = os.popen(cmd)
    info = r.readlines()  # 读取命令行的输出到一个list
    level_map = process_info(info)
    r.close()
    return level_map2dict(level_map)["Bluetooth"]["Devices (Paired, Configured, etc.)"]


def process_info(info):
    '''
    处理命令行结果,生成根据缩进判定的层级
    :param info: 命令行结果,每行以数组形成存储
    :return: 带有层级的数组
    '''
    level_map = []
    for line in info:  # 按行遍历
        line = line.strip('\r\n')
        if len(line) > 0:  # 第二行是空行
            line_info = get_line_info(line)
            if line_info["level"] >= 0:  # 把第一行Bluetooth去除
                level_map.append(line_info)
    print(level_map)
    return level_map


def get_line_info(line):
    '''
    解析命令行每行信息
    :param line: 传入行
    :return: 根据冒号分割以及缩进,生成的每行信息(字典形式)
    '''
    split = line.split(':')
    name = split[0].lstrip(' ')
    value = split[1].lstrip(' ')
    level = (len(line) - len(line.lstrip(' ')) - 2) / 4
    if level < 0:
        level = 0  # 针对第一行

    line_info = {}
    line_info["name"] = name
    line_info["value"] = value
    line_info["level"] = level
    return line_info


def level_map2dict(ttree, level=0):
    '''
    根据层级信息转化成字典结构
    :param ttree:
    :param level:
    :return:
    '''
    result = {}
    for i in range(0, len(ttree)):
        current_node = ttree[i]
        try:
            next_node = ttree[i + 1]
        except:
            next_node = {'level': -1}

        # 边界条件,非当前层级
        if current_node['level'] > level:  # 已经作为整体插入,跳过
            continue
        if current_node['level'] < level:  # 当前是上一级的,直接返回现有结果
            return result


        # 递归生成
        if next_node['level'] == level:  # 同级
            dict_insert_or_append(result, current_node['name'], current_node['value'])
        elif next_node['level'] > level:  # 下一级,将下一级整体插入
            next_level = level_map2dict(ttree[i + 1:], level=next_node['level'])  # 剩下的进行处理
            dict_insert_or_append(result, current_node['name'], next_level)
        else:  # 下一个是上一级的,当前插入完成直接返回
            dict_insert_or_append(result, current_node['name'], current_node['value'])
            return result
    return result


def dict_insert_or_append(adict, key, val):
    '''
    针对key是否存在,新增或者添加
    :param adict:
    :param key:
    :param val:
    :return:
    '''
    if key in adict:  # 添加
        if type(adict[key]) != list:
            adict[key] = [adict[key]]  # 将单独存在的转化成list
        adict[key].append(val)
    else:  # 新增
        adict[key] = val

bluetooth_rssi_get()