当前位置: 首页 > article >正文

ESP32 BLE实战:手把手教你用Web蓝牙API控制智能旋钮(附完整代码)

ESP32 BLE实战手把手教你用Web蓝牙API控制智能旋钮附完整代码在智能家居和物联网设备快速普及的今天蓝牙低功耗BLE技术因其低功耗、低成本的优势成为连接智能设备的首选方案之一。ESP32作为一款集成了Wi-Fi和蓝牙双模通信的微控制器凭借其出色的性能和丰富的开发资源成为物联网开发者的热门选择。本文将带你从零开始实现一个基于ESP32的BLE智能旋钮并通过Web Bluetooth API在浏览器中直接控制无需安装任何APP。1. BLE基础与项目架构设计蓝牙低功耗BLE通信中设备分为服务器Server和客户端Client两种角色。在我们的智能旋钮项目中ESP32作为服务器端负责广播服务和传输旋钮数据浏览器作为客户端通过Web Bluetooth API发现并连接ESP32实现数据的读写交互。GATT协议是BLE通信的核心它定义了数据的组织方式服务Service代表设备提供的功能集合如电池服务、设备信息服务等特性Characteristic服务中的具体数据点包含实际值和操作权限描述符Descriptor提供特性的额外元信息智能旋钮的BLE服务设计如下表组件UUID权限描述主服务4fafc201-1fb5-459e-8fcc-c5c9c331914b-包含旋钮和按钮特性旋钮特性beb5483e-36e1-4688-b7f5-ea07361b26a8读/写/通知传输旋钮旋转值按钮特性cba1d466-344c-4be3-ab3f-189f80dd7518读/写/通知传输按钮按压状态2. ESP32 BLE服务器实现2.1 硬件准备与开发环境搭建所需硬件组件ESP32开发板如ESP32-WROOM-32旋转编码器EC11或类似型号两个轻触开关10kΩ电阻若干面包板和连接线开发环境配置步骤安装Arduino IDE1.8.x或更高版本添加ESP32开发板支持# 在Arduino首选项中添加开发板管理器URL https://dl.espressif.com/dl/package_esp32_index.json安装ESP32开发板包通过开发板管理器安装BLE库在库管理器中搜索ESP32 BLE Arduino并安装2.2 BLE服务端完整代码实现以下是ESP32端的完整代码实现了包含旋钮和按钮特性的BLE服务#include BLEDevice.h #include BLEUtils.h #include BLEServer.h #define SERVICE_UUID 4fafc201-1fb5-459e-8fcc-c5c9c331914b #define KNOB_CHAR_UUID beb5483e-36e1-4688-b7f5-ea07361b26a8 #define BUTTON_CHAR_UUID cba1d466-344c-4be3-ab3f-189f80dd7518 // 引脚定义 const int encoderPinA 34; const int encoderPinB 35; const int button1Pin 32; const int button2Pin 33; // 全局变量 int encoderPos 0; int button1State 0; int button2State 0; bool deviceConnected false; // BLE特性定义 BLECharacteristic *pKnobCharacteristic; BLECharacteristic *pButtonCharacteristic; class MyServerCallbacks: public BLEServerCallbacks { void onConnect(BLEServer* pServer) { deviceConnected true; Serial.println(Device connected); } void onDisconnect(BLEServer* pServer) { deviceConnected false; Serial.println(Device disconnected); // 断开后重新开始广播 pServer-startAdvertising(); } }; void setup() { Serial.begin(115200); // 初始化GPIO pinMode(encoderPinA, INPUT); pinMode(encoderPinB, INPUT); pinMode(button1Pin, INPUT_PULLUP); pinMode(button2Pin, INPUT_PULLUP); // 创建BLE设备 BLEDevice::init(SmartKnob); BLEServer *pServer BLEDevice::createServer(); pServer-setCallbacks(new MyServerCallbacks()); // 创建BLE服务 BLEService *pService pServer-createService(SERVICE_UUID); // 添加旋钮特性 pKnobCharacteristic pService-createCharacteristic( KNOB_CHAR_UUID, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY ); pKnobCharacteristic-setValue(0); // 添加按钮特性 pButtonCharacteristic pService-createCharacteristic( BUTTON_CHAR_UUID, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY ); pButtonCharacteristic-setValue(0,0); // 启动服务 pService-start(); // 开始广播 BLEAdvertising *pAdvertising BLEDevice::getAdvertising(); pAdvertising-addServiceUUID(SERVICE_UUID); pAdvertising-setScanResponse(true); pAdvertising-setMinPreferred(0x06); BLEDevice::startAdvertising(); Serial.println(Waiting for a client connection...); } void loop() { // 读取旋钮状态 static int lastEncoderPos 0; int a digitalRead(encoderPinA); int b digitalRead(encoderPinB); // 此处添加旋钮解码逻辑... // 读取按钮状态 int newButton1State digitalRead(button1Pin); int newButton2State digitalRead(button2Pin); // 如果状态变化且设备已连接更新BLE特性 if (deviceConnected (encoderPos ! lastEncoderPos || newButton1State ! button1State || newButton2State ! button2State)) { // 更新旋钮值 char knobStr[16]; sprintf(knobStr, %d, encoderPos); pKnobCharacteristic-setValue(knobStr); pKnobCharacteristic-notify(); // 更新按钮状态 char buttonStr[16]; sprintf(buttonStr, %d,%d, newButton1State LOW ? 1 : 0, newButton2State LOW ? 1 : 0); pButtonCharacteristic-setValue(buttonStr); pButtonCharacteristic-notify(); lastEncoderPos encoderPos; button1State newButton1State; button2State newButton2State; } delay(10); }提示在实际项目中建议为旋钮编码器添加去抖动处理并在loop()中实现更精确的旋转检测算法。3. Web蓝牙客户端开发3.1 Web Bluetooth API基础Web Bluetooth API允许网页与附近的BLE设备通信主要接口包括navigator.bluetooth.requestDevice()请求连接设备device.gatt.connect()连接GATT服务器server.getPrimaryService()获取服务service.getCharacteristic()获取特性characteristic.readValue()/writeValue()读写数据characteristic.startNotifications()启用通知3.2 完整网页控制界面实现以下HTML代码实现了完整的Web控制界面包含设备连接、数据读取和实时通知功能!DOCTYPE html html head title智能旋钮控制器/title style body { font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto; padding: 20px; } .control-panel { border: 1px solid #ddd; padding: 20px; border-radius: 8px; } .status { margin: 15px 0; padding: 10px; background: #f5f5f5; border-radius: 4px; } button { padding: 10px 15px; background: #4285f4; color: white; border: none; border-radius: 4px; cursor: pointer; } button:disabled { background: #cccccc; } .knob-value { font-size: 24px; font-weight: bold; text-align: center; margin: 20px 0; } .button-state { display: flex; justify-content: space-around; margin: 20px 0; } .button { padding: 10px; border: 1px solid #ddd; border-radius: 4px; text-align: center; } .active { background: #4285f4; color: white; } /style /head body h1智能旋钮控制器/h1 div classcontrol-panel button idconnectBtn连接设备/button div classstatus idconnectionStatus未连接/div h3旋钮值/h3 div classknob-value idknobValue--/div h3按钮状态/h3 div classbutton-state div classbutton idbutton1按钮1: 未按下/div div classbutton idbutton2按钮2: 未按下/div /div /div script let bluetoothDevice; let knobCharacteristic; let buttonCharacteristic; document.getElementById(connectBtn).addEventListener(click, connectToDevice); async function connectToDevice() { try { console.log(Requesting Bluetooth Device...); bluetoothDevice await navigator.bluetooth.requestDevice({ filters: [{ name: SmartKnob }], optionalServices: [SERVICE_UUID] }); document.getElementById(connectionStatus).textContent 连接中...; console.log(Connecting to GATT Server...); const server await bluetoothDevice.gatt.connect(); document.getElementById(connectionStatus).textContent 已连接: bluetoothDevice.name; console.log(Getting Service...); const service await server.getPrimaryService(SERVICE_UUID); console.log(Getting Characteristics...); knobCharacteristic await service.getCharacteristic(KNOB_CHAR_UUID); buttonCharacteristic await service.getCharacteristic(BUTTON_CHAR_UUID); // 设置旋钮值变化通知 knobCharacteristic.addEventListener(characteristicvaluechanged, event { const value new TextDecoder().decode(event.target.value); document.getElementById(knobValue).textContent value; }); await knobCharacteristic.startNotifications(); // 设置按钮状态变化通知 buttonCharacteristic.addEventListener(characteristicvaluechanged, event { const value new TextDecoder().decode(event.target.value); const [btn1, btn2] value.split(,); const button1Element document.getElementById(button1); const button2Element document.getElementById(button2); button1Element.textContent 按钮1: ${btn1 1 ? 已按下 : 未按下}; button2Element.textContent 按钮2: ${btn2 1 ? 已按下 : 未按下}; button1Element.classList.toggle(active, btn1 1); button2Element.classList.toggle(active, btn2 1); }); await buttonCharacteristic.startNotifications(); // 读取初始值 const knobValue await knobCharacteristic.readValue(); document.getElementById(knobValue).textContent new TextDecoder().decode(knobValue); const buttonValue await buttonCharacteristic.readValue(); const [btn1, btn2] new TextDecoder().decode(buttonValue).split(,); const button1Element document.getElementById(button1); const button2Element document.getElementById(button2); button1Element.textContent 按钮1: ${btn1 1 ? 已按下 : 未按下}; button2Element.textContent 按钮2: ${btn2 1 ? 已按下 : 未按下}; button1Element.classList.toggle(active, btn1 1); button2Element.classList.toggle(active, btn2 1); // 处理设备断开连接 bluetoothDevice.addEventListener(gattserverdisconnected, onDisconnected); } catch(error) { console.error(Error:, error); document.getElementById(connectionStatus).textContent 连接失败: error.message; } } function onDisconnected() { console.log(Device disconnected); document.getElementById(connectionStatus).textContent 设备已断开; document.getElementById(knobValue).textContent --; document.getElementById(button1).textContent 按钮1: 未连接; document.getElementById(button2).textContent 按钮2: 未连接; document.getElementById(button1).classList.remove(active); document.getElementById(button2).classList.remove(active); } // 定义UUID常量 const SERVICE_UUID 4fafc201-1fb5-459e-8fcc-c5c9c331914b; const KNOB_CHAR_UUID beb5483e-36e1-4688-b7f5-ea07361b26a8; const BUTTON_CHAR_UUID cba1d466-344c-4be3-ab3f-189f80dd7518; /script /body /html4. 常见问题与优化策略4.1 连接稳定性问题解决方案在实际测试中可能会遇到以下连接问题设备无法被发现确保ESP32正在广播调用startAdvertising()检查设备名称是否匹配在Web Bluetooth的requestDevice()中添加optionalServices频繁断开连接实现自动重连机制bluetoothDevice.addEventListener(gattserverdisconnected, () { console.log(尝试重新连接...); setTimeout(connectToDevice, 1000); });在ESP32端增加断开检测和重新广播if (!deviceConnected oldDeviceConnected) { delay(500); // 等待连接完全断开 pServer-startAdvertising(); oldDeviceConnected deviceConnected; }通知丢失问题合并多个特性值到一个特性中传输增加数据校验和重传机制降低通知发送频率适当增加delay4.2 性能优化技巧数据格式优化使用二进制格式代替字符串传输uint8_t buffer[5]; buffer[0] encoderPos 0xFF; buffer[1] (encoderPos 8) 0xFF; buffer[2] button1State; buffer[3] button2State; pCombinedCharacteristic-setValue(buffer, 4);电源管理调整广播间隔pAdvertising-setMinPreferred(0x12); // 降低广播频率无连接时进入低功耗模式Web端优化添加连接状态指示器实现数据变化平滑过渡动画添加本地存储保存设备配对信息4.3 扩展功能实现基于现有框架可以轻松扩展更多功能多设备控制// 同时连接多个ESP32设备 const devices await Promise.all([ navigator.bluetooth.requestDevice({ filters: [{ name: SmartKnob-1 }] }), navigator.bluetooth.requestDevice({ filters: [{ name: SmartKnob-2 }] }) ]);数据记录与分析const knobHistory []; knobCharacteristic.addEventListener(characteristicvaluechanged, event { const value parseFloat(new TextDecoder().decode(event.target.value)); knobHistory.push({ timestamp: Date.now(), value }); // 绘制历史曲线图... });远程控制通过WebSocket将BLE数据转发到云端实现多用户协同控制在实际项目中我发现将ESP32的BLE广播间隔设置为100ms左右0x06能在连接速度和功耗间取得良好平衡。对于需要频繁更新数据的场景建议使用单个特性传输多个参数这比多个特性分别通知更可靠。

