BLE 蓝牙客户端和服务器连接

        蓝牙通信在设计小型智能设备时非常普遍,之前一直没有使用过,最近使用ardunio ESP32 做了一些实验,做了一个收听播客的智能旋钮(Smart Knob),它带有一个旋转编码器和两个按键。

     本文介绍BLE 服务器Server和Web BLE API 作为Client。的程序

BLE 服务器和客户端

        使用蓝牙低功耗,有两种类型的设备:服务器客户端

        服务器 宣传它的存在,因此它可以被其他设备发现并包含客户端 可以读取的数据。客户端扫描附近的设备,当它找到它正在寻找的服务器时,它会建立连接并监听传入的数据。这称为点对点通信。

2. GATT 协议

        GATT (Generic Attribute Profile) 代表通用属性,它定义了向连接的 BLE 设备公开的分层数据结构。这意味着 GATT 定义了两个 BLE 设备发送和接收标准消息的方式。

服务集合 Profile: 针对特定用例的标准服务集合;
服务 Service: 收集相关信息,如传感器读数、电池电量、心率等;
特征 Characteristic: 它是实际数据保存在层次结构(值)上的位置;
描述 Descriptor: 关于数据的元数据;
属性 Properties: 描述如何与特征值交互。例如:读、写、通知、广播、指示等。

