QT for Android BLE Bluetooch QT BLE
小白式的介绍,很详细了,很多主要内容写在程序的注释里,慢慢看
下面是我的源码
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; // 服务uuidQList<DeviceInfo*> m_devices; // 搜索到的蓝牙设备信息QStringList m_devicesNames; // 搜索到的设备名称QStringList m_services; // 服务列表
设备
首先将读取的设备信息归为一个类DeviceInfo
class DeviceInfo: public QObject
{Q_OBJECTpublic: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();
#elsereturn 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());//发现并更新设备名称ComboBoxQString 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); //连接成功切换到服务器列表//发现服务Servicesm_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::PropertyTypeif(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" };
相关文章:

QT for Android BLE Bluetooch QT BLE
小白式的介绍,很详细了,很多主要内容写在程序的注释里,慢慢看 下面是我的源码 https://download.csdn.net/download/qq_27620407/87464307 源码打不开的话可以试试下图的操作,之后电机确定,可能是加图标搞的࿰…...

【蓝桥集训】第四天——双指针
作者:指针不指南吗 专栏:Acwing 蓝桥集训每日一题 🐾或许会很慢,但是不可以停下🐾 文章目录1.字符串删减2.最长连续不重复子序列3.数组元素的目标和1.字符串删减 给定一个由 n 个小写字母构成的字符串。 现在ÿ…...
List<Map<String, Object>>的数据结构的添加和删除实例
对List<Map<String, Object>>的数据结构的添加和删除实例添加//初始化List<Map<String, Object>> products new ArrayList<Map<String,Object>>();//也可以这样初始化List<Map<String, Object>> products null//初始Map<…...
5.2 线程实际案例练习
文章目录1.概述2.实现方案一:继承Thread2.1 代码实现2.2 代码分析3.实现方案二:实现Runnable接口3.1 代码实现3.2 代码分析4.实现方案三:构建线程池4.1 代码实现4.2 代码分析1.概述 接下来我们通过一个售票案例的实际操作来深入理解线程的相…...

stm32f407探索者开发板(十七)——串口寄存器库函数配置方法
文章目录一、STM32串口常用寄存器和库函数1.1 常用的串口寄存器1.2 串口相关的库函数1.3 状态寄存器(USART_ SR)1.4 数据寄存器(USART_ DR)1.5 波特率寄存器(USART_BRR)二、串口配置一般步骤一、STM32串口常…...
山西省2023年软考报名3月14日开始
根据2023年上半年计算机技术与软件专业技术资格(水平)考试工作计划,可以得知,全国考务管理服务平台将于2023年3月13日开放,各地开始组织报名,如山西已发布2023上半年报名简章,从3月14号开始报名。 软考报名官网 大部…...

进程章节总结性实验
进程实验课笔记 本节需要有linux基础,懂基本的linux命令操作即可。 Ubuntu镜像下载 https://note.youdao.com/s/VxvU3eVC ubuntu安装 https://www.bilibili.com/video/BV1j44y1S7c2/?spm_id_from333.999.0.0 实验环境ubuntu22版本,那个linux环境都可以…...
【MyBatis】MyBatis的缓存
10、MyBatis的缓存 10.1、MyBatis的一级缓存 一级缓存是SqlSession级别的,通过同一个SqlSession查询的数据会被缓存,下次查询相同的数据,就会从缓存中直接获取,不会从数据库重新访问 使一级缓存失效的四种情况: 不…...

MyBatis基本使用
一、简介 MyBatis 中文文档 https://mybatis.org/mybatis-3/zh/index.html 1.什么是 MyBatis 概述:MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBa…...

如何运行YOLOv6的代码实现目标识别?
YOLOv6是由美团视觉团队开发的1.环境配置我们先把YOLOv6的代码clone下来git clone https://github.com/meituan/YOLOv6.git安装一些必要的包pip install pycocotools2.0作者要求pytorch的版本是1.8.0,我的环境是1.7.0,也是可以正常运行的pip install -r requirement…...

新品BCM6755A1KFEBG/MT7921LE/MT7921AU WiFi芯片
博通在WiFi市场具有相当的实力。在WiFi6上有下面这几个解决方案:型号:BCM6755 BCM6755A1KFEBG类型:四核1.5GHz CPU封装:BGA批次:新BCM6755和BCM6750还是A7架构,更多的用在中低端型号上。BCM6755和BCM6750 C…...

析构函数、拷贝构造
1、析构函数析构函数的定义方式函数名和类名相同,在类名前加~,没有返回值类型,没有函数形参(不能重载)当对象生命周期结束的时候,系统会自动调用析构函数先调用析构函数,再释放对象的空间析构函…...
光学镜头是制作过程阶段理解
光学镜头是由多组镜片组合而成,它是摄影机投影一及显微镜上必不可少的部件。那么光学镜头是如何制造的呢?光学镜头的制作分为以下四个阶段:第一、首先将一大块光学玻璃用钻石锯片进行切片,然后用钻头在每一块玻璃切片上钻出多块冰…...

实验室设计|实验室设计要点SICOLAB
一、实验室设计规划要素1、实验室布局:实验室的布局要符合实验室工作流程,可以将实验室划分为干净区和污染区,以确保实验室的卫生和实验的准确性。2、设备选购:根据实验需要选择适当的设备,并确保设备的质量和性能符合…...

I.MX6ULL_Linux_系统篇(16) uboot分析-启动流程
原文链接:I.MX6ULL_系统篇(16) uboot分析-启动流程 – WSY Personal Blog (cpolar.cn) 前面我们详细的分析了 uboot 的顶层 Makefile,了解了 uboot 的编译流程。本章我们来详细的分析一下 uboot 的启动流程,理清 uboot 是如何启动的。通过对 …...

【C#】async关键字修饰后有无await的影响
文章目录测试总结拓展:js的async await问题参考测试 来自微软官网的说法: 异步方法通常包含 await 运算符的一个或多个匹配项,但缺少 await 表达式不会导致编译器错误。 如果异步方法未使用 await 运算符标记悬挂点,则该方法将作…...

Interspeech2022 | 一种基于元辅助学习的低资源口语语义理解方法
中国移动研究院首席科学家冯俊兰博士带领人工智能与智慧运营中心语音团队共同撰写的文章《Meta Auxiliary Learning for Low-resource Spoken Language Understanding》被语音国际顶会Interspeech2022接收。 关于Interspeech Interspeech 是国际最大且最全面关于言语科学与技…...

File类的用法和InputStream,OutputStream的用法
这里写自定义目录标题一、File类1.构造方法2.普通方法二、InputStream1.方法2.FileInputStream3.Scanner类的应用三、OutputStream1.方法2.FileOutputStream3.PrintWriter类的应用一、File类 1.构造方法 签名说明File(File parent, Stringchild)根据父目录 孩子文件路径&…...
Java多线程——Thread类的基本用法
一.线程的创建继承Thread类//继承Thread类class MyThread extends Thread{Overridepublic void run() {System.out.println("线程运行的代码");} } public class Demo1 {public static void main(String[] args) {MyThread t new MyThread();t.start();//启动线程&a…...

【C++】类和对象练习——日期类的实现
文章目录前言1. 日期的合法性判断2. 日期天数(/)2.1 和的重载2.2 对于两者复用的讨论3. 前置和后置重载4. 日期-天数(-/-)5. 前置- -和后置- -的重载6. 日期-日期7. 流插入<<重载8. 流提取>>重载9. 总结10. 源码展示前…...

利用最小二乘法找圆心和半径
#include <iostream> #include <vector> #include <cmath> #include <Eigen/Dense> // 需安装Eigen库用于矩阵运算 // 定义点结构 struct Point { double x, y; Point(double x_, double y_) : x(x_), y(y_) {} }; // 最小二乘法求圆心和半径 …...

装饰模式(Decorator Pattern)重构java邮件发奖系统实战
前言 现在我们有个如下的需求,设计一个邮件发奖的小系统, 需求 1.数据验证 → 2. 敏感信息加密 → 3. 日志记录 → 4. 实际发送邮件 装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其…...

【力扣数据库知识手册笔记】索引
索引 索引的优缺点 优点1. 通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。2. 可以加快数据的检索速度(创建索引的主要原因)。3. 可以加速表和表之间的连接,实现数据的参考完整性。4. 可以在查询过程中,…...
在HarmonyOS ArkTS ArkUI-X 5.0及以上版本中,手势开发全攻略:
在 HarmonyOS 应用开发中,手势交互是连接用户与设备的核心纽带。ArkTS 框架提供了丰富的手势处理能力,既支持点击、长按、拖拽等基础单一手势的精细控制,也能通过多种绑定策略解决父子组件的手势竞争问题。本文将结合官方开发文档,…...

家政维修平台实战20:权限设计
目录 1 获取工人信息2 搭建工人入口3 权限判断总结 目前我们已经搭建好了基础的用户体系,主要是分成几个表,用户表我们是记录用户的基础信息,包括手机、昵称、头像。而工人和员工各有各的表。那么就有一个问题,不同的角色…...

剑指offer20_链表中环的入口节点
链表中环的入口节点 给定一个链表,若其中包含环,则输出环的入口节点。 若其中不包含环,则输出null。 数据范围 节点 val 值取值范围 [ 1 , 1000 ] [1,1000] [1,1000]。 节点 val 值各不相同。 链表长度 [ 0 , 500 ] [0,500] [0,500]。 …...
工业自动化时代的精准装配革新:迁移科技3D视觉系统如何重塑机器人定位装配
AI3D视觉的工业赋能者 迁移科技成立于2017年,作为行业领先的3D工业相机及视觉系统供应商,累计完成数亿元融资。其核心技术覆盖硬件设计、算法优化及软件集成,通过稳定、易用、高回报的AI3D视觉系统,为汽车、新能源、金属制造等行…...

c#开发AI模型对话
AI模型 前面已经介绍了一般AI模型本地部署,直接调用现成的模型数据。这里主要讲述讲接口集成到我们自己的程序中使用方式。 微软提供了ML.NET来开发和使用AI模型,但是目前国内可能使用不多,至少实践例子很少看见。开发训练模型就不介绍了&am…...
【碎碎念】宝可梦 Mesh GO : 基于MESH网络的口袋妖怪 宝可梦GO游戏自组网系统
目录 游戏说明《宝可梦 Mesh GO》 —— 局域宝可梦探索Pokmon GO 类游戏核心理念应用场景Mesh 特性 宝可梦玩法融合设计游戏构想要素1. 地图探索(基于物理空间 广播范围)2. 野生宝可梦生成与广播3. 对战系统4. 道具与通信5. 延伸玩法 安全性设计 技术选…...

html css js网页制作成品——HTML+CSS榴莲商城网页设计(4页)附源码
目录 一、👨🎓网站题目 二、✍️网站描述 三、📚网站介绍 四、🌐网站效果 五、🪓 代码实现 🧱HTML 六、🥇 如何让学习不再盲目 七、🎁更多干货 一、👨…...