相关文章:

ESP32 BLE实战:手把手教你用Web蓝牙API控制智能旋钮(附完整代码)

ESP32 BLE实战:手把手教你用Web蓝牙API控制智能旋钮(附完整代码) 在智能家居和物联网设备快速普及的今天,蓝牙低功耗(BLE)技术因其低功耗、低成本的优势,成为连接智能设备的首选方案之一。ESP32…...

Windows 10/11 上 Docker 部署 MiGPT 4.2.0 全流程(含 Ollama 配置避坑指南)

Windows 10/11 上 Docker 部署 MiGPT 4.2.0 全流程(含 Ollama 配置避坑指南) 在本地运行大型语言模型(LLM)正成为开发者探索AI能力的新趋势。对于Windows用户而言,Docker提供了一种相对简单的环境隔离方案,…...

Python实战:基于LDA主题模型与情感分析的新能源汽车论坛口碑深度挖掘与竞品洞察

1. 为什么需要分析新能源汽车论坛数据? 最近两年新能源汽车市场简直像坐上了火箭,各家品牌你追我赶好不热闹。作为数据科学从业者,我经常被问到:"现在消费者到底最关心什么?"、"我们的产品在用户眼中真…...

比迪丽LoRA模型GitHub打不开时的备选方案:镜像站下载与部署

