Qt基础 低功耗蓝牙通信

发布时间:2024年01月23日

?首先说下基础知识,蓝牙连接是分经典蓝牙和BLE(低功耗)蓝牙的,他们的通信方式不一样,经典蓝牙用的socket,BLE用的是服务与特征。平台式VS2019+Qt5.12.11,但是Qt支持蓝牙并不好,如果你们有其他方案的话,尽量使用其他方案,因为有些问题,博主也没解决。

添加头文件?

#include <QLowEnergyController>
#include <QLowEnergyService>
#include <QLowEnergyCharacteristic>
#include <QBluetoothUuid>
#include <QCoreApplication>

1.扫描蓝牙?

首先这里的扫描很坑,1.不能断点调式(会导致崩溃)。2.扫描蓝牙无法区分设备状态(获取的设备列表会把已配对和已连接放在一个设备列表中)。基本上这个步骤都劝退一大批人

// 扫描蓝牙
deviceDiscoveryAgent = new QBluetoothDeviceDiscoveryAgent(this);
//发现设备 会得到QBluetoothDeviceInfo对象通过其方法可以获得设备信息
	connect(deviceDiscoveryAgent, &QBluetoothDeviceDiscoveryAgent::deviceDiscovered, this, &CNWBluetooth::slotsdeviceDiscovered);
//扫描完成  根据得到QBluetoothDeviceInfo创建QLowEnergyController控制器
	connect(deviceDiscoveryAgent, &QBluetoothDeviceDiscoveryAgent::finished, this, &CNWBluetooth::slotsFinished);

2.找到相应服务创建控制器

//根据上面设备创建服务 我这边创建了一个Qlist存放
m_pControl = QLowEnergyController::createCentral(m_listDeviceInfo.at(i));
connect(m_pControl, &QLowEnergyController::connected, this, [=](){
        m_pControl->discoverServices();});
