《Electron 跨平台应用开发:蓝牙模块实战(一套代码Windows/macOS/Linux 适配)》

  • 时间:2025-12-10 23:38 作者: 来源: 阅读:0
  • 扫一扫,手机访问
摘要:接上篇《Electron + Vite 集成 Node 蓝牙开发实战》 因为上篇只介绍了noble-winrt只支持win10/win11,不支持其他系统,如linux(银河麒麟),macOS。那么我们要兼容多系统怎么办呢,办法有的朋友,我在我的项目中已经实战过了,同样抽取蓝牙部分拿出来做一个demo讲解,手把手喂饭给没有做过或正在为node蓝牙多系统适配焦头烂额的朋友,看的有用的,帮我gith
接上篇《Electron + Vite 集成 Node 蓝牙开发实战》

因为上篇只介绍了noble-winrt只支持win10/win11,不支持其他系统,如linux(银河麒麟),macOS。那么我们要兼容多系统怎么办呢,办法有的朋友,我在我的项目中已经实战过了,同样抽取蓝牙部分拿出来做一个demo讲解,手把手喂饭给没有做过或正在为node蓝牙多系统适配焦头烂额的朋友,看的有用的,帮我github点个星星!!!!

https://github.com/Jadeite2/electron-vite--abandonware-noble

一、环境要求

node.js: 20.x.x

Python 3.11.9

node-gyp v9.4.1

VisualStudioSetup(选择使用c++的桌面开发)

windows安装依赖要注意,因为这次要用的node蓝牙属于是c++的开发扩展,所以要安装node-gyp、VisualStudioSetup并选择使用c++的桌面开发安装后,才能完成node_moudel的编译和安装。

本文的node蓝牙api设备发现、连接、服务/特征发现、数据收发流程与上一篇 noble 基本一致,如下。 本次我们使用@abandonware/noble来作为蓝牙模块来开发功能。

二、目录结构

使用electron-egg v4官方结构



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:(需要自己想办法获取,或者硬件开发会有蓝牙uuid文档)

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

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

本次需要注意蓝牙模块api返回的characteristics返回的是4位16进制UUID!!

四、运行效果

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文件夹中abandonwareNble.js对@abandonware/noble方法的总结及参考(本文重点之一)

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

1、基本引用 

2、事件监听

noble.on('stateChange', callback)   监听蓝牙适配器状态变化
状态:`poweredOn`, `poweredOff`, `resetting`, `unauthorized`, `unsupported`, `unknown`

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



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

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



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`:是否允许重复发现同一设备。


noble.startScanning([], true);

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


noble.stopScanning();
4、Peripheral(设备对象)

peripheral.id 设备唯一标识符。

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

  - `localName`

  - `txPowerLevel`

  - `manufacturerData`

  - `serviceData`

  - `serviceUuids`

  - `solicitationServiceUuids`

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


peripheral.connect((error) => { ... });

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

- `serviceUUIDs`:要发现的服务 UUID 数组

- 回调参数:`(error, services)`  


peripheral.discoverServices([serviceUuid], (error, services) => { ... });

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


peripheral.disconnect((error) => { ... });
5、Service(服务对象)

service.uuid  服务的 UUID

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

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

- 回调参数:`(error, characteristics)`


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

characteristic.uuid 特征的 UUID。

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

- `data`:用Buffer 类型数据。

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


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

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


characteristic.subscribe((error) => { ... });

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



characteristic.on('data', (data) => {
 
    // data为Buffer
 
  });

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


characteristic.unsubscribe((error) => { ... });

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

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

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

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

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

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



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
            });
          });

5. 通过 `peripheral.discoverServices()` 获取服务。

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

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

六、macOS 权限问题

启动奔溃:

解决方案:系统设置-隐私与安全性-蓝牙,为终端添加权限

打包设置:需要在electron打包配置中写入获取mac系统权限的字段,以及在根目录下新建 entitlements.mac.plist文件:



"mac": {
    // ...
    "extendInfo": {
      "NSBluetoothAlwaysUsageDescription": "This app uses Bluetooth to communicate with nearby devices."
    },
    "entitlements": "entitlements.mac.plist"
  }


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
  <dict>
    <key>com.apple.security.device.bluetooth</key>
    <true/>
  </dict>
</plist>

这样macOS intel架构的打包就没问题了。

- 不要直接套用@abandonware/noble里面的api,用我项目里的api,都是经过我自己踩坑看源码测试出来的。

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

github地址:https://github.com/Jadeite2/electron-vite--abandonware-noble

  • 全部评论(0)
最新发布的资讯信息
【系统环境|】政府二级架构网络等保三级设计方案(2025-12-11 00:56)
【系统环境|】【光纤单元电路测试方案】(2025-12-11 00:56)
【系统环境|】油藏地质的模型(2025-12-11 00:55)
【系统环境|】从零开始了解数据采集技术篇(6)——认数采通讯网络(2025-12-11 00:55)
【系统环境|】电商行业中的智能化供应链管理(2025-12-11 00:55)
【系统环境|】EtherCAT主站开发(学习笔记七)(2025-12-11 00:55)
【系统环境|】《中英双语版•七律金鼎凌云暨玄武金鼎吟》作者王本海、赏评木兰(2025-12-11 00:55)
【系统环境|】成毅片场意外受伤:坠落后脑勺触地,当场蜷缩翻滚无法起身,其官(2025-12-11 00:54)
【系统环境|】被严重低估的文玩手串排行榜前八名:不炒高价,花小钱玩出大花样(2025-12-11 00:54)
【系统环境|】刚健蕴雅:清代曾国荃二十幅经典书法赏析(2025-12-11 00:54)
手机二维码手机访问领取大礼包
返回顶部