小白式的介绍,很详细了,很多主要内容写在程序的注释里,慢慢看

下面是我的源码


源码打不开的话可以试试下图的操作,之后电机确定,可能是加图标搞的,但是我没遇到过,需要的可以联系897741243@qq.com

QT 连接android连接红米手机_BLE


收发数据有问题可以查看

QT 连接android连接红米手机_QT_02

如果服务不是单个类型的服务,这里的等号是没法判断出来的,添加下图程序可以显示出BLE发送出来的,用这一句判断是不是有多种类型一体的特征值

QT 连接android连接红米手机_BLE_03


pro文件添加

QT       += bluetooth

头文件

#include <QtBluetooth/qbluetoothlocaldevice.h> // 本地设备信息
#include <QBluetoothDeviceDiscoveryAgent>      // 设备搜寻
#include <QBluetoothDeviceInfo>                // 设备信息
#include <QLowEnergyController>                // 设备连接
#include <QLowEnergyService>                   // 数据接收、发送

操作BLE与普通蓝牙不同,普通蓝牙连接设备后直接收发就可以
BLE蓝牙需要连接设备后搜寻设备服务,连接服务,再查找特征值,根据不同特征值设计不同功能
后面篇章分为设备、服务、特征值

程序主要结构就是以上三个部分有固定的槽函数入口,入口内各有一套固定的信号和槽函数的组合
这里先提前写明H文件里的变量,不再根据功能划分了

private:
    Ui::MainWindow *ui;
    QBluetoothLocalDevice *m_plocalDevice;                   // 对本地蓝牙进行操作,如打开、关闭……
    QBluetoothDeviceDiscoveryAgent *m_pdeviceDiscoveryAgent; // 用于蓝牙设备搜寻
    QLowEnergyController *m_pcontrol;                        // 用于设备连接
    QLowEnergyService *m_pservice;                           // 用于数据接收、发送
    QLowEnergyCharacteristic m_readCharacteristic;           // "读" 服务特性
    QLowEnergyCharacteristic m_writeCharacteristic;          // "写" 服务特性
    QLowEnergyCharacteristic m_notifyCharacteristic;         // 通知 服务特性

    QLowEnergyService::WriteMode m_writeMode;                // "写"特性模式
    QLowEnergyDescriptor m_notificationDesc;                 // 用于存储BLE描述符信息

    QList<QBluetoothUuid> m_servicesUuid; // 服务uuid
    QList<DeviceInfo*> m_devices;         // 搜索到的蓝牙设备信息

    QStringList m_devicesNames;           // 搜索到的设备名称
    QStringList m_services;               // 服务列表

设备

首先将读取的设备信息归为一个类DeviceInfo

class DeviceInfo: public QObject
{
    Q_OBJECT

public:
    DeviceInfo(const QBluetoothDeviceInfo &device);
    void setDevice(const QBluetoothDeviceInfo &device);//设定当前通讯目标设备
    QString getName() const { return m_device.name(); }//获取设备名称
    QString getAddress() const;//获取MAC地址
    int getRssi() const;  //信号强度
    QBluetoothDeviceInfo getDevice() const;

signals:
    void deviceChanged();   //设定设备完成

private:
    QBluetoothDeviceInfo m_device;
};
DeviceInfo::DeviceInfo(const QBluetoothDeviceInfo &info):
    QObject(), m_device(info)
{
}
QBluetoothDeviceInfo DeviceInfo::getDevice() const
{
    return m_device;
}
//MAC地址
QString DeviceInfo::getAddress() const
{
#ifdef Q_OS_MAC
    // workaround for Core Bluetooth:
    return m_device.deviceUuid().toString();
#else
    return m_device.address().toString();
#endif
}
//信号强度
int DeviceInfo::getRssi() const
{
    return m_device.rssi();
}

void DeviceInfo::setDevice(const QBluetoothDeviceInfo &device)
{
    m_device = device;
    emit deviceChanged();
}

检测本地蓝牙并开始搜索

private: 
    QBluetoothLocalDevice *m_plocalDevice;                   // 对本地蓝牙进行操作,如打开、关闭……
	QBluetoothDeviceDiscoveryAgent *m_pdeviceDiscoveryAgent; // 用于蓝牙设备搜寻
