Electron+Node蓝牙开发实战:noble-winrt从零到精通

把近期做的项目抽取蓝牙部分拿出来做一个demo讲解,手把手喂饭给没有做过或正在为node蓝牙焦头烂额的朋友,看的有用的,麻烦帮我github点个星星

github地址:github.com/Jadeite2/el...

一、概述

使用electorn-egg V4 版本开发,集成 noble-winrt (蓝牙低功耗)蓝牙的demo
noble-winrt 的优点 :

  1. 天然支持win10/win11,
  2. 不需要安装额外驱动,不需要插usb外设,不会影响本机蓝牙。

缺点:

  1. 不能跨平台支持其他操作系统(我这个项目想要一套代码支持macOS和linux)
  2. 文档缺失(对于第一次开发集成蓝牙项目的人非常不友好,用ai的话它会给你自己造api坑你),目前是2025年,npm和github上显示作者最后维护是7年前!!!!

这是npm上的状态,详情页完全没有api,本人靠阅读源码,请教硬件大佬一点点啃下来的,经验来之不易,麻烦帮我github点个星星

electorn-egg V4需要先在根目录npm install,然后cd到frontend目录下进行npm install,最后回到外层根目录 npm run dev运行!

✨ "noble-winrt": "^0.1.1"开发(较老旧且无人维护,npm及github无api),仅支持win10/win11系统,无需安装其他编译插件(后续会再出一个基于noble需要插件编译,不需要安装驱动的跨平台支持macOS,win,linux的demo)

使用node v20.12.1

二、目录结构

js 复制代码
electorn-egg V4版本官方结构
project
├── package.json npm包配置
├── bulid 打包用的资源和脚本
    ├── icons 软件图标(打包用到)
    ├── extraResources 额外资源目录
├── cmd 脚本/打包 命令配置
    ├── bin.js 开发环境配置    
    ├── builder-xxx.json 打包配置
├── electron 主进程服务
    ├── main.js 入口文件 
    ├── config 配置文件
        ├── config.default.js 默认配置,都会加载
        ├── config.local.js dev环境加载
        ├── config.prod.js 生产环境加载
    ├── controller 控制器
    ├── service 业务层
    ├── preload 预加载
        ├── index.js 入口文件,在程序启动时加载,如托盘、自动升级等功能要提前加载代码
        ├── bridge.js 桥接文件
        ├── lifecycle.js 生命周期函数
    ├── jobs 任务
├── frontend 前端目录(demo是用vue编写的)  
├── go go目录(可选)
├── out 打包后生成的可执行文件
    ├── latest.yml 自动升级文件
    ├── xxx.exe window应用安装包
    ├── xxx.exe.blockmap window应用增量升级包
    ├── xxx.dmg mac应用安装包
    ├── xxx.deb linux应用安装包后缀有多种    
├── logs 日志 
├── public 资源目录
    ├── dist 前端资源会移动到这里,生产环境加载
    ├── electron 主进程代码,生产环境加载
    ├── html 一些模板
    ├── images 一些图片
├── data 内置数据库文件
    ├── sqlite-demo.db 示例sqlite数据库

我们主要关注electron 主进程服务controller 控制器 和 service 业务层,frontend 前端目录的view的indx.vue

三、运行demo准备

1、手机需要把本机蓝牙名改为AK62
2、手机上安装BLE蓝牙调试助手(后续调试用)
3、准备设备的特征UUID:SERVICE_UUID、NOTIFY_UUID、WRITE_UUID:

在蓝牙通信中这三个uuid是最重要的,NOTIFY_UUID负责订阅消息,WRITE_UUID负责向蓝牙设备发送消息,NOTIFY_UUID 和WRITE_UUID 是服务下的特征(Characteristic),必须归属于某个 SERVICE_UUID。

4、两种方式获取手机调试设备的特征UUID:
第一种:

使用2个手机,都安装上面的BLE调试助手,一个手机选从机模式,另一个手机搜索连接蓝牙名AK62的从机或者是自己手机蓝牙名的从机,可以查看到fff0,fff1,fff2特征,替换到frontend 前端目录的view的indx.vue的86-89行

第二种:

如果你没有两个手机那就直接运行项目,手机的BLE选择从机模式并开启广播

1、运行项目后Ctrl+Shift+I 打开开发者工具,点击扫描设备,等待列表出现AK62,点击连接,会报错,报错没关系,要的就是报错,报错就会有uuid,把uuid复制到index.vue的第86行SERVICE_UUID

