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. 监听 stateChangepoweredOn 时允许扫描。

  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 天前
Electron自定义菜单栏及Mac最大化无效的问题解决
前端·electron
卸任2 天前
Electron自制翻译工具:自动更新
前端·react.js·electron
唐璜Taro3 天前
electron自定义国内镜像
前端·javascript·electron
bilupilu3 天前
electron 静默安装同时安装完成后自动启动(nsis)
前端·javascript·electron
之梦4 天前
Electron + Vue3开源跨平台壁纸工具实战(十二)颜色壁纸
前端·electron
龙国浪子7 天前
从零构建桌面写作软件的书籍管理系统:Electron + Vue 3 实战指南
vue.js·electron
Jerry_Rod7 天前
Electron一小时新手快速入门
前端·electron
PegasusYu7 天前
Electron使用WebAssembly实现CRC-16 IBM校验
electron·nodejs·wasm·webassembly·ibm·crc16·crc-16