/*检测本地蓝牙状态*/
    m_plocalDevice = new QBluetoothLocalDevice(this);
    if(m_plocalDevice->hostMode() == QBluetoothLocalDevice::HostPoweredOff)
    {
        // 如果本地蓝牙处于关闭状态,则打开蓝牙
        m_plocalDevice->powerOn();
    }
    /*自动搜寻设备*/
    m_pdeviceDiscoveryAgent = new QBluetoothDeviceDiscoveryAgent(this);
    m_pdeviceDiscoveryAgent ->setLowEnergyDiscoveryTimeout(5000);//设置BLE的搜索时间
    connect(m_pdeviceDiscoveryAgent, SIGNAL(deviceDiscovered(const QBluetoothDeviceInfo&)),   //扫描发现新设备
            this, SLOT(on_addDevice(const QBluetoothDeviceInfo&)));
    connect(m_pdeviceDiscoveryAgent, SIGNAL(error(QBluetoothDeviceDiscoveryAgent::Error)),    //错误
            this, SLOT(on_deviceScanError(QBluetoothDeviceDiscoveryAgent::Error)));
    connect(m_pdeviceDiscoveryAgent, SIGNAL(finished()),this, SLOT(on_scanFinished()));  //扫描完成

使用的信号

void deviceDiscovered(const QBluetoothDeviceInfo &info); //发现新设备
 void error(QBluetoothDeviceDiscoveryAgent::Error error);  //查找设备时出错
 void finished();        //差找完成

对应的槽函数(需要自己写的)

//扫描到新设备,读取设备信息
void MainWindow::on_addDevice(const QBluetoothDeviceInfo &device)
{
    if (device.coreConfigurations() & QBluetoothDeviceInfo::LowEnergyCoreConfiguration)  //筛选只要BLE设备
    {
        QString strDeviceName = device.name();      // 设备名称
        //        QBluetoothAddress addr = device.address();  // 设备地址
        //        qint16 iRssi = device.rssi();//信号强度

        //空名的不要
        if("" == strDeviceName) return;
        //同MAC的不要
        for(int i=0;i<m_devices.size();i++)
        {
            if(m_devices.at(i)->getAddress() == device.address().toString())
            {
                return;
            }
        }
        m_devicesNames.append(strDeviceName);    //记录搜索到的设备名
        DeviceInfo *dev = new DeviceInfo(device);   // 设备信息
        m_devices.append(dev);           //记录设备信息
        qDebug() << "Discovered LE Device name: " << device.name()<< " Address: "<< device.address().toString()<<QString::number(device.rssi());

        //发现并更新设备名称ComboBox
        QString label = QString("%1 \r\n%2").arg(device.name()).arg(device.address().toString());  //这里为了好看,记录设备名和设备地址
        QList<QListWidgetItem *> items = ui->Device_List->findItems(label, Qt::MatchExactly);      //显示备名和设备地址

        if (items.empty()) {
            QListWidgetItem *item = new QListWidgetItem(label);
            QBluetoothLocalDevice::Pairing pairingStatus = m_plocalDevice->pairingStatus(device.address());
            /* 蓝牙状态pairingStatus,Pairing枚举类型 0:Unpaired没配对 1:Paired配对但没授权 2:AuthorizedPaired配对且授权 */
            if (pairingStatus == QBluetoothLocalDevice::Paired || pairingStatus == QBluetoothLocalDevice::AuthorizedPaired )
            {
                item->setForeground(QColor(Qt::green));
            } else{
                item->setForeground(QColor(Qt::black));
            }
            ui->Device_List->addItem(item);
            //切换底色
            static bool Background=true;
            if(Background)
            {
                item->setBackground(QColor(Qt::darkGray));
            }else{
                item->setBackground(QColor(Qt::white));
            }
            Background=!Background;
        }
    }
}
//扫描出错
void MainWindow::on_deviceScanError(QBluetoothDeviceDiscoveryAgent::Error error)
{
    ui->textEdit->append(QString("错误:%1").arg(error));
}
//扫描完成后槽函数
void MainWindow::on_scanFinished()
{
    m_pdeviceDiscoveryAgent->stop();
    ui->textEdit->append("扫描完成");
}

手动扫描设备

//手动扫描
    connect(ui->Scan_Device,&QPushButton::clicked,[=]{
        qDeleteAll(m_devices);                             //删除所有先前的设备信息
        m_devices.clear();                                 //删除所有先前的设备信息 
        m_pdeviceDiscoveryAgent->start();    //开始扫描
        ui->Device_List->clear();            //清除显示
        ui->textEdit->append("正在搜索...");  //提示信息
        ui->List_Box->setCurrentIndex(0);//开始扫描设备,切换到设备列表
    });

到这里第一组信号槽函数结束,完成BLE设备查找筛选并显示

连接设备

上一步将设备名显示在listwidget控件中,可以使用该控件的双击或者单独一个按钮,还有断开设备连接