2、然后重新运行项目,再扫描和连接,此时因为NOTIFY_UUID和WRITE_UUID也是我手机的特征,虽然连接成功了但是订阅不到也发送不了消息到蓝牙,这次要查看终端的控制台,找到打印消息是 11111111111的日志记录,找到里面uuid有fff2的Characteristic,把fff2的替换到index.vue的第88行WRITE_UUID中

3、找到里面uuid有fff1的Characteristic,把fff1的替换到index.vue的第87行 NOTIFY_UUID中

4、至此大功告成80%了,重新运行项目 npm run dev,重新扫描和连接手机上的BLE调试助手,选择从机模式,就可以通信了。切换到设备模式就是让手机变成蓝牙从机

四、运行效果

1、运行项目后可以按Ctrl+Shift+I 打开开发者工具
2、点击扫描设备,会出现列表,做了过滤只会显示蓝牙名为AK62的设备,单击设备会连接设备并订阅设备特征
3、连接设备后页面的控制台可以看到数据监听已启动、停止扫描设备!
4、连接设备后发送0XB0,会向蓝牙从机发送Buffer数据,可以看service的ble.js的buildProtocolPacket方法,业务需求可能会有一些帧头帧尾+校验和的验证处理,需要自己根据业务实现
5、BLE蓝牙调试助手中会收到数据 95 AB 04B0 00 00 00
6、在BLE蓝牙调试助手向electron发送11111111111111,electron接收到数据控制台会打印如下

五、service文件夹中ble.js对noble-winrt方法的总结及参考 (本文重点之一)

Electron的ipc通信不在本文讲解范围内,请自行阅读electron文档及electron-egg框架文档。

1、 基本引用
js 复制代码
const noble = require('noble-winrt');
2、事件监听

noble.on('stateChange', callback) 监听蓝牙适配器状态变化

状态:poweredOn, poweredOff, resetting, unauthorized, unsupported, unknown

主要用到poweredOn,判断当前电脑蓝牙是否可用

js 复制代码
noble.on('stateChange', (state) => {
      // logger.info('BLE状态变化:', state);
      if (state === 'poweredOn') {
        // logger.info('蓝牙已启用,可以开始扫描');
      } else {
        // logger.warn('蓝牙不可用:', state);
        this.stopScan();
      }
    });

noble.on('discover', callback) 发现蓝牙设备时触发。回调参数为 peripheral 设备对象。

js 复制代码
noble.on('discover', (peripheral) => {
      console.log(`发现设备: ${peripheral}`);
      // 简化设备名称处理,只使用localName或默认名称
      const deviceName = peripheral.advertisement.localName || '--';
      const deviceInfo = {
        id: peripheral.id,
        name: deviceName,
        rssi: peripheral.rssi,
        connectable: peripheral.connectable,
        state: peripheral.state,
        advertisement: {
          localName: peripheral.advertisement.localName,
          txPowerLevel: peripheral.advertisement.txPowerLevel,
          manufacturerData: peripheral.advertisement.manufacturerData ? peripheral.advertisement.manufacturerData.toString('hex') : null,
          serviceData: peripheral.advertisement.serviceData,
          serviceUuids: peripheral.advertisement.serviceUuids || [],
          solicitationServiceUuids: peripheral.advertisement.solicitationServiceUuids || []
        },
        peripheral: peripheral // 保存完整的peripheral对象以便后续连接使用
      };
      this.devices.set(peripheral.id, deviceInfo);
    });
3、扫描相关

noble.startScanning(serviceUUIDs, allowDuplicates) 开始扫描设备。

  • serviceUUIDs:要扫描的服务 UUID 数组,空数组表示扫描所有设备。(这个参数我发填入了会扫描不到,所以我都不填)

  • allowDuplicates:是否允许重复发现同一设备。

js 复制代码
noble.startScanning([], true);

noble.stopScanning(callback) 停止扫描设备。

js 复制代码
noble.stopScanning();
4、Peripheral(设备对象)

peripheral.id 设备唯一标识符。

peripheral.advertisement 设备广播信息对象,包含:

  • localName

  • txPowerLevel

  • manufacturerData

  • serviceData

  • serviceUuids

  • solicitationServiceUuids

peripheral.connect(callback) 连接到设备。(ble.js第188行)

js 复制代码
peripheral.connect((error) => { ... });

peripheral.discoverServices(serviceUUIDs, callback) 发现设备的服务。(ble.js第195行)

  • serviceUUIDs:要发现的服务 UUID 数组

  • 回调参数:(error, services)

js 复制代码
peripheral.discoverServices([serviceUuid], (error, services) => { ... });

peripheral.disconnect(callback)- 断开设备连接。

js 复制代码
peripheral.disconnect((error) => { ... });
5、Service(服务对象)

service.uuid 服务的 UUID