比迪丽LoRA模型GitHub打不开时的备选方案:镜像站下载与部署 最近想玩一下比迪丽LoRA模型,结果第一步就卡住了——GitHub打不开。这应该是很多国内开发者都遇到过的问题,尤其是在需要快速部署一些热门AI项目的时候。别担心,GitHub…...

PDF-Parser-1.0功能实测:公式转LaTeX,表格转JSON,真实好用

PDF-Parser-1.0功能实测:公式转LaTeX,表格转JSON,真实好用 1. 从“头疼”到“真香”:我的PDF处理体验转变 上周,我收到一份30多页的学术论文PDF,里面密密麻麻全是公式和表格。我需要把里面的数据整理出来…...

OpenCV min/max函数避坑指南:为什么你的图像比较结果总是不对?

OpenCV min/max函数避坑指南:为什么你的图像比较结果总是不对? 在计算机视觉项目中,图像像素级比较是最基础却最容易出错的环节之一。许多开发者在使用OpenCV的min()和max()函数时,明明按照文档调用了接口,结果却与预期…...

Android开发实战:JNA库版本冲突与32/64位兼容性问题的终极解决方案

Android开发实战:JNA库版本冲突与32/64位兼容性问题的终极解决方案 在Android开发中,JNA(Java Native Access)库为开发者提供了一种无需编写复杂JNI代码即可调用本地库的便捷方式。然而,随着项目复杂度提升和硬件架构多…...