m_pcontrol->disconnectFromDevice();//断开连接
m_pcontrol->connectToDevice(); //连接设备
/* 双击listwidget的项目,触发连接蓝牙的槽 */
    connect(ui->Device_List, SIGNAL(itemActivated(QListWidgetItem*)),this, SLOT(connect_Device()));
    //连接设备
    connect(ui->Link_Device,&QPushButton::clicked,[=]{
        connect_Device();
    });
        //断开设备连接
    connect(ui->disLink_Device,&QPushButton::clicked,[=]{
        m_pcontrol->disconnectFromDevice();
        ui->Server_List->clear();
    });

itemActivated(QListWidgetItem*)是双击listwidget的信号
槽函数connect_Device

QLowEnergyController *m_pcontrol;                        // 用于设备连接
//连接BLE
void MainWindow::connect_Device()
{
    m_pdeviceDiscoveryAgent->stop();// 停止搜寻设备
    ui->Server_List->clear();       //重置服务信息
    ui->textEdit->append(QString::fromLocal8Bit("开始连接设备"));
    if(m_devices.isEmpty())            //如果没有连接设备提前终止
    {
        ui->textEdit->append(QString::fromLocal8Bit("没有设备"));
        return;
    }
    if(ui->Device_List->currentRow()!=-1)   //如果设备选取没有选取也提前终止,不然会闪退
    {
        DeviceInfo* currentDevice= m_devices[ui->Device_List->currentRow()];   //读取要读取服务的设备信息
        qDebug()<<currentDevice->getName()<<"----"<<currentDevice->getAddress();
        m_pcontrol = new QLowEnergyController(currentDevice->getDevice(), this);
        //每找到一个服务就会发出此信号
        connect(m_pcontrol, SIGNAL(error(QLowEnergyController::Error)),this, SLOT(on_controllerError(QLowEnergyController::Error)));
        connect(m_pcontrol, SIGNAL(disconnected()),this, SLOT(on_deviceDisconnected()));
        connect(m_pcontrol, SIGNAL(connected()),this, SLOT(on_deviceConnected()));
        connect(m_pcontrol, SIGNAL(discoveryFinished()),this, SLOT(on_serviceScanDone()));
        // 连接设备
        m_pcontrol->connectToDevice();
    }

}

这里要使用的信号

void error(QLowEnergyController::Error newError);  //连接失败
	void connected();       //连接成功
    void disconnected();    //连接失败或断开连接
    void discoveryFinished();   //连接并找到服务

由于这里连接设备成功后我直接开始搜索服务,所以connected和discoveryFinished看起来有点重复
开始搜索服务包含在 on_serviceScanDone 函数内

后续需要的槽函数,需要自己写

//连接出错
void MainWindow::on_controllerError(QLowEnergyController::Error error)
{
    switch(error)
    {
    case QLowEnergyController::NoError:{ui->textEdit->append("NoError");break;}
    case QLowEnergyController::UnknownError:{ui->textEdit->append("UnknownError");break;}
    case QLowEnergyController::UnknownRemoteDeviceError:{ui->textEdit->append("UnknownRemoteDeviceError");break;}
    case QLowEnergyController::NetworkError:{ui->textEdit->append("NetworkError");break;}
    case QLowEnergyController::InvalidBluetoothAdapterError:{ui->textEdit->append("InvalidBluetoothAdapterError");break;}
    case QLowEnergyController::ConnectionError:{ui->textEdit->append("ConnectionError");break;}
    case QLowEnergyController::AdvertisingError:{ui->textEdit->append("AdvertisingError");break;}
    case QLowEnergyController::RemoteHostClosedError:{ui->textEdit->append("RemoteHostClosedError");break;}
    case QLowEnergyController::AuthorizationError:{ui->textEdit->append("AuthorizationError");break;}
    default: {ui->textEdit->append("Unknow error");break;}
    }
}
// 设备断开
void MainWindow::on_deviceDisconnected()
{
    ui->textEdit->append(tr("设备已断开连接!!!"));
    ui->List_Box->setCurrentIndex(0); //断开连接切换回设备列表
}
// 设备连接成功
void MainWindow::on_deviceConnected()
{
    ui->textEdit->append(tr("成功连接设备!!!"));
    m_servicesUuid.clear();
    m_services.clear();
    ui->List_Box->setCurrentIndex(1); //连接成功切换到服务器列表
    //发现服务Services
    m_pcontrol->discoverServices();
}