// 获取服务信息 其实就是uuid
connect(m_pControl, &QLowEnergyController::serviceDiscovered, this, &CNWBluetooth::slotsserviceDiscovered
// 服务扫描完成
connect(m_pControl, &QLowEnergyController::discoveryFinished, this, &CNWBluetooth::slotsdiscoveryFinished);
// 失败
void (QLowEnergyController:: * bleDeviceConnectionErrorOccurred)(QLowEnergyController::Error) = &QLowEnergyController::error;
connect(m_pControl, bleDeviceConnectionErrorOccurred, this, [=](QLowEnergyController::Error newError)
{qDebug() << newError;}); //设备连接出现错误
m_pControl->connectToDevice();

3.监听所需服务

这一步通过上一步获取到uuid列表创建服务,然后监听数据返回和发送

                m_pService = m_pControl->createServiceObject(m_listUuid.at(i), this);
				if (m_pService == NULL) {
					qDebug() << u8"创建服务失败";
				}
				else {
					qDebug() << u8"服务创建成功";
					//ui->textBrowser->append("服务创建成功");

					//监听服务状态变化
					connect(m_pService, SIGNAL(stateChanged(QLowEnergyService::ServiceState))
						, this, SLOT(slotsstateChanged(QLowEnergyService::ServiceState)));
					//服务的characteristic变化,有数据传来
					connect(m_pService, SIGNAL(characteristicChanged(QLowEnergyCharacteristic, QByteArray))
						, this, SLOT(slotscharacteristicChanged(QLowEnergyCharacteristic, QByteArray)));
					//描述符成功被写
					//connect(m_pService, SIGNAL(descriptorWritten(QLowEnergyDescriptor, QByteArray)), this, SLOT(mDescriptorWritten(QLowEnergyDescriptor, QByteArray)));
					//服务详情发现函数

					//触发服务详情发现函数
					m_pService->discoverDetails();

整体流程就是这么一个流程(搜索设备>>控制器>>服务>>特征(读还是写)),在说下断开,因为很多博文没提到断开,我查资料从控制器开始后面都是11对应的,如果你控制器失效了,那么这个服务监听也就失效了

Tip:关于Window下面蓝牙方案可以参靠这个博客?,更多的蓝牙类可以参考

关于上面所说的,获取不到蓝牙设备信息,目前方案是用python获取,其实还是用的windos api

#!/usr/bin/env python2

debug = 0

import json
from ctypes import HRESULT, resize, string_at, wstring_at, CDLL, windll, Structure, Union, POINTER, c_ulong, c_ulonglong, c_int, c_char, byref, sizeof, create_unicode_buffer, WinError, c_byte
from ctypes.wintypes import DWORD, WORD, BOOL, BOOLEAN, BYTE, USHORT, ULONG, WCHAR, HANDLE
import codecs

SAPI = windll.SetupAPI
BT = windll.BluetoothAPIs
BP = windll['bthprops.cpl']

def Appearance(a):
    app = {64:'Phone', 961:'Keyboard', 962:'Mouse', 963:'Joystick', 964:'Gamepad'}
    return app[a] if a in app else 'Unknown'

def BTClassName(c):
    ma = (c >> 8) & 0x1f
    mi = (c >> 2) & 0x3f
    app = { 2:{3:'Smartphone'}, 4:{1:'Headset', 4:'Microphone'}, 5:{1*16:'Keyboard',2*16:'Mouse',3*16:'Keyboard+Mouse'} }
    return app[ma][mi] if (ma in app and mi in app[ma]) else 'Unknown'

def new(structure, **kwargs):
    s = structure()
    for k, v in kwargs.items(): setattr(s, k, v)
    return s

class GUID(Structure):_fields_ = [('data1', DWORD), ('data2', WORD),('data3', WORD), ('data4', c_byte * 8)]
class SP_DEVINFO_DATA(Structure): _fields_ = [('cbSize', DWORD), ('ClassGuid', GUID), ('DevInst', DWORD), ('Reserved', POINTER(c_ulong))]
class SP_DEVICE_INTERFACE_DATA(Structure): _fields_ = [('cbSize', DWORD),('InterfaceClassGuid', GUID),('Flags', DWORD),('Reserved', POINTER(c_ulong))]
class PSP_DEVICE_INTERFACE_DETAIL_DATA(Structure): _fields_ = [('cbSize', DWORD), ('DevicePath',WCHAR * 1)]
class BTH_LE_UUID(Structure):_fields_ = [ ('isShortUuid', BOOL), ('ShortUuid', USHORT), ('LongUuid', GUID)]
class BTH_LE_GATT_SERVICE(Structure): _fields_ = [('ServiceUuid', BTH_LE_UUID),('AttributeHandle', USHORT)]

def parse_uuid(s):
    s = s.strip('{}').replace('-', '')
    return GUID(int(s[:8],16),int(s[8:12],16),int(s[12:16],16),(c_byte*8).from_buffer_copy(bytearray.fromhex(s[16:])))

class BTH_LE_GATT_CHARACTERISTIC(Structure): _pack_ = 4; _fields_ = [
    ('ServiceHandle', USHORT),
    ('CharacteristicUuid', BTH_LE_UUID),
    ('AttributeHandle', USHORT),
    ('CharacteristicValueHandle', USHORT),
    ('IsBroadcastable', BOOL),
    #('IsReadable', BOOLEAN),  #TODO: investigate structure alignment here
    #('IsWritable', BOOL),
    #('IsWritableWithoutResponse', BOOL),
    #('IsSignedWritable', BOOL),
    #('IsNotifiable', BOOL),
    #('IsIndicatable', BOOL),
    #('HasExtendedProperties', BOOL),
]

class BTH_LE_GATT_CHARACTERISTIC_VALUE(Structure): _fields_= [('DataSize', ULONG), ('Data', BYTE*1)]

BTH_LE_GATT_DESCRIPTOR_TYPE = BYTE

class BTH_LE_GATT_DESCRIPTOR(Structure): _fields_ = [
    ('ServiceHandle', USHORT),
    ('CharacteristicHandle', USHORT),
    ('DescriptorType', BTH_LE_GATT_DESCRIPTOR_TYPE),
    ('DescriptorUuid', BTH_LE_UUID),
    ('AttributeHandle', USHORT)
]

class BTH_LE_GATT_DESCRIPTOR_VALUE(Structure): _fields_ = [('DescriptorType', USHORT),
    ('DescriptorUuid', BTH_LE_UUID),
    ('DataSize', ULONG),
    ('Data', WCHAR*1)
]

DIGCF_PRESENT = 0x02
DIGCF_DEVINTERFACE = 0x10

GENERIC_WRITE = 1073741824
GENERIC_READ = -2147483648
FILE_SHARE_READ = 1
FILE_SHARE_WRITE = 2
OPEN_EXISTING = 3

BLUETOOTH_GATT_FLAG_NONE = 0
BLUETOOTH_GATT_FLAG_FORCE_READ_FROM_DEVICE = 0x00000004

def DeviceConnect(path, db, field, cid):
    if debug: print('Connecting to:', path)

    cached = 1
    device_found = -1
    for j in range(len(db)):
        address = db[j]['hwid'].split('_')[1]
        if address in path:
            device_found = j
            cached = db[j]['status'] != 'connected'
    if device_found<0:
        return db

    hDevice = windll.kernel32.CreateFileW(path, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, None, OPEN_EXISTING, 0, None)
    ServicesBufferCount = USHORT()
    BT.BluetoothGATTGetServices(hDevice, 0, None, byref(ServicesBufferCount), BLUETOOTH_GATT_FLAG_NONE)
    ServicesBuffer = new(BTH_LE_GATT_SERVICE * ServicesBufferCount.value)   
    BT.BluetoothGATTGetServices(hDevice, ServicesBufferCount, byref(ServicesBuffer), byref(ServicesBufferCount), BLUETOOTH_GATT_FLAG_NONE)

    for Service in ServicesBuffer:
        CharacteristicsBufferCount = USHORT(0)
        hr = BT.BluetoothGATTGetCharacteristics(hDevice, byref(Service), 0, None, byref(CharacteristicsBufferCount), BLUETOOTH_GATT_FLAG_NONE)
        CharacteristicsBuffer = new(BTH_LE_GATT_CHARACTERISTIC * CharacteristicsBufferCount.value)
        CharacteristicsBufferActual = USHORT(0)
        BT.BluetoothGATTGetCharacteristics(hDevice, byref(Service), CharacteristicsBufferCount, byref(CharacteristicsBuffer), byref(CharacteristicsBufferActual), BLUETOOTH_GATT_FLAG_NONE)

        for Characteristic in CharacteristicsBuffer:
            uuid = Characteristic.CharacteristicUuid.ShortUuid
            charValueDataSize = USHORT()
            hr = BT.BluetoothGATTGetCharacteristicValue(
                hDevice,
                byref(Characteristic),
                0,
                None,
                byref(charValueDataSize),
                BLUETOOTH_GATT_FLAG_NONE if cached else BLUETOOTH_GATT_FLAG_FORCE_READ_FROM_DEVICE)

            pCharValueBuffer = BTH_LE_GATT_CHARACTERISTIC_VALUE()
            resize(pCharValueBuffer, charValueDataSize.value)

            hr = BT.BluetoothGATTGetCharacteristicValue(
                hDevice,
                byref(Characteristic),
                charValueDataSize,
                byref(pCharValueBuffer),
                None,
                BLUETOOTH_GATT_FLAG_NONE if cached else BLUETOOTH_GATT_FLAG_FORCE_READ_FROM_DEVICE)

            StringValue = string_at(byref(pCharValueBuffer, sizeof(ULONG)))
            arr = bytearray(StringValue)

            if uuid==0x2A19: # battery level
                value = 0 if len(arr)<1 else arr[0]
            elif uuid==0x2A01: # appearance
                value = int(codecs.encode(arr, 'hex'), 16)
                value = Appearance(value)
            else: # string
                value = str(StringValue.decode(errors='ignore'))

            if debug: print ( '\t%X' % uuid, value )

            if device_found!=-1 and uuid==cid: db[device_found][field] = value

    return db


DEVPROPGUID = GUID
DEVPROPID = ULONG
class DEVPROPKEY(Structure):
    _fields_ = [
        ('fmtid', DEVPROPGUID),
        ('pid', DEVPROPID)]

LPTSTR = POINTER(c_char)
WBAse = windll.Kernel32
ERROR_INSUFFICIENT_BUFFER = 0x7A
ERROR_INVALID_DATA = 0xD
GetLastError = WBAse.GetLastError

SPDRP_FRIENDLYNAME = DWORD(0x0000000C)
SPDRP_DEVICEDESC = DWORD(0x00000000)
SPDRP_ENUMERATOR_NAME = DWORD(0x00000016)
SPDRP_HARDWAREID = DWORD(0x00000001)

def GetConnectedStatus(hdi, devinfo_data):
    DEVPROPTYPE = ULONG
    DN_DEVICE_DISCONNECTED = 0x2000000
    ulPropertyType = DEVPROPTYPE()
    dwSize = DWORD()
    devst = (ULONG*1)()
    key = DEVPROPKEY(parse_uuid('{4340a6c5-93fa-4706-972c-7b648008a5a7}'), 2)
    SAPI.SetupDiGetDevicePropertyW(hdi, byref(devinfo_data), byref(key),byref(ulPropertyType),
        byref(devst), sizeof(devst), byref(dwSize), DWORD(0))
    return not (devst[0] & DN_DEVICE_DISCONNECTED)

# 69400001-B5A3-F393-E0A9-E50E24DCCA99  e0cbf06c-cd8b-4647-bb8a-263b43f0f974
def GetDeviceNames(uuid='{e0cbf06c-cd8b-4647-bb8a-263b43f0f974}'):
    db = []
    hdi = SAPI.SetupDiGetClassDevsW(byref(parse_uuid(uuid)), None, None, DIGCF_PRESENT)
    devinfo_data = new(SP_DEVINFO_DATA, cbSize=sizeof(SP_DEVINFO_DATA))
    i = 0
    while SAPI.SetupDiEnumDeviceInfo(hdi, i, byref(devinfo_data)):
        i += 1
        hwid = get_device_info(hdi, i, devinfo_data, SPDRP_HARDWAREID)
        if not (hwid.startswith('BTHENUM\\Dev_') or hwid.startswith('BTHLE\\Dev_')):
            continue
        name = get_device_info(hdi, i, devinfo_data, SPDRP_FRIENDLYNAME)
        desc = get_device_info(hdi, i, devinfo_data, SPDRP_DEVICEDESC)
        enum = get_device_info(hdi, i, devinfo_data, SPDRP_ENUMERATOR_NAME)
        status = 'connected' if GetConnectedStatus(hdi, devinfo_data) else 'paired'
        db.append( { 'name':name, 'status':status, 'hwid':hwid } )
    return db


def get_device_info(hdi, i, devinfo_data, propertyname):
    data_t = DWORD()
    buffer = LPTSTR()
    buffersize = DWORD(0)
    while ( not SAPI.SetupDiGetDeviceRegistryPropertyW( hdi, byref(devinfo_data), propertyname, data_t, byref(buffer), buffersize, byref(buffersize))):
        err = GetLastError()
        if err == ERROR_INSUFFICIENT_BUFFER:
            buffer = create_unicode_buffer(buffersize.value)
        elif err == ERROR_INVALID_DATA:
            break
        else:
            raise WinError(err)
    try:
        return str(buffer.value)
    except:
        return ''

def GetDevicePaths(uuid, db, field, cid):
    guid = parse_uuid(uuid)
    hdi = SAPI.SetupDiGetClassDevsW(byref(guid), None, None, DIGCF_DEVINTERFACE | DIGCF_PRESENT)
    dd = SP_DEVINFO_DATA(sizeof(SP_DEVINFO_DATA))
    did = SP_DEVICE_INTERFACE_DATA(sizeof(SP_DEVICE_INTERFACE_DATA))
    i = 0
    while SAPI.SetupDiEnumDeviceInterfaces(hdi, None, byref(guid), i, byref(did)):
        i += 1
        size = DWORD(0)
        SAPI.SetupDiGetDeviceInterfaceDetailW(hdi, byref(did), None, 0, byref(size), 0)
        didd = PSP_DEVICE_INTERFACE_DETAIL_DATA()
        resize(didd, size.value)
        didd.cbSize = sizeof(PSP_DEVICE_INTERFACE_DETAIL_DATA) - sizeof(WCHAR * 1)
        SAPI.SetupDiGetDeviceInterfaceDetailW(hdi, byref(did), byref(didd), size, byref(size), byref(dd))
        path = wstring_at(byref(didd, sizeof(DWORD)))
        db = DeviceConnect(path, db, field, cid)
    return db

BTH_ADDR = c_ulonglong

class SYSTEMTIME(Structure):
    _fields_ = [
        ("wYear", WORD),
        ("wMonth", WORD),
        ("wDayOfWeek", WORD),
        ("wDay", WORD),
        ("wHour", WORD),
        ("wMinute", WORD),
        ("wSecond", WORD),
        ("wMilliseconds", WORD)]

class BLUETOOTH_ADDRESS(Union):
    _fields_ = [
        ("ullLong", BTH_ADDR),
        ("rgBytes", BYTE * 6)]

class BLUETOOTH_DEVICE_SEARCH_PARAMS(Structure):
    _fields_ = [
        ("dwSize", DWORD),
        ("fReturnAuthenticated", BOOL),
        ("fReturnRemembered", BOOL),
        ("fReturnUnknown", BOOL),
        ("fReturnConnected", BOOL),
        ("fIssueInquiry", BOOL),
        ("cTimeoutMultiplier", c_ulong),
        ("hRadio", HANDLE)]

class BLUETOOTH_DEVICE_INFO(Structure):
    _fields_ = [
        ("dwSize", DWORD),
        ("Address", BLUETOOTH_ADDRESS),
        ("ulClassofDevice", c_ulong),
        ("fConnected", BOOL),
        ("fRemembered", BOOL),
        ("fAuthenticated", BOOL),
        ("stLastSeen", SYSTEMTIME),
        ("stLastUsed", SYSTEMTIME),
        ("szName", WCHAR * 248)]

def GetClassicDevices(db, field):

    BLUETOOTH_DEVICE_FIND = HANDLE
    sp = BLUETOOTH_DEVICE_SEARCH_PARAMS()
    df = BLUETOOTH_DEVICE_FIND()
    di = BLUETOOTH_DEVICE_INFO()

    sp.dwSize = sizeof(sp)
    sp.hRadio = None
    sp.fReturnAuthenticated = 1
    sp.fReturnRemembered = 1
    sp.fReturnConnected = 1
    sp.fReturnUnknown = 1
    sp.fIssueInquiry = 0
    sp.cTimeoutMultiplier = 0

    di.dwSize = sizeof(di);
    df = BP.BluetoothFindFirstDevice(byref(sp), byref(di))
    while True:

        for j in range(len(db)):
            if di.szName in db[j]['name']: db[j][field] = BTClassName(di.ulClassofDevice)

        if not BP.BluetoothFindNextDevice(df, byref(di)):
            break

    return db

def main():
    db = GetDeviceNames()
    db = GetClassicDevices(db, 'class')
    db = GetDevicePaths('{0000180F-0000-1000-8000-00805F9B34FB}', db, 'battery_level', 0x2A19)
    db = GetDevicePaths('{00001800-0000-1000-8000-00805F9B34FB}', db, 'appearance', 0x2A01)
    db = GetDevicePaths('{0000180A-0000-1000-8000-00805F9B34FB}', db, 'manufacturer', 0x2A29)
    db = GetDevicePaths('{0000180A-0000-1000-8000-00805F9B34FB}', db, 'firmware', 0x2A26)
    db = GetDevicePaths('{0000180A-0000-1000-8000-00805F9B34FB}', db, 'serial', 0x2A25)

    if not debug:
        print ( json.dumps(db, sort_keys=True, indent=4) )

if __name__=='__main__':
    main()

文章来源:https://blog.csdn.net/qq_31565379/article/details/135764206
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。