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.cpp
    Ported to Arduino ESP32 by Evandro Copercini
    updates 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);
  //Knob
   pService->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 compatibility
  BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
  pAdvertising->addServiceUUID(SERVICE_UUID);
  pAdvertising->setScanResponse(true);
  pAdvertising->setMinPreferred(0x06);  // functions that help with iPhone connections issue
  pAdvertising->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 advertising
    Serial.println("Start advertising");
    oldDeviceConnected = deviceConnected;
  }
  // connecting
  if (deviceConnected && !oldDeviceConnected) {
    // do stuff here on connecting
    oldDeviceConnected = 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.cpp
    Ported to Arduino ESP32 by Evandro Copercini
    updates 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);
  //Knob
   pService->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 issue
  pAdvertising->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);
  }
   // disconnecting
  if (!deviceConnected && oldDeviceConnected) {
    Serial.println("Device disconnected.");
    delay(500); 
    pServer->startAdvertising(); // restart advertising
    Serial.println("Start advertising");
    oldDeviceConnected = deviceConnected;
  }
  // connecting
  if (deviceConnected && !oldDeviceConnected) {
    // do stuff here on connecting
    oldDeviceConnected = 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 server
        var services
        async 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:"+Val
            value = 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 server
        var services
        async 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)|v1
                    console.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)|v1
                    console.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非官方文档。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

海康威视和大华视频设备对接方案

目录 一、海康威视 【老版本】 【新版本】 二、大华 一、海康威视 【老版本】 URL规定&#xff1a; rtsp://username:password[ipaddress]/[videotype]/ch[number]/[streamtype] 注&#xff1a;VLC可以支持解析URL里的用户名密码&#xff0c;实际发给设备的RTSP请求不支…

STM32设计智能翻译手势识别加算法系统

目录 前言 一、本设计主要实现哪些很“开门”功能&#xff1f; 二、电路设计原理图 电路图采用Altium Designer进行设计&#xff1a; 三、实物设计图 四、程序源代码设计 五、获取资料内容 前言 在全球化的浪潮下&#xff0c;语言的多样性也为人们的交流带来了不小的挑战…

基本定时器---内/外部时钟中断

一、定时器的概念 定时器&#xff08;TIM&#xff09;&#xff0c;可以对输入的时钟信号进行计数&#xff0c;并在计数值达到设定值的时候触发中断。 STM32的定时器系统有一个最为重要的结构是时基单元&#xff0c;它由一个16位计数器&#xff0c;预分频器&#xff0c;和自动重…

Qt文件目录操作

文件目录操作相关类 Qt 为文件和目录操作提供了一些类&#xff0c;利用这些类可以方便地实现一些操作。Qt 提供的与文件和目录操作相关的类包括以下几个&#xff1a; QCoreApplication&#xff1a;用于提取应用程序路径&#xff0c;程序名等文件信息&#xff1b;QFile&#x…

.NET 通过模块和驱动收集本地EDR的工具

01阅读须知 此文所提供的信息只为网络安全人员对自己所负责的网站、服务器等&#xff08;包括但不限于&#xff09;进行检测或维护参考&#xff0c;未经授权请勿利用文章中的技术资料对任何计算机系统进行入侵操作。利用此文所提供的信息而造成的直接或间接后果和损失&#xf…

css中的box-sizing,记录

border-box&#xff1a;最终高度为height&#xff0c;默认包含padding border等属性 content-box&#xff1a;box-sizing默认值&#xff0c;最终大小为heightpaddingborder 等

【AI绘画】Alpha-VLLM 的 Lumina-Next:新一代图像生成器

简介 Lumina-Next-T2I 是在 Lumina-T2I 成功基础上发展起来的尖端图像生成模型。它采用了带有 2B 参数模型的 Next-DiT 和 Gemma-2B 文本编码器&#xff0c;推理速度更快&#xff0c;生成样式更丰富&#xff0c;并增强了多语言支持。 模型架构 Lumina-Next-T2I 的生成模型建…

Redis学习 ——缓存

文章目录 一、Redis缓存的介绍二、Redis缓存问题2.1 缓存穿透2.2 缓存击穿2.3 缓存雪崩2.4 双写一致性2.5 缓存持久化RDBAOF 三、缓存数据管理3.1 数据过期策略3.2 数据淘汰策略 一、Redis缓存的介绍 我们在日常的代码编写中比较少使用到Redis&#xff0c;但是如果涉及到了比较…

【阅读记录-章节2】Build a Large Language Model (From Scratch)

目录 2.Working with text data2.1 Understanding word embeddings2.2 Tokenizing text通过一个简单的实验来理解文本的词元化概念关键概念 2.3 Converting tokens into token IDs实现分词器类&#xff08;Tokenizer Class&#xff09;应用分词器测试文本的编码与解码通过分词器…