// 服务搜寻完毕,更新服务下拉框
void MainWindow::on_serviceScanDone()
{
    m_servicesUuid = m_pcontrol->services();
    if(m_servicesUuid.isEmpty())
    {
        ui->Server_List->addItem("无服务");
    }
    else
    {
        ui->Server_List->addItem(QString("服务数:%1").arg(m_servicesUuid.length()));
        for(int i=0;i<m_servicesUuid.length();i++)
        {
            QString UUID =m_servicesUuid.at(i).toString().remove('{').remove('}');//获取UUID  去除{} 生成的UUID是没有{}的
            int UUID_Num=UUID_Find.indexOf(UUID);   //查表UUID是哪类设备,没找也能用,下面这步为了好看
            if(UUID_Num!=-1)
            {
                ui->Server_List->addItem(QString("%1:%2\n%3").arg(i).arg(UUID).arg(UUID_Find.at(UUID_Num+1)));

            }else{
                ui->Server_List->addItem(QString("%1:%2\n%3").arg(i).arg(UUID).arg("Unknown"));
            }
            qDebug()<<"m_servicesUuid"<<m_servicesUuid.at(i).toString( );
        } 
    }
}

到这一步为止,完成连接设备并查找存在的服务

服务

连接服务使用的是UUID,这里从显示的控件中截取出UUID的字符格式,然后使用QUuid生成实际的UUID,再用m_pcontrol->createServiceObject函数连接服务

连接服务

//双击连接服务器
    connect(ui->Server_List, SIGNAL(itemActivated(QListWidgetItem*)),this, SLOT(connect_Server(QListWidgetItem*)));
    //按钮连接服务
    connect(ui->Link_Server,&QPushButton::clicked,[=]{
        if(ui->Server_List->currentRow()!=-1)//先确认有设备被选择
        {
            connect_Server(ui->Server_List->currentItem());
        }
    });

槽函数 主要就是根据Qstring显示的UUID使用QUUID生成实际的UUID,重点在后面

void MainWindow::connect_Server(QListWidgetItem* Item)
{
    qDebug()<<"Item"<<Item;
    //连接服务使用的是UUID,这里从显示的控件中截取出UUID的字符格式,然后使用QUuid生成实际的UUID,再用m_pcontrol->createServiceObject函数连接服务
    if((Item->text().indexOf('\n')!=-1)&&(Item->text().indexOf(':')!=-1))
    {
        QString text = Item->text();
        ui->textEdit->append(QString("连接服务器"));
        //qDebug()<<"m_servicesUuid"<<m_servicesUuid.at(text.toInt());
        text = Item->text();
        text=text.left(text.indexOf('\n'));
        text.remove(0,text.lastIndexOf(':')+1);
        if(QUuid(text).toString()!="00000000-0000-0000-8000-000000000000")
        {
           update_currentService(QUuid(text));
        }else{
            ui->textEdit->append(QString("请选择服务器"));
        }
    }
}
//连接服务
void MainWindow::update_currentService(QBluetoothUuid servicesUuid)
{
    // 创建Service UUID所表示的服务实例
    m_pservice = m_pcontrol->createServiceObject(servicesUuid, this);
    connect(m_pservice, SIGNAL(stateChanged(QLowEnergyService::ServiceState)),this, SLOT(on_serviceStateChanged(QLowEnergyService::ServiceState)));
    connect(m_pservice, SIGNAL(characteristicChanged(QLowEnergyCharacteristic,QByteArray)),this, SLOT(on_characteristicChanged(QLowEnergyCharacteristic,QByteArray)));
    connect(m_pservice, SIGNAL(characteristicRead(QLowEnergyCharacteristic,QByteArray)),this, SLOT(on_characteristicRead(QLowEnergyCharacteristic,QByteArray)));
    connect(m_pservice, SIGNAL(characteristicWritten(QLowEnergyCharacteristic,QByteArray)),this, SLOT(on_characteristicWrite(QLowEnergyCharacteristic,QByteArray)));
    connect(m_pservice, SIGNAL(error(QLowEnergyService::ServiceError)),this, SLOT(on_serviceError(QLowEnergyService::ServiceError)));
    if(m_pservice->state() == QLowEnergyService::DiscoveryRequired)
    {
        ui->textEdit->append("查找特性");
        m_pservice->discoverDetails();//查找特性
    }
    else
    {
        ui->textEdit->append("未同步");
        searchCharacteristic();
    }
}

这里的信号槽函数最多
直接进入特征值详解,

特征值

对这个我不是很清楚,现在是一知半解的用着

