小白式的介绍,很详细了,很多主要内容写在程序的注释里,慢慢看
下面是我的源码
https://download.csdn.net/download/qq_27620407/87464307
源码打不开的话可以试试下图的操作,之后电机确定,可能是加图标搞的,但是我没遇到过,需要的可以联系897741243@qq.com
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" };