UUID

        每个服务 、特征描述符都有一个 UUID(通用唯一标识符。UUID 是唯一的 128 位(16 字节)数字。

ardunio 代码(1)

使用两个charactic

/*Based on Neil Kolban example for IDF: https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleServer.cppPorted to Arduino ESP32 by Evandro Coperciniupdates by chegewara
*/#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEServer.h>// See the following for generating UUIDs:
// https://www.uuidgenerator.net/#define SERVICE_UUID        "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
#define CHARACTERISTIC_UUID_1 "beb5483e-36e1-4688-b7f5-ea07361b26a8"
#define CHARACTERISTIC_UUID_2  "cba1d466-344c-4be3-ab3f-189f80dd7518"
bool deviceConnected = false;//创建一个旋钮属性 BLECharacteristic KnobCharacteristic(CHARACTERISTIC_UUID_1,BLECharacteristic::PROPERTY_READ   |BLECharacteristic::PROPERTY_WRITE  |BLECharacteristic::PROPERTY_NOTIFY |BLECharacteristic::PROPERTY_INDICATE);
BLEDescriptor KnobDescriptor(BLEUUID((uint16_t)0x2902));//创建一个开关(Button)BLECharacteristic ButtonCharacteristic(CHARACTERISTIC_UUID_2,BLECharacteristic::PROPERTY_READ   |BLECharacteristic::PROPERTY_WRITE  |BLECharacteristic::PROPERTY_NOTIFY |BLECharacteristic::PROPERTY_INDICATE);    
BLEDescriptor ButtonDescriptor(BLEUUID((uint16_t)0x2903)); 
int KnobValue  =100;
int ButtonValue=1;   class MyServerCallbacks: public BLEServerCallbacks {void onConnect(BLEServer* pServer) {deviceConnected = true;// 客户端连接到服务器,状态为true};void onDisconnect(BLEServer* pServer) {deviceConnected = false;}
};void setup() {Serial.begin(115200);Serial.println("Starting BLE work!");BLEDevice::init("ESP32_KNOB");BLEServer *pServer = BLEDevice::createServer();// 将 BLE 设备设置为服务器并分配回调函数pServer->setCallbacks(new MyServerCallbacks());BLEService *pService = pServer->createService(SERVICE_UUID);//KnobpService->addCharacteristic(&KnobCharacteristic);KnobDescriptor.setValue("Knob");KnobCharacteristic.addDescriptor(&KnobDescriptor);pService->addCharacteristic(&ButtonCharacteristic);ButtonDescriptor.setValue("Button");ButtonCharacteristic.addDescriptor(&ButtonDescriptor);pService->start();// BLEAdvertising *pAdvertising = pServer->getAdvertising();  // this still is working for backward compatibilityBLEAdvertising *pAdvertising = BLEDevice::getAdvertising();pAdvertising->addServiceUUID(SERVICE_UUID);pAdvertising->setScanResponse(true);pAdvertising->setMinPreferred(0x06);  // functions that help with iPhone connections issuepAdvertising->setMinPreferred(0x12);BLEDevice::startAdvertising();Serial.println("Characteristic defined! Now you can read it in your phone!");
}void loop() {KnobValue++;ButtonValue++;if (deviceConnected) {String KnobStr=String(KnobValue);String ButtonStr=String(ButtonValue);KnobCharacteristic.setValue(KnobStr.c_str());KnobCharacteristic.notify();ButtonCharacteristic.setValue(ButtonStr.c_str());ButtonCharacteristic.notify();}delay(50);
}

adunio代码(2)

        在测试的时候发现,Notify 两个Caractic时候,Web Bluetooth ,只能接收到第一个notify,第二个value 始终为空,于是我使用一个charactic 传送两个参数。

    另外,在loop 中增加 startAdvertising 的程序,保证在断开时不断地startAdvertising.web 能够扫描到BLE Server。

 if (!deviceConnected && oldDeviceConnected) {Serial.println("Device disconnected.");delay(500); pServer->startAdvertising(); // restart advertisingSerial.println("Start advertising");oldDeviceConnected = deviceConnected;}// connectingif (deviceConnected && !oldDeviceConnected) {// do stuff here on connectingoldDeviceConnected = deviceConnected;Serial.println("Device Connected");}
/*Based on Neil Kolban example for IDF: https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleServer.cppPorted to Arduino ESP32 by Evandro Coperciniupdates by chegewara
*/#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEServer.h>// See the following for generating UUIDs:
// https://www.uuidgenerator.net/#define SERVICE_UUID        "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
#define CHARACTERISTIC_UUID_1 "beb5483e-36e1-4688-b7f5-ea07361b26a8"
BLEServer* pServer = NULL;
bool deviceConnected = false;
bool oldDeviceConnected = false;//创建一个旋钮属性 BLECharacteristic KnobCharacteristic(CHARACTERISTIC_UUID_1,BLECharacteristic::PROPERTY_READ   |BLECharacteristic::PROPERTY_WRITE  |BLECharacteristic::PROPERTY_NOTIFY |BLECharacteristic::PROPERTY_INDICATE);
BLEDescriptor KnobDescriptor(BLEUUID((uint16_t)0x2902));int KnobValue  =100;
int ButtonValue=1;   class MyServerCallbacks: public BLEServerCallbacks {void onConnect(BLEServer* pServer) {deviceConnected = true;// 客户端连接到服务器,状态为true};void onDisconnect(BLEServer* pServer) {deviceConnected = false;}
};void setup() {Serial.begin(115200);Serial.println("Starting BLE work!");BLEDevice::init("ESP32_KNOB");pServer = BLEDevice::createServer();// 将 BLE 设备设置为服务器并分配回调函数pServer->setCallbacks(new MyServerCallbacks());BLEService *pService = pServer->createService(SERVICE_UUID);//KnobpService->addCharacteristic(&KnobCharacteristic);KnobDescriptor.setValue("Knob");KnobCharacteristic.addDescriptor(&KnobDescriptor);pService->start();BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();pAdvertising->addServiceUUID(SERVICE_UUID);pAdvertising->setScanResponse(true);pAdvertising->setMinPreferred(0x06);  // functions that help with iPhone connections issuepAdvertising->setMinPreferred(0x12);// BLEDevice::startAdvertising();pServer->getAdvertising()->start();Serial.println("Characteristic defined! Now you can read it in your phone!");
}void loop() {if (deviceConnected) {KnobValue++;ButtonValue++;byte buffer[8];memcpy(&buffer[0],&KnobValue,4);memcpy(&buffer[4],&ButtonValue,4);// String KnobStr=String(KnobValue);//  String ButtonStr=String(ButtonValue);KnobCharacteristic.setValue((uint8_t*)&buffer, 8);KnobCharacteristic.notify();delay(500);}// disconnectingif (!deviceConnected && oldDeviceConnected) {Serial.println("Device disconnected.");delay(500); pServer->startAdvertising(); // restart advertisingSerial.println("Start advertising");oldDeviceConnected = deviceConnected;}// connectingif (deviceConnected && !oldDeviceConnected) {// do stuff here on connectingoldDeviceConnected = deviceConnected;Serial.println("Device Connected");}delay(200);
}

通过web 访问蓝牙

为了简单地做测试,我使用Web BlueTooth 的API 。

连接到设备

        从浏览器连接到设备。可以调用函数 navigator.bluetooth.requestDevice() 并为函数提供配置对象,该对象含有关我们要使用哪个设备,以及都有哪些服务可用的信息。

let device = await navigator.bluetooth.requestDevice({filters: [ { namePrefix: 'PLAYBULB' } ],optionalServices: [ 0xff0f ]
});

        当我们调用此函数时,会弹出一个窗口,显示符合过滤规则的设备列表。 现在必须手动选择我们想要连接的设备。这是出于安全和隐私的需要,并为用户提供控制的权利。用户决定是否允许 Web 应用连接到设备,当然还有已经被允许连接的设备。 如果没有用户手动选择设备,Web 应用则无法获取设备列表或连接。

        在我们访问设备之后,可以通过调用设备 gatt 属性上的 connect() 函数连接到 GATT 服务器并等待返回结果。

let server = await device.gatt.connect();

一旦我们连上服务器,就可以调用 getPrimaryService() 并传递服务的UUID,然后等待结果返回。

let service = await server.getPrimaryService(0xff0f);

然后使用特性的UUID作为参数调用服务上的 getCharacteristic() 并再次等待结果返回。

现在就得到了可用于读写数据的特性:

let characteristic = await service.getCharacteristic(0xfffc);
写数据

        要写入数据,我们可以在特性上调用函数 writeValue() ,以 ArrayBuffer 的形式传递想要写入的值 ,这是二进制数据的存储方法。

characteristic.writeValue(new Uint8Array([ 0, r, g, b  ])
);
读数据

要读取灯泡的当前颜色,可以使用 readValue() 函数并等待结果返回。

let value = await characteristic.readValue();let r = value.getUint8(1); 
let g = value.getUint8(2);
let b = value.getUint8(3);
获得通知变更

        最后,还有一种方法可以在设备值发生变化时收到通知。

characteristic.addEventListener('characteristicvaluechanged', e => {let r = e.target.value.getUint8(1); let g = e.target.value.getUint8(2);let b = e.target.value.getUint8(3);}
);characteristic.startNotifications();

简单的方式

    async function onButtonClick() {let device = await navigator.bluetooth.requestDevice({acceptAllDevices: true,});let server = await device.gatt.connect();console.log(server);let services = await server.getPrimaryServices();console.log(services);let characteristics = await services[0].getCharacteristics();let value = await characteristics[0].readValue();console.log(value);}

代码(读取方式)

<!DOCTYPE html>
<html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>BLE Server </title></head><body><h1>  Ble Server Connection</h1><button onclick="Connection()">连接</button><button onclick="ReadOut()">读操作</button><p id="Knob">Knob:null</p><p id="button">button:null</p><script>var servervar servicesasync function ReadOut() {let characteristics = await services[0].getCharacteristics();let value = await characteristics[0].readValue();let textDecoder = new TextDecoder('ascii');let Val = textDecoder.decode(value.buffer);console.log("Knob:" + Val);document.getElementById("Knob").textContent="knob:"+Valvalue = await characteristics[1].readValue();textDecoder = new TextDecoder('ascii');Val = textDecoder.decode(value.buffer);console.log("button:" + Val);document.getElementById("button").textContent="button:"+Val}async function Connection() {let device = await navigator.bluetooth.requestDevice({filters: [{ name: 'ESP32_KNOB' }]});server = await device.gatt.connect();services = await server.getPrimaryServices();}</script>
</body></html>

代码(使用Notify)

<!DOCTYPE html>
<html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>BLE Server </title></head><body><h1> Ble Server Connection</h1><button onclick="Connection()">连接</button><h4>Parameters</h4><p id="Knob">Knob:null</p><p id="button">button:null</p><script>var servervar servicesasync function startNotification() {let textDecoder = new TextDecoder('ascii');let characteristics= await services[0].getCharacteristics()       let characteristic = await services[0].getCharacteristic("beb5483e-36e1-4688-b7f5-ea07361b26a8");characteristic.addEventListener('characteristicvaluechanged', e => {v1=e.target.value.getUint8(0); v2=e.target.value.getUint8(1); v3=e.target.value.getUint8(2); v4=e.target.value.getUint8(3); Val=(v4<<24)|(v3<<16)|(v2<<8)|v1console.log("Knob:" + Val);document.getElementById("Knob").textContent = "knob:" + Val//v1=e.target.value.getUint8(4); v2=e.target.value.getUint8(5); v3=e.target.value.getUint8(6); v4=e.target.value.getUint8(7); Val=(v4<<24)|(v3<<16)|(v2<<8)|v1console.log("button:" + Val);document.getElementById("button").textContent = "button:" + Val});characteristic.startNotifications(); }async function Connection() {let device = await navigator.bluetooth.requestDevice({filters: [{ name: 'ESP32_KNOB' }],optionalServices: [  "4fafc201-1fb5-459e-8fcc-c5c9c331914b"]});server = await device.gatt.connect();services = await server.getPrimaryServices();startNotification()}</script>
</body></html>

结论

        本文已经覆盖了 WebBluetooth API 的90%。 只需调用几个函数并发送 4 个字节,你就可以创建一个控制灯泡颜色的 Web 应用。 如果再添加几行,你甚至可以控制玩具车或驾驶无人机。 随着越来越多的蓝牙设备进入市场,将产生无穷的可能性。

更多资源

  • Bluetooth.rocks! Demos | (GitHub 上的源代码)
  • “Web Bluetooth Specification,” Web蓝牙社区
  • Open GATT Registry 蓝牙低功耗设备的GATT非官方文档。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/19523.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

图(Graph)的概念和遍历

目录 定义 相关概念 无向图&#xff08;Undirected graphs) 有向图&#xff08;Directed graphs&#xff09; 完全图 稀疏图 稠密图 权&#xff08;Weight&#xff09; 网&#xff08;Network&#xff09; 子图&#xff08;Subgraph&#xff09; 图的顶点与边间关系 …

python成长技能之正则表达式

文章目录 一、认识正则表达式二、使用正则表达式匹配单一字符三、正则表达式之重复出现数量匹配四、使用正则表达式匹配字符集五、正则表达式之边界匹配六、正则表达式之组七、正则表达式之贪婪与非贪婪 一、认识正则表达式 什么是正则表达式 正则表达式&#xff08;英语&…

Unity图形学之RenderQueue

1.指定物体的渲染顺序 Tags { “Queue” “XXXX” } 取值类型&#xff1a; Background&#xff1a; 对应数值为 1000&#xff0c;用于需要被最先渲染的对象&#xff0c;。 Geometry&#xff1a; 对应数值为 2000, 用于不透明的物体。这个是默认的选项&#xff08;如果不指明…

i春秋-破译(凯撒密码+数字替换单词中的字母)

练习平台地址 竞赛中心 题目描述 题目内容 就是破译&#xff01;&#xff01;&#xff01; 解题 观察到最后一段是四个字母加上{xxxxx}的形式&#xff0c;很像flag&#xff0c;我们猜测要破译的主要是这个片段 大括号依然存在&#xff0c;那么可能是通过凯撒密码来加密的&a…

丹摩征文活动|平台评测与使用体验报告

一、基本信息 目标产品 丹摩智算平台www.damodel.com 体验设备 台式机 体验系统/环境 Windows 10/浏览器 体验时间 2024/11 二、产品信息 产品类型&#xff1a;云计算服务提供商 产品定位&#xff1a;提供AI开发和算力GPU租赁服务的平台。它旨在简化AI开发流程&#…

Stable Diffusion核心网络结构——CLIP Text Encoder

&#x1f33a;系列文章推荐&#x1f33a; 扩散模型系列文章正在持续的更新&#xff0c;更新节奏如下&#xff0c;先更新SD模型讲解&#xff0c;再更新相关的微调方法文章&#xff0c;敬请期待&#xff01;&#xff01;&#xff01;&#xff08;本文及其之前的文章均已更新&…

20241118给荣品PRO-RK3566开发板刷Rockchip原厂的buildroot后使用iperf3打流

20241118给荣品PRO-RK3566开发板刷Rockchip原厂的buildroot后使用iperf3打流 2024/11/18 16:38 缘起&#xff0c;使用荣品的DTS。 Y:\RK3566_RK3568_Linux5.10_V1.2.0\device\rockchip\.chips\rk3566_rk3568\rockchip_rk3566_evb2_lp4x_v10_defconfig 1、指定RK_KERNEL_DTS_NAM…

基于java+SpringBoot+Vue的基于web的智慧社区设计与实现

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; Springboot mybatis Maven mysql5.7或8.0等等组成&#x…

分析一个库 便于以后的使用 WiFiManager.h 2024/11/18

这一段是开启服务器 的 这些都不是重点 重点是那个R_update ,没猜错的话应该是升级的功能 直接索引到定义看看 ,很明显这里是设置了一个 web 访问地址 那就只有换个思路往后查找 handleUpdate 找到这个函数定义 void WiFiManager::handleUpdate() {#ifdef WM_DEBUG_LEVELDEBUG…

学习笔记024——Ubuntu 安装 Redis遇到相关问题

目录 1、更新APT存储库缓存&#xff1a; 2、apt安装Redis&#xff1a; 3、如何查看检查 Redis版本&#xff1a; 4、配置文件相关设置&#xff1a; 5、重启服务&#xff0c;配置生效&#xff1a; 6、查看服务状态&#xff1a; 1、更新APT存储库缓存&#xff1a; sudo apt…

【MySQL系列】深入理解MySQL中的存储、排序字符集

前言 在创建数据库时&#xff0c;我们经常会需要填写数据库的所用字符集、排序规则&#xff0c;字符集和排序规则是两个非常重要的概念&#xff0c;它们决定了数据库如何存储和比较字符串数据。在 MySQL 中&#xff0c;常用的存储字符集有 utf8、utf8mb4&#xff0c;而排序字符…

tcp 超时计时器

在 TCP&#xff08;传输控制协议&#xff09;中有以下四种重要的计时器&#xff1a; 重传计时器&#xff08;Retransmission Timer&#xff09; 作用&#xff1a;用于处理数据包丢失的情况。当发送方发送一个数据段后&#xff0c;就会启动重传计时器。如果在计时器超时之前没有…

Docker部署ES7.9.3单节点

Elasticsearch&#xff08;简称ES&#xff09;是一个分布式、可扩展、实时的搜索与数据分析引擎&#xff01; Elasticsearch位于Elastic Stack核心&#xff0c;为所有类型的数据提供近乎实时的搜索和分析。无论是结构化或非结构化文本、数字数据还是地理空间数据&#xff0c;El…

ChromeDriver驱动下载地址更新(保持最新最全)

说明&#xff1a; ChromeDriver 是 Selenium WebDriver 用于控制 Chrome 的独立可执行文件。 为了方便下载使用&#xff0c;本文保持ChromeDriver的最新版本更新&#xff0c;并提供115.0.5763.0-133.0.6841.0版本的下载地址&#xff1a; 所有版本和下载地址&#xff1a; &am…

CSS:高级寄巧

精灵图 为什么需要精灵图呢&#xff1f; 一个网页中往往会应用很多小背景图作为修饰&#xff0c;当网页中的图像过多时&#xff0c;服务器就会频繁地接收和发送 请求图片&#xff0c;造成服务器请求压力过大&#xff0c;这将大大降低页面的加载速度。 因此&#xff0c;为了有…

AutosarMCAL开发——基于EB DsAdc驱动

目录 一、旋转变压器与DsAdc原理1.常见电机角度反馈方式2.可变磁阻旋变工作原理3.使用TC3XX EDSADC进行旋变软解码 二、EB配置1.载波输出2.通道配置3.调制器4.滤波链路5.整流6.积分 三、Mcal接口应用1.AUtosar标准API接口2.应用步骤 四、总结 一、旋转变压器与DsAdc原理 1.常见…

web应用安全和信息泄露预防

文章目录 1&#xff1a;spring actuator导致的信息泄露1.1、Endpoint配置启用检测1.2、信息泄露复现1.3、防御 2&#xff1a;服务端口的合理使用3&#xff1a;弱口令&#xff08;密码&#xff09;管理4&#xff1a;服务端攻击4.1、短信业务&#xff0c;文件上传等资源型接口1、…

C语言:链表

链表是一种常见的线性数据结构&#xff0c;其中每个元素&#xff08;称为节点&#xff09;包含两部分&#xff1a;数据和指向下一个节点的指针。链表的主要优点是插入和删除操作的时间复杂度较低&#xff0c;但随机访问的效率不如数组。 1. 链表的基本概念 节点&#xff08;N…