void stateChanged(QLowEnergyService::ServiceState newState);        //服务被发现
void characteristicChanged(const QLowEnergyCharacteristic &info,const QByteArray &value); //Read服务值和Notify服务值都会触发 
void characteristicRead(const QLowEnergyCharacteristic &info, const QByteArray &value);//read服务,即app收到read服务值的信息时会触发这个,相当于串口接收
void characteristicWritten(const QLowEnergyCharacteristic &info,const QByteArray &value);//write写完后会触发
void error(QLowEnergyService::ServiceError error);

经过update_currentService 函数后,需要的槽函数

void MainWindow::searchCharacteristic()
{
    if(m_pservice)
    {
        foreach (QLowEnergyCharacteristic c, m_pservice->characteristics() )
        {
            ui->List_Box->setCurrentIndex(2);
            //ui->Service_List->addItem(c.uuid().toString());
            //c.properties()   //类型
            //QLowEnergyCharacteristic::PropertyType
            if(c.isValid())
            {
                ui->textEdit->append(QString(c.uuid().toString()));
                if (((c.properties() == QLowEnergyCharacteristic::WriteNoResponse) ||
                     (c.properties() == QLowEnergyCharacteristic::Write)))
                {
                    m_writeCharacteristic = c;
                    if((c.properties() & QLowEnergyCharacteristic::WriteNoResponse))
                    {
                        m_writeMode = QLowEnergyService::WriteWithoutResponse;
                        ui->textEdit->append("WriteWithoutResponse");
                        ui->Service_List->addItem(QString("%1\n%2").arg(c.uuid().toString()).arg("WriteWithoutResponse"));
                    }
                    else
                    {
                        m_writeMode = QLowEnergyService::WriteWithResponse;
                        ui->textEdit->append("WriteWithResponse");
                        ui->Service_List->addItem(QString("%1\n%2").arg(c.uuid().toString()).arg("WriteWithResponse"));
                    }
                }else{
                    if ((c.properties() == QLowEnergyCharacteristic::Read))
                    {
                        m_readCharacteristic = c;
                        ui->textEdit->append("Read");
                        ui->Service_List->addItem(QString("%1\n%2").arg(c.uuid().toString()).arg("Read"));
                    }else{
                        if ((c.properties() == QLowEnergyCharacteristic::Notify))
                        {
                            m_notifyCharacteristic = c;
                            ui->textEdit->append("Notify");
                            ui->Service_List->addItem(QString("%1\n%2").arg(c.uuid().toString()).arg("Notify"));
                        }
                    }
                }

                m_notificationDesc = c.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration);
                if (m_notificationDesc.isValid())
                {
                    m_pservice->writeDescriptor(m_notificationDesc, QByteArray::fromHex("0100"));
                }
            }
        }
    }
}

void MainWindow::dataReceived(QByteArray value)
{
    ui->Re_Data->setText(QString("dataReceived:%1").arg(QString(value)));
    ui->Re_Data->append(QString("HEX:%1").arg(QString(value.toHex().toUpper())));
}
void MainWindow::Notify_Data(QByteArray value)
{
    ui->NOTIFY->setText(value.toHex().toUpper());
}
void MainWindow::write(const QByteArray &data)
{
    if(m_pservice && m_writeCharacteristic.isValid())
    {
        m_pservice->writeCharacteristic(m_writeCharacteristic, data, m_writeMode);
    }else{
        qDebug()<<"发送失败";
    }
}

// 服务被发现
void MainWindow::on_serviceStateChanged(QLowEnergyService::ServiceState s)
{

    if (s == QLowEnergyService::ServiceDiscovered)
    {
        ui->textEdit->append("服务被发现");
        searchCharacteristic();
    }
}
void MainWindow::on_characteristicChanged(const QLowEnergyCharacteristic &c,
                              const QByteArray &value)
{
    qDebug() << "Characteristic Changed: " << value;
    //qDebug() <<c.name()<<"|"<<c.uuid()<<"|"<<c.value()<<"|"<<c.handle()<<"|"<<c.isValid()<<"|"<<c.properties();
    //qDebug() << "uuid: " << c.uuid();
    //ui->textEdit->append(QString("Characteristic Changed: %1 %2").arg(value.toInt()).arg(c.uuid().toString()));
    if(c.properties()==QLowEnergyCharacteristic::Notify)
    {
        Notify_Data(value);
    }else{
        if(c.properties()==QLowEnergyCharacteristic::Read)
        {
            dataReceived(value);
        }else{
            ui->textEdit->append("Unkown characteristicChanged");
        }
    }


}

void MainWindow::on_characteristicRead(const QLowEnergyCharacteristic &c,
                           const QByteArray &value)
{
    qDebug() << "Characteristic Read: " << value.toHex();
    qDebug() << "uuid: " << c.uuid();
    ui->textEdit->append(QString("Characteristic Read:|%1|%2").arg(QString(value.toHex())).arg(c.uuid().toString()));
    dataReceived(value);
}