etcd部署(基于v3.5.15)

etcd部署 单节点部署下载etcd&#xff0c;解压etcd二进制包&#xff0c;并进入解压后目录创建数据目录移动可执行文件到/usr/local/bin/目录测试版本配置systemd管理启动etcd&#xff0c;设置开机启动验证 集群部署(3节点)环境准备准备3台服务器配置3台服务器hosts配置3台服务器…

HTML5实现趣味飞船捡金币小游戏(附源码)

文章目录 1.设计来源1.1 主界面1.2 游戏中界面1.2 飞船边界框效果 2.效果和源码2.1 动态效果2.2 源代码 源码下载 作者&#xff1a;xcLeigh 文章地址&#xff1a;https://blog.csdn.net/weixin_43151418/article/details/143799554 HTML5实现趣味飞船捡金币小游戏(附源码)&…

ASP.NET Core Webapi 返回数据的三种方式

ASP.NET Core为Web API控制器方法返回类型提供了如下几个选择&#xff1a; Specific type IActionResult ActionResult<T> 1. 返回指定类型&#xff08;Specific type&#xff09; 最简单的API会返回原生的或者复杂的数据类型&#xff08;比如&#xff0c;string 或者…

汽车资讯新动力:Spring Boot技术驱动

2相关技术 2.1 MYSQL数据库 MySQL是一个真正的多用户、多线程SQL数据库服务器。 是基于SQL的客户/服务器模式的关系数据库管理系统&#xff0c;它的有点有有功能强大、使用简单、管理方便、安全可靠性高、运行速度快、多线程、跨平台性、完全网络化、稳定性等&#xff0c;非常…

Go语言跨平台桌面应用开发新纪元:LCL、CEF与Webview全解析

开篇寄语 在Go语言的广阔生态中&#xff0c;桌面应用开发一直是一个备受关注的领域。今天&#xff0c;我将为大家介绍三款基于Go语言的跨平台桌面应用开发框架——LCL、CEF与Webview&#xff0c;它们分别拥有独特的魅力和广泛的应用场景。通过这三款框架&#xff0c;你将能够轻…

如何确保爬取的数据准确性和完整性?

在数据驱动的业务环境中&#xff0c;爬虫程序的准确性和完整性至关重要。本文将探讨如何使用Java编写爬虫程序&#xff0c;并确保其在爬取数据时的准确性和完整性。 1. 精确的HTML解析 确保数据准确性的第一步是精确地解析HTML。Jsoup是Java中常用的HTML解析库&#xff0c;它提…

【linux】如何扩展磁盘容量(VMware虚拟机)-转载

如何扩展磁盘容量(VMware虚拟机) 一、前置准备工作 扩展虚拟机磁盘前&#xff0c;需要先把虚拟机关机才能进行扩展磁盘操作 1.选择虚拟机设置&#xff0c;如下图所示 2.输入你想扩展的磁盘容量&#xff0c;以本次实操为例&#xff0c;我这里输入的30G&#xff08;具体按照实…

esp32学习:windows下idf离线安装(最简单)

很多朋友学习esp32时发现&#xff0c;安装idf很麻烦&#xff0c;需要很多依赖&#xff0c;那有没有简单的方法呢&#xff0c;答案是肯定的. 乐鑫Windows 平台工具链的标准设置 - ESP32-S3 - — ESP-IDF 编程指南 release-v5.0 文档编程指南里提供了windows下离线安装包 dl.espr…

tensorflow案例6--基于VGG16的猫狗识别(准确率99.8%+),以及tqdm、train_on_batch的简介

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 前言 本次还是学习API和如何搭建神经网络为主&#xff0c;这一次用VGG16去对猫狗分类&#xff0c;效果还是很好的&#xff0c;达到了99.8% 文章目录 1、tqdm…

Amazon Web Services (AWS)

一、Amazon Web Services (AWS)介绍 1、简介 2、产品 AWS 提供了各种云计算服务&#xff0c;包括 DynamoDB、S3、EC2、Lambda 等等。 登录aws后点击所有服务也可以看到amazon的所有服务&#xff1a; 3、免费试用产品 除了免费的Amazon Step Functions、Amazon Lambda&#…

Quartus+Nios II for eclipse问题合集

由于对于FPGANIOS II 的工作需要&#xff0c;对工作过程中遇到的问题进行记录&#xff0c;持续更新。 1、BSP directory does not exist: . Stop.Nios II使用过程中遇到的一些问题2_error executing nios2-bsp-generate-files --bsp-di-CSDN博客https://blog.csdn.net/qq_39485…