Docker+Guacamole实战:5分钟搞定远程桌面网关(含MySQL配置避坑指南)

DockerGuacamole实战:5分钟搭建企业级远程桌面网关 在数字化转型浪潮中,远程办公已成为企业刚需。想象一下这样的场景:出差在外的销售总监需要紧急查看公司内网的CRM系统,外包开发团队需要安全访问测试服务器,分支机构…...

Typora风格技术文档撰写:借助万象熔炉·丹青幻境自动生成Markdown内容

Typora风格技术文档撰写:借助万象熔炉丹青幻境自动生成Markdown内容 每次写技术文档,你是不是也头疼过?尤其是项目README、API说明这类需要结构清晰、格式美观的文档。手动调整标题层级、插入代码块、制作表格,不仅耗时耗力&…...

GLM-OCR赋能在线教育:自动批改手写作业与试卷

GLM-OCR赋能在线教育:自动批改手写作业与试卷 每次看到孩子带回来一沓沓需要批改的作业和试卷,你是不是也替老师感到头疼?尤其是现在很多在线教育平台,老师隔着屏幕,要对着学生上传的、五花八门的手写作业照片打分&am…...

FireRedASR-AED-L生产环境实践:医院门诊录音→结构化病历初稿生成

FireRedASR-AED-L生产环境实践:医院门诊录音→结构化病历初稿生成 1. 引言:从录音到病历的自动化挑战 想象一下这个场景:一位医生结束了一天的门诊,面对几十段与患者的对话录音,需要手动整理成规范的电子病历。这个过…...

VIIRS卫星数据下载避坑指南:从NOAA到NASA的完整流程(2023最新版)

VIIRS卫星数据下载避坑指南:从NOAA到NASA的完整流程(2023最新版) 深夜盯着屏幕前闪烁的FTP连接超时提示,这是我第三次尝试下载VIIRS的SDR数据。作为遥感领域的新手,本以为按照官方文档操作就能顺利获取数据&#xff0c…...

避坑指南:为什么你的pip离线安装whl总是失败?90%人不知道的平台兼容性检查方法

深度解析Python离线安装whl包的平台兼容性问题与实战解决方案 在Python生态中,whl(wheel)格式的二进制包因其安装便捷性而广受欢迎。然而,当开发者尝试在离线环境中安装whl包时,经常会遇到各种兼容性问题,导…...

SQLline避坑指南:从入门到精通的问题解决方案

SQLline避坑指南:从入门到精通的问题解决方案 【免费下载链接】sqlline Shell for issuing SQL to relational databases via JDBC 项目地址: https://gitcode.com/gh_mirrors/sq/sqlline SQLline作为一款通过JDBC连接关系型数据库的Shell工具,是…...

Ostrakon-VL-8B助力SolidWorks设计文档智能检索

Ostrakon-VL-8B助力SolidWorks设计文档智能检索 你是不是也遇到过这种情况?在电脑里翻找几个月前画的一个零件图,只记得大概形状和几个关键尺寸,但文件名早就忘了。或者,新来的同事想参考一个老项目的装配体设计,面对…...

【嵌入式】牧马人G3 电子竞技鼠标芯片A702/A704深度解析与应用探索

1. 牧马人G3电竞鼠标芯片A702/A704初探 第一次拆开牧马人G3鼠标时,那颗标着"INSTAN A702D"的小芯片让我愣了半天。作为一款主打性价比的电竞鼠标,它的核心竟藏着这么个神秘角色。后来查资料才发现,A702和A704这对兄弟芯片在入门级电…...

微信小程序进阶:mobx-miniprogram与miniprogram-computed的实战融合指南

1. 为什么需要同时使用mobx-miniprogram和miniprogram-computed 在开发复杂微信小程序时,我们经常遇到两种典型场景:一是需要在多个组件间共享全局状态(比如用户登录信息、购物车数据),二是需要在单个组件内部处理复杂…...

他励直流电动机启动策略的仿真建模与性能对比

1. 他励直流电动机启动策略概述 第一次接触他励直流电动机时,我被它那"简单粗暴"的直接启动方式吓了一跳——就像突然把油门踩到底的汽车,电流瞬间飙升到额定值的10倍以上。这种启动方式虽然简单,但对电机和电网的冲击实在太大了。…...

告别插件英文障碍:obsidian-i18n让高效汉化变得简单

告别插件英文障碍:obsidian-i18n让高效汉化变得简单 【免费下载链接】obsidian-i18n 项目地址: https://gitcode.com/gh_mirrors/ob/obsidian-i18n 你是否曾在使用Obsidian插件时,因全英文界面而反复切换翻译软件?是否遇到过因术语理…...

GP2Y1014AU粉尘传感器在TI MSPM0开发板上的ADC驱动与浓度计算实战

GP2Y1014AU粉尘传感器在TI MSPM0开发板上的ADC驱动与浓度计算实战 最近在做一个小型空气质量监测站,用到了GP2Y1014AU这款粉尘传感器。很多刚开始接触嵌入式环境监测的朋友都问,怎么把传感器读到的电压值变成我们能看懂的PM2.5浓度?今天我就以…...

泛微E8自定义报表实战:从虚拟表单到查询菜单的完整配置流程

泛微E8自定义报表实战:从虚拟表单到查询菜单的完整配置流程 在当今企业数字化办公环境中,数据的高效呈现与灵活查询已成为提升管理决策速度的关键。泛微E8作为国内领先的协同办公平台,其自定义报表功能能够将分散的业务数据转化为直观的可视化…...

阿里云容器镜像服务避坑指南:Docker推送失败的5个常见原因及解决方法

阿里云容器镜像服务深度排障手册:从Docker推送失败到高效运维 当你第17次在深夜尝试将Docker镜像推送到阿里云仓库却看到红色的错误提示时,那种挫败感我深有体会。作为每天处理数百次镜像推送的DevOps工程师,我整理了一份你在任何官方文档都找…...

Meta-Llama-3-8B-Instruct保姆级部署教程:5分钟在3060显卡上跑通AI对话

Meta-Llama-3-8B-Instruct保姆级部署教程:5分钟在3060显卡上跑通AI对话 1. 引言 1.1 为什么选择Meta-Llama-3-8B-Instruct 如果你正在寻找一个能在消费级显卡上流畅运行的AI对话模型,Meta-Llama-3-8B-Instruct绝对值得考虑。这个80亿参数的模型专为指…...

Ubuntu 22.04 下 Gazebo Fortress 与 TurtleBot3 仿真实战:从零部署到避障挑战

1. 环境准备:Ubuntu 22.04与ROS 2 Humble基础配置 在开始Gazebo Fortress与TurtleBot3的仿真之旅前,我们需要确保系统环境正确配置。Ubuntu 22.04作为长期支持版本,提供了稳定的基础,而ROS 2 Humble则是与之完美匹配的机器人操作系…...

Android MQTT开发避坑指南:Hivemq Client自动重连的正确姿势

Android MQTT开发避坑指南:Hivemq Client自动重连的正确姿势 在物联网应用开发中,MQTT协议因其轻量级和高效性成为设备通信的首选方案。Hivemq MQTT Client作为Java生态中的明星库,为Android开发者提供了强大的MQTT功能支持。然而&#xff0c…...

Ostrakon-VL-8B自动化测试:基于Python的模型接口全面验证

Ostrakon-VL-8B自动化测试:基于Python的模型接口全面验证 最近在部署一个多模态大模型服务,模型上线后最怕什么?不是效果不好,而是服务不稳定。用户上传一张图片,等了半天没反应,或者返回一个莫名其妙的错…...

从比对到过滤:BMGE在多序列比对后处理中的实战应用指南

从比对到过滤:BMGE在多序列比对后处理中的实战应用指南 在系统发育分析中,多序列比对的质量直接影响着最终结果的可靠性。然而,即使是使用MAFFT等优秀工具生成的比对结果,也常常包含一些对齐不良的区域。这些区域可能由于高变异性…...

黑苹果配置太复杂?OpCore Simplify的自动化引擎让EFI创建效率提升90%

黑苹果配置太复杂?OpCore Simplify的自动化引擎让EFI创建效率提升90% 【免费下载链接】OpCore-Simplify A tool designed to simplify the creation of OpenCore EFI 项目地址: https://gitcode.com/GitHub_Trending/op/OpCore-Simplify 价值定位&#xff1a…...

PP-DocLayoutV3惊艳案例:装订孔遮挡区域通过多点边界框实现语义级补全

PP-DocLayoutV3惊艳案例:装订孔遮挡区域通过多点边界框实现语义级补全 1. 新一代统一布局分析引擎 PP-DocLayoutV3作为新一代统一布局分析引擎,彻底改变了传统文档处理方式。与以往只能识别简单矩形区域的工具不同,它能够精准识别文档中的各…...

电动机突然反转?可能是三相电反相序在作怪!5种排查方法总结

电动机突然反转?可能是三相电反相序在作怪!5种排查方法总结 在工业现场,电动机突然反转往往会让维护工程师措手不及。上周某化工厂的离心泵就出现了这种情况——明明按下正转启动按钮,设备却反向旋转,差点导致管道系统…...