void MainWindow::on_characteristicWrite(const QLowEnergyCharacteristic &c,
                            const QByteArray &value)
{
    qDebug() << "Characteristic Written: " << value;
    qDebug() << "uuid: " << c.uuid();
}
void MainWindow::on_serviceError(QLowEnergyService::ServiceError e)
{
    ui->textEdit->append(QString("serviceError %1").arg(e));
}

重点是searchCharacteristic函数 对查找出来的特征值分类,并分配相应的的 QLowEnergyCharacteristic
QLowEnergyCharacteristic是后续服务特性的主要部分

QLowEnergyCharacteristic m_readCharacteristic;           // "读" 服务特性
    QLowEnergyCharacteristic m_writeCharacteristic;          // "写" 服务特性
    QLowEnergyCharacteristic m_notifyCharacteristic;         // 通知 服务特性

分类依据就是 c.properties() 这里返回的是个枚举,可以直接跳转查看,就是QLowEnergyCharacteristic对象中的PropertyType

enum PropertyType {
        Unknown = 0x00,
        Broadcasting = 0x01,
        Read = 0x02,
        WriteNoResponse = 0x04,
        Write = 0x08,
        Notify = 0x10,
        Indicate = 0x20,
        WriteSigned = 0x40,
        ExtendedProperty = 0x80
    };

常用的就是

QLowEnergyCharacteristic::WriteNoResponse
QLowEnergyCharacteristic::Write
QLowEnergyCharacteristic::Read
QLowEnergyCharacteristic::Notify

dataReceived和Notify_Data用于显示收到的数据和通知数据,数据包含在QByteArray value

write是app往外发数据用的,这个很简单,和串口发送一样

connect(ui->btn_Send,&QPushButton::clicked,[=]{
        QByteArray data;
        if(ui->Send_Hex->checkState()==Qt::Checked)
        {
            data =  QByteArray::fromHex(ui->Re_Data_2->toPlainText().toLatin1());
        }
        else
        {
            data = QByteArray(ui->Re_Data_2->toPlainText().toLatin1());
        }
        write(data);
    });

on_characteristicChanged和on_characteristicRead用起来会有些重复

现在就简单的能用,详细的部分后续再补充

常见的UUID