service.discoverCharacteristics(characteristicUUIDs, callback) 发现服务下的特征(ble.js第208行)

  • characteristicUUIDs:要发现的特征 UUID 数组。

  • 回调参数:(error, characteristics)

js 复制代码
service.discoverCharacteristics([writeUuid, notifyUuid], (error, characteristics) => { ... });
6. Characteristic(特征对象)

characteristic.uuid 特征的 UUID。

characteristic.write(data, withoutResponse, callback) 向特征写入数据。

  • data:用Buffer 类型数据。

  • withoutResponse:布尔值,是否不需要响应。

js 复制代码
characteristic.write(Buffer.from([0x01, 0x02]), false, (error) => { ... });

characteristic.subscribe(callback) 订阅特征的通知(notify),订阅后才能监听蓝牙数据返回。

js 复制代码
characteristic.subscribe((error) => { ... });

characteristic.on('data', callback)监听特征数据变化(notify)。

js 复制代码
characteristic.on('data', (data) => {
 
    // data为Buffer
 
  });

characteristic.unsubscribe(callback) 取消订阅通知。

js 复制代码
characteristic.unsubscribe((error) => { ... });

characteristic.removeAllListeners('data') 移除所有数据监听器。

参考流程(结合本项目代码)

  1. 监听 stateChange,poweredOn 时允许扫描。

  2. 调用 noble.startScanning() 开始扫描。

  3. 监听 discover 事件,获取 peripheral。

  4. 通过 peripheral.connect() 连接设备,连接的时候对特征做一个存储,读写的时候方便用,不要每次重新查找 ,如下。

js 复制代码
service.discoverCharacteristics([].filter(Boolean), (error, characteristics) => {
            logger.info(characteristics, '11111111111') 
            if (error) {
              reject(error);
              return;
            }
            // 缓存 writeUuid 特征
            const writeChar = characteristics.find(item => item.uuid === writeUuid);
            if (writeChar) {
              this.characteristics.set(writeUuid, writeChar);
              // logger.info('设备连接成功并缓存writeUuid特征:', writeUuid);
            }
            // 缓存 notifyUuid 特征(如果有)
            if (notifyUuid) {
              const notifyChar = characteristics.find(item => item.uuid === notifyUuid);
              if (notifyChar) {
                this.characteristics.set(notifyUuid, notifyChar);
                // logger.info('设备连接成功并缓存notifyUuid特征:', notifyUuid);
              }
            }
            // logger.info(this.characteristics, 'this.characteristicsthis.characteristicsthis.characteristics', this.characteristics.size)
            resolve({
              success: true,
              message: '设备连接成功',
              device: { id: device.id, name: device.name },
              services: services.length
            });
          });
  1. 通过 peripheral.discoverServices() 获取服务。

  2. 通过 service.discoverCharacteristics() 获取特征。

  3. 通过 characteristic.write() 写数据,characteristic.subscribe() 订阅通知,characteristic.on('data') 监听数据。

六、其他说明

  • noble-winrt 仅支持 Windows 10/11,且需要蓝牙适配器支持 BLE。

  • 设备发现、连接、服务/特征发现、数据收发流程与经典 noble 基本一致。

  • 详细参数和回调结构可参考 noble 文档,但是不完全一样,不要直接套用里面的api,用我项目里的api,都是经过我自己踩坑看源码测试出来的。

看的有用的,麻烦帮我github点个星星, 一键999连!!!

github地址:github.com/Jadeite2/el...

gitee地址:gitee.com/jadeite2/el...

相关推荐
月走乂山1 天前
DSH 桌面版用 DeepSeek 报 REQUEST_EXTENSION 的根因定位与修复
electron·node.js·插件系统·deepseek·bug排查·dsh·deepseek harness
mont evergreen2 天前
一封从外部世界寄来的信:二维码数据如何穿越三重门,抵达你的屏幕
electron·vue·事件
fellow993 天前
DSH Desktop:把 DeepSeek Harness 变成真正的桌面应用
electron·agent·ai编程·deepseek
CDwenhuohuo3 天前
electron pc项目打包成桌面端
前端·javascript·electron
不可能片场4 天前
命令行中文变问号 我用环境变量救了场
前端·electron
不可能片场5 天前
AI视频口型对齐 用音频驱动说话镜头
前端·electron
Zoom5 天前
【开源】7 天,我用 Rust 复刻了 FinalShell!Rhost v1.0.0 发布
rust·electron·shell
爱丶不疚7 天前
Electron: Sentry 都采集了什么?你知道吗
前端·electron
格数致用7 天前
03- Luckysheet与Vue3+Electron集成实战指南
electron·vue3·luckysheet