美化gatttool的输出

最近在使用bluez的小工具gatttool,同时也会配合其他小工具一起使用,比如hcitool,hciconfig等,但是我发现gatttool的输出结果很不友好,很难一眼就从众多的handle里面找到自己想要读写的handle,于是就萌生了想要美化下gatttool的输出的想法:

输出结果对比

之前很长一段时间都是在手机上查看设备的服务,手机上的界面一直是这样的,非常美观:
在这里插入图片描述

可以看到,手机的输出非常Nice,一个设备上有哪些服务,这些服务的Characteristic有哪些,他们的属性是什么样子的,一目了然,我们对比一下,gatttool的输出是这样的:

attr handle = 0x0001, end grp handle = 0x0005 uuid: 00001800-0000-1000-8000-00805f9b34fb
attr handle = 0x0006, end grp handle = 0x0009 uuid: 00001801-0000-1000-8000-00805f9b34fb
attr handle = 0x000a, end grp handle = 0x0011 uuid: 0000ffe0-0000-1000-8000-00805f9b34fb
attr handle = 0x0012, end grp handle = 0x0015 uuid: 66666666-6666-6666-6666-666666666666
attr handle = 0x0016, end grp handle = 0x0019 uuid: 86868686-8686-8686-8686-868686868686

是这样的:

handle = 0x0001, uuid = 00002800-0000-1000-8000-00805f9b34fb
handle = 0x0002, uuid = 00002803-0000-1000-8000-00805f9b34fb
handle = 0x0003, uuid = 00002a00-0000-1000-8000-00805f9b34fb
handle = 0x0004, uuid = 00002803-0000-1000-8000-00805f9b34fb
handle = 0x0005, uuid = 00002a01-0000-1000-8000-00805f9b34fb
handle = 0x0006, uuid = 00002800-0000-1000-8000-00805f9b34fb
handle = 0x0007, uuid = 00002803-0000-1000-8000-00805f9b34fb
handle = 0x0008, uuid = 00002a05-0000-1000-8000-00805f9b34fb
handle = 0x0009, uuid = 00002902-0000-1000-8000-00805f9b34fb
handle = 0x000a, uuid = 00002800-0000-1000-8000-00805f9b34fb
handle = 0x000b, uuid = 00002803-0000-1000-8000-00805f9b34fb
handle = 0x000c, uuid = 0000ffe1-0000-1000-8000-00805f9b34fb
handle = 0x000d, uuid = 00002902-0000-1000-8000-00805f9b34fb
handle = 0x000e, uuid = 00002901-0000-1000-8000-00805f9b34fb
handle = 0x000f, uuid = 00002803-0000-1000-8000-00805f9b34fb
handle = 0x0010, uuid = 0000ffe2-0000-1000-8000-00805f9b34fb
handle = 0x0011, uuid = 00002901-0000-1000-8000-00805f9b34fb
handle = 0x0012, uuid = 00002800-0000-1000-8000-00805f9b34fb
handle = 0x0013, uuid = 00002803-0000-1000-8000-00805f9b34fb
handle = 0x0014, uuid = 77777777-7777-7777-7777-777777777777
handle = 0x0015, uuid = 00002902-0000-1000-8000-00805f9b34fb
handle = 0x0016, uuid = 00002800-0000-1000-8000-00805f9b34fb
handle = 0x0017, uuid = 00002803-0000-1000-8000-00805f9b34fb
handle = 0x0018, uuid = 97979797-9797-9797-9797-979797979797
handle = 0x0019, uuid = 00002902-0000-1000-8000-00805f9b34fb

又或者是这样的:

handle = 0x0002, char properties = 0x02, char value handle = 0x0003, uuid = 00002a00-0000-1000-8000-00805f9b34fb
handle = 0x0004, char properties = 0x02, char value handle = 0x0005, uuid = 00002a01-0000-1000-8000-00805f9b34fb
handle = 0x0007, char properties = 0x22, char value handle = 0x0008, uuid = 00002a05-0000-1000-8000-00805f9b34fb
handle = 0x000b, char properties = 0x10, char value handle = 0x000c, uuid = 0000ffe1-0000-1000-8000-00805f9b34fb
handle = 0x000f, char properties = 0x0c, char value handle = 0x0010, uuid = 0000ffe2-0000-1000-8000-00805f9b34fb
handle = 0x0013, char properties = 0x18, char value handle = 0x0014, uuid = 77777777-7777-7777-7777-777777777777
handle = 0x0017, char properties = 0x18, char value handle = 0x0018, uuid = 97979797-9797-9797-9797-979797979797

如果不稍微分析下这个结果的话,很难知道这个设备上包含哪些服务,这些服务包含哪些Characteristic,这些Characteristic的读写属性是咋样的,虽然作为技术宅需要具备这样的能力,但是清晰胜于模糊,简明胜于复杂,于是我想是不是可以写一个脚本把gatttool的输出美化一下呢?

于是这就是这篇博客的由来,我恰好会一点Python,于是就想到用Python来实现这个功能,由于Python水平不是很高,可能有些地方写得也相对冗余了些,先给大家看下成果吧

在这里插入图片描述

图看的出来,我使用的是树莓派_。现在,这样的输出是不是清楚多了,我的设备上有5个服务,其中前2个是系统服务,后面3个是用户定制的服务,每个服务包好几个Characteristic,这些Characteristic的读写属性也标记出来了,但是这里并没有对Properties作进一步的解析了,其实可以把他们解析成Write Notify之类的字符串的,以后有时间可能会更新下。

我在图中着重打印出了USER DESC HANDLECLIENT CONFIG HANDLE,因为这2个属性挺重要的,读一下USER DESC HANDLE会返回一个字符串,这个字符串是用户对于这个Characteristic的文字描述

$ gatttool -i hci0 -b 12:34:56:C2:9C:C7 --char-read -a 0x0011
Characteristic value/descriptor: 44 61 74 61 20 50 61 74 68 20 52 58 20 44 61 74 61 00

我们看下这段数字代表的字符串是什么:

$ python
>>> a="44 61 74 61 20 50 61 74 68 20 52 58 20 44 61 74 61".split(' ')
>>> "".join([chr(int("0x"+v, 16)) for v in a])
'Data Path RX Data'

就是我手机截图中的字符串

另一个CLIENT CONFIG HANDLE也很重要,如果设备的某个属性是Notify的,意思是允许设备给我们发数据,但是默认是不可以发数据的,必须要向这个属性写1才能开启这个数据发送扽功能,写0时关闭,我看到有些同学使用gatttool接收不到设备发来的数据,原因就在这里,具体教程可以看下我的另一篇博客(gatttool命令详解),会有非常详细的介绍

Python实现原理

先说下思路吧,主要是用了Pythonos.popen管道来调用gatttool命令,然后读取其输出,最后处理其输出结果的字符串

$ python
>>> import os
>>> output = os.popen("sudo gatttool -i hci0 -b <Device> --primary").read()

我通过这个os.popen分别调用了--primary, ----characteristics和--char-desc,把结果汇总起来,最后再输出结果,下面贴出我的源代码,亲测是可以直接运行的

#! /usr/bin/python

import re
import sys
import os

PRIMARY_SERVICE_UUID = "00002800-0000-1000-8000-00805f9b34fb"
SECOND_SERVICE_UUID = "00002801-0000-1000-8000-00805f9b34fb"
INCLUDED_SERVICE_UUID = "00002802-0000-1000-8000-00805f9b34fb"
CHARACTERISTICS_UUID = "00002803-0000-1000-8000-00805f9b34fb"

GATT_USER_DESC_UUID = "00002901-0000-1000-8000-00805f9b34fb"
GATT_CLIENT_CHARAC_UUID = "00002902-0000-1000-8000-00805f9b34fb"
GATT_SERVER_CHARAC_CFG_UUID = "00002903-0000-1000-8000-00805f9b34fb"

class Gatttool:
    def __init__(self, address):
        self.getPrimaryGattCommand = "sudo gatttool -i hci0 -b %s --primary" % address
        self.getCharacteristicsGattCommand = "sudo gatttool -i hci0 -b %s --characteristics" % address
        self.getCharDescGattCommand = "sudo gatttool -i hci0 -b %s --char-desc" % address

    def getAllPrimarys(self):
        primary = os.popen(self.getPrimaryGattCommand).read()
        #  primary = g_primary
        if primary:
            return primary.split('\n')
        return None

    def getAllCharacteristics(self):
        #  characteristics = g_characteristics
        characteristics = os.popen(self.getCharacteristicsGattCommand).read()
        if characteristics:
            return characteristics.split('\n')
        return None

    def getAllCharDesc(self):
        #  descs = g_descs
        descs = os.popen(self.getCharDescGattCommand).read()
        if descs:
            return descs.split('\n')
        return None

class Primary:
    def __init__(self, startHandle, endHandle, uuid):
        self.startHandle = startHandle
        self.endHandle = endHandle
        self.uuid = uuid
        self.characteristics =[]

    def addCharacteristics(self, characteristic):
        self.characteristics.append(characteristic)

    def __str__(self):
        return "[Primary] Service UUID: %s Start Handle: %s End Handle: %s" % (self.uuid, self.startHandle, self.endHandle)

class Characteristics:
    def __init__(self, charHandle, valueHandle, uuid, properties):
        self.charHandle = charHandle
        self.valueHandle = valueHandle
        self.properties = properties
        self.uuid = uuid
        self.userDescHandle = None
        self.clientConfigHandle = None

    def setUserDescHandle(self, handle):
        self.userDescHandle = handle

    def setClientConfigHandle(self, handle):
        self.clientConfigHandle = handle

    def __str__(self):
        return "[Characteristics] Value Handle: %s Char Handler: %s Properties: %s" % (self.valueHandle, self. charHandle, self.properties)

class CharDesc:
    def __init__(self, handle, uuid):
        self.handle = handle
        self.uuid = uuid

    def __str__(self):
        return "[CharDesc] UUID: %s Handler: %s" % (self.uuid, self.handle)

class PrettyPrint:
    def __init__(self, address):
        self.myPrimaryList = ParserUtil().primarysParser(Gatttool(address).getAllPrimarys())
        self.myCharacteristics = ParserUtil().characteristicsParser(Gatttool(address).getAllCharacteristics())
        self.myDescList = ParserUtil().descParser(Gatttool(address).getAllCharDesc())

        for char in self.myCharacteristics:
            service = self.findSerivceByHandle(char.valueHandle)
            service.addCharacteristics(char)
            self.searchUserDescByHandle(char)

        self.output()

    def getPrettyUUID(self, uuid):
        reString = "0000(?P<shortUUID>[0-9a-f]{4})-0000-1000-8000-00805f9b34fb"
        match = re.search(reString, uuid)
        if match:
            return match.groupdict()['shortUUID']
        return uuid

    def output(self):
        print('------------------------------------------------------------------------')
        for service in self.myPrimaryList:
            print("\033[32mService UUID:\033[0m %s CHAR NUM: %s" % (self.getPrettyUUID(service.uuid).upper(), len(service.characteristics)))
            index = 0
            for characteristic in service.characteristics:
                print("  CHAR%d UUID: %s " % (index, self.getPrettyUUID(characteristic.uuid).upper()))
                print("        USER DESC HANDLE: %s   CLIENT CONFIG HANDLE: %s" % (characteristic.userDescHandle, characteristic.clientConfigHandle))
                print("        VALUE CHAR HANDLE: %s    PROPERTIES: %s" % (characteristic.valueHandle, characteristic.properties))
                index += 1
            print('------------------------------------------------------------------------')

    def findSerivceByHandle(self, handle):
        for service in self.myPrimaryList:
            if int(handle, 16) >= int(service.startHandle, 16) and int(handle, 16) <= int(service.endHandle, 16):
                return service
        return None

    def findDescByHandle(self, handleNum):
        for desc in self.myDescList:
            if int(desc.handle, 16) == handleNum:
                return desc
        return None

    def nextDescByHandle(self, handleNum):
        for desc in self.myDescList:
            if int(desc.handle, 16) > handleNum:
                return desc, int(desc.handle, 16)
        return None, None

    def searchUserDescByHandle(self, char):
        handleNum = int(char.valueHandle, 16)
        desc = self.findDescByHandle(handleNum)
        while desc:
            if desc.uuid == PRIMARY_SERVICE_UUID or desc.uuid == CHARACTERISTICS_UUID:
                return
            if desc.uuid == GATT_USER_DESC_UUID:
                char.setUserDescHandle(desc.handle)
            elif desc.uuid == GATT_CLIENT_CHARAC_UUID:
                char.setClientConfigHandle(desc.handle)
            desc, handleNum = self.nextDescByHandle(handleNum)


class ParserUtil:
    def __init__(self):
        pass

    def primarysParser(self, primarys):
        self.primaryRe = 'attr handle = (?P<startHandle>0x[0-9a-f]{4}), \
end grp handle = (?P<endHandle>0x[0-9a-f]{4}) \
uuid: (?P<uuid>[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})'
        ret = []
        for primary in primarys:
            match = re.search(self.primaryRe, primary)
            if match:
                obj = match.groupdict()
                ret.append(Primary(obj['startHandle'], obj['endHandle'], obj['uuid']))

        return ret

    def characteristicsParser(self, characteristics):
        self.characteristicsRe = 'handle = (?P<charHandle>0x[0-9a-f]{4}), \
char properties = (?P<properties>0x[0-9a-f]{2}), \
char value handle = (?P<valueHandle>0x[0-9a-f]{4}), \
uuid = (?P<uuid>[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})'
        ret = []
        for characteristic in characteristics:
            match = re.search(self.characteristicsRe, characteristic)
            if match:
                obj = match.groupdict()
                ret.append(Characteristics(obj['charHandle'], obj['valueHandle'], obj['uuid'], obj['properties']))

        return ret

    def descParser(self, descs):
        self.descRe = 'handle = (?P<handle>0x[0-9a-f]{4}), \
uuid = (?P<uuid>[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})'
        ret = []
        for desc in descs:
            match = re.search(self.descRe, desc)
            if match:
                obj = match.groupdict()
                ret.append(CharDesc(obj['handle'], obj['uuid']))
        return ret

if __name__ == "__main__":
    if len(sys.argv) != 2:
        print("Invalid Input")
        exit(1)

    address = sys.argv[1]
    PrettyPrint(address)

有需要的同学可以拿过去亲自测试下,欢迎给我提意见~

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值