QStringList UUID_Find=
{// Sample Services.
"0000180d-0000-1000-8000-00805f9b34fb", "Heart Rate Service"	,
"0000180a-0000-1000-8000-00805f9b34fb", "Device Information Service"	,
// Sample Characteristics.
"00002a37-0000-1000-8000-00805f9b34fb", "Heart Rate Measurement"	,
"00002a29-0000-1000-8000-00805f9b34fb", "Manufacturer Name String"	,

// GATT Services
"00001800-0000-1000-8000-00805f9b34fb", "Generic Access"	,
"00001801-0000-1000-8000-00805f9b34fb", "Generic Attribute"	,


// GATT Declarations
"00002800-0000-1000-8000-00805f9b34fb", "Primary Service"	,
"00002801-0000-1000-8000-00805f9b34fb", "Secondary Service"	,
"00002802-0000-1000-8000-00805f9b34fb", "Include"	,
"00002803-0000-1000-8000-00805f9b34fb", "Characteristic"	,

// GATT Descriptors
"00002900-0000-1000-8000-00805f9b34fb", "Characteristic Extended Properties"	,
"00002901-0000-1000-8000-00805f9b34fb", "Characteristic User Description"	,
"00002902-0000-1000-8000-00805f9b34fb", "Client Characteristic Configuration"	,
"00002903-0000-1000-8000-00805f9b34fb", "Server Characteristic Configuration"	,
"00002904-0000-1000-8000-00805f9b34fb", "Characteristic Presentation Format"	,
"00002905-0000-1000-8000-00805f9b34fb", "Characteristic Aggregate Format"	,
"00002906-0000-1000-8000-00805f9b34fb", "Valid Range"	,
"00002907-0000-1000-8000-00805f9b34fb", "External Report Reference Descriptor"	,
"00002908-0000-1000-8000-00805f9b34fb", "Report Reference Descriptor"	,

// GATT Characteristics
"00002a00-0000-1000-8000-00805f9b34fb", "Device Name"	,
"00002a01-0000-1000-8000-00805f9b34fb", "Appearance"	,
"00002a02-0000-1000-8000-00805f9b34fb", "Peripheral Privacy Flag"	,
"00002a03-0000-1000-8000-00805f9b34fb", "Reconnection Address"	,
"00002a04-0000-1000-8000-00805f9b34fb", "PPCP"	,
"00002a05-0000-1000-8000-00805f9b34fb", "Service Changed"	,

// GATT Service UUIDs
"00001802-0000-1000-8000-00805f9b34fb", "Immediate Alert"	,
"00001803-0000-1000-8000-00805f9b34fb", "Link Loss"	,
"00001804-0000-1000-8000-00805f9b34fb", "Tx Power"	,
"00001805-0000-1000-8000-00805f9b34fb", "Current Time Service"	,
"00001806-0000-1000-8000-00805f9b34fb", "Reference Time Update Service"	,
"00001807-0000-1000-8000-00805f9b34fb", "Next DST Change Service"	,
"00001808-0000-1000-8000-00805f9b34fb", "Glucose"	,
"00001809-0000-1000-8000-00805f9b34fb", "Health Thermometer"	,
"0000180a-0000-1000-8000-00805f9b34fb", "Device Information"	,
"0000180b-0000-1000-8000-00805f9b34fb", "Network Availability"	,
"0000180d-0000-1000-8000-00805f9b34fb", "Heart Rate"	,
"0000180e-0000-1000-8000-00805f9b34fb", "Phone Alert Status Service"	,
"0000180f-0000-1000-8000-00805f9b34fb", "Battery Service"	,
"00001810-0000-1000-8000-00805f9b34fb", "Blood Pressure"	,
"00001811-0000-1000-8000-00805f9b34fb", "Alert Notification Service"	,
"00001812-0000-1000-8000-00805f9b34fb", "Human Interface Device"	,
"00001813-0000-1000-8000-00805f9b34fb", "Scan Parameters"	,
"00001814-0000-1000-8000-00805f9b34fb", "Running Speed and Cadence"	,
"00001816-0000-1000-8000-00805f9b34fb", "Cycling Speed and Cadence"	,
"00001818-0000-1000-8000-00805f9b34fb", "Cycling Power"	,
"00001819-0000-1000-8000-00805f9b34fb", "Location and Navigation"	,

// GATT Characteristic UUIDs
"00002a06-0000-1000-8000-00805f9b34fb", "Alert Level"	,
"00002a07-0000-1000-8000-00805f9b34fb", "Tx Power Level"	,
"00002a08-0000-1000-8000-00805f9b34fb", "Date Time"	,
"00002a09-0000-1000-8000-00805f9b34fb", "Day of Week"	,
"00002a0a-0000-1000-8000-00805f9b34fb", "Day Date Time"	,
"00002a0c-0000-1000-8000-00805f9b34fb", "Exact Time 256"	,
"00002a0d-0000-1000-8000-00805f9b34fb", "DST Offset"	,
"00002a0e-0000-1000-8000-00805f9b34fb", "Time Zone"	,
"00002a0f-0000-1000-8000-00805f9b34fb", "Local Time Information"	,
"00002a11-0000-1000-8000-00805f9b34fb", "Time with DST"	,
"00002a12-0000-1000-8000-00805f9b34fb", "Time Accuracy"	,
"00002a13-0000-1000-8000-00805f9b34fb", "Time Source"	,
"00002a14-0000-1000-8000-00805f9b34fb", "Reference Time Information"	,
"00002a16-0000-1000-8000-00805f9b34fb", "Time Update Control Point"	,
"00002a17-0000-1000-8000-00805f9b34fb", "Time Update State"	,
"00002a18-0000-1000-8000-00805f9b34fb", "Glucose Measurement"	,
"00002a19-0000-1000-8000-00805f9b34fb", "Battery Level"	,
"00002a1c-0000-1000-8000-00805f9b34fb", "Temperature Measurement"	,
"00002a1d-0000-1000-8000-00805f9b34fb", "Temperature Type"	,
"00002a1e-0000-1000-8000-00805f9b34fb", "Intermediate Temperature"	,
"00002a21-0000-1000-8000-00805f9b34fb", "Measurement Interval"	,
"00002a22-0000-1000-8000-00805f9b34fb", "Boot Keyboard Input Report"	,
"00002a23-0000-1000-8000-00805f9b34fb", "System ID"	,
"00002a24-0000-1000-8000-00805f9b34fb", "Model Number String"	,
"00002a25-0000-1000-8000-00805f9b34fb", "Serial Number String"	,
"00002a26-0000-1000-8000-00805f9b34fb", "Firmware Revision String"	,
"00002a27-0000-1000-8000-00805f9b34fb", "Hardware Revision String"	,
"00002a28-0000-1000-8000-00805f9b34fb", "Software Revision String"	,
"00002a29-0000-1000-8000-00805f9b34fb", "Manufacturer Name String"	,
"00002a2a-0000-1000-8000-00805f9b34fb", "IEEE 11073-20601 Regulatory Certification Data List"	,
"00002a2b-0000-1000-8000-00805f9b34fb", "Current Time"	,
"00002a31-0000-1000-8000-00805f9b34fb", "Scan Refresh"	,
"00002a32-0000-1000-8000-00805f9b34fb", "Boot Keyboard Output Report"	,
"00002a33-0000-1000-8000-00805f9b34fb", "Boot Mouse Input Report"	,
"00002a34-0000-1000-8000-00805f9b34fb", "Glucose Measurement Context"	,
"00002a35-0000-1000-8000-00805f9b34fb", "Blood Pressure Measurement"	,
"00002a36-0000-1000-8000-00805f9b34fb", "Intermediate Cuff Pressure"	,
"00002a37-0000-1000-8000-00805f9b34fb", "Heart Rate Measurement"	,
"00002a38-0000-1000-8000-00805f9b34fb", "Body Sensor Location"	,
"00002a39-0000-1000-8000-00805f9b34fb", "Heart Rate Control Point"	,
"00002a3e-0000-1000-8000-00805f9b34fb", "Network Availability"	,
"00002a3f-0000-1000-8000-00805f9b34fb", "Alert Status"	,
"00002a40-0000-1000-8000-00805f9b34fb", "Ringer Control Point"	,
"00002a41-0000-1000-8000-00805f9b34fb", "Ringer Setting"	,
"00002a42-0000-1000-8000-00805f9b34fb", "Alert Category ID Bit Mask"	,
"00002a43-0000-1000-8000-00805f9b34fb", "Alert Category ID"	,
"00002a44-0000-1000-8000-00805f9b34fb", "Alert Notification Control Point"	,
"00002a45-0000-1000-8000-00805f9b34fb", "Unread Alert Status"	,
"00002a46-0000-1000-8000-00805f9b34fb", "New Alert"	,
"00002a47-0000-1000-8000-00805f9b34fb", "Supported New Alert Category"	,
"00002a48-0000-1000-8000-00805f9b34fb", "Supported Unread Alert Category"	,
"00002a49-0000-1000-8000-00805f9b34fb", "Blood Pressure Feature"	,
"00002a4a-0000-1000-8000-00805f9b34fb", "HID Information"	,
"00002a4b-0000-1000-8000-00805f9b34fb", "Report Map"	,
"00002a4c-0000-1000-8000-00805f9b34fb", "HID Control Point"	,
"00002a4d-0000-1000-8000-00805f9b34fb", "Report"	,
"00002a4e-0000-1000-8000-00805f9b34fb", "Protocol Mode"	,
"00002a4f-0000-1000-8000-00805f9b34fb", "Scan Interval Window"	,
"00002a50-0000-1000-8000-00805f9b34fb", "PnP ID"	,
"00002a51-0000-1000-8000-00805f9b34fb", "Glucose Feature"	,
"00002a52-0000-1000-8000-00805f9b34fb", "Record Access Control Point"	,
"00002a53-0000-1000-8000-00805f9b34fb", "RSC Measurement"	,
"00002a54-0000-1000-8000-00805f9b34fb", "RSC Feature"	,
"00002a55-0000-1000-8000-00805f9b34fb", "SC Control Point"	,
"00002a5b-0000-1000-8000-00805f9b34fb", "CSC Measurement"	,
"00002a5c-0000-1000-8000-00805f9b34fb", "CSC Feature"	,
"00002a5d-0000-1000-8000-00805f9b34fb", "Sensor Location"	,
"00002a63-0000-1000-8000-00805f9b34fb", "Cycling Power Measurement"	,
"00002a64-0000-1000-8000-00805f9b34fb", "Cycling Power Vector"	,
"00002a65-0000-1000-8000-00805f9b34fb", "Cycling Power Feature"	,
"00002a66-0000-1000-8000-00805f9b34fb", "Cycling Power Control Point"	,
"00002a67-0000-1000-8000-00805f9b34fb", "Location and Speed"	,
"00002a68-0000-1000-8000-00805f9b34fb", "Navigation"	,
"00002a69-0000-1000-8000-00805f9b34fb", "Position Quality"	,
"00002a6a-0000-1000-8000-00805f9b34fb", "LN Feature"	,
"00002a6b-0000-1000-8000-00805f9b34fb", "LN Control Point"	};