Electron + Vite 集成 Node 蓝牙开发实战:从环境搭建到设备通信的全流程
在桌面应用开发中,实现蓝牙设备通信是许多物联网、硬件交互场景的核心需求。Electron 作为跨平台桌面应用框架,结合 Vite 的快速构建能力和 Node.js 的蓝牙模块,为开发者提供了高效的蓝牙开发解决方案。本文将详细讲解如何在 Electron + Vite 项目中集成 Node 蓝牙功能,从环境搭建、权限配置到设备扫描、数据交互,通过实战代码示例展示完整开发流程,帮助开发者快速掌握桌面应用与蓝牙设备的通信技术。
一、技术栈与开发环境搭建
Electron + Vite + Node 蓝牙开发的技术栈组合,兼顾了开发效率与功能完整性,需要提前做好环境配置。
1. 核心技术组件
Electron:提供桌面应用运行环境,支持 Node.js API 在渲染进程中使用
Vite:作为构建工具,提供快速的热更新和模块打包能力
@abandonware/noble:Node.js 蓝牙低功耗(BLE)开发库,是 noble 的活跃分支
electron-builder:用于应用打包,处理不同平台的蓝牙权限配置
2. 项目初始化与依赖安装
# 创建Vite项目 npm create vite@latest electron-bluetooth -- --template vanilla cd electron-bluetooth # 安装Electron及相关依赖 npm install electron electron-builder vite-plugin-electron --save-dev npm install @electron/remote --save # 安装蓝牙开发库 npm install @abandonware/noble
3. 基础配置文件
vite.config.js(配置 Electron 插件):
import { defineConfig } from 'vite' import electron from 'vite-plugin-electron' export default defineConfig({ plugins: [ electron({ entry: 'electron/main.js', // Electron主进程入口 }), ], })
electron/main.js(主进程基础配置):
import { app, BrowserWindow } from 'electron' import path from 'path' // 允许渲染进程使用Node API app.whenReady().then(() => { const mainWindow = new BrowserWindow({ width: 800, height: 600, webPreferences: { nodeIntegration: true, // 启用Node集成 contextIsolation: false, // 关闭上下文隔离 preload: path.join(__dirname, 'preload.js') } }) // 加载Vite开发服务器 mainWindow.loadURL('http://localhost:5173') mainWindow.webContents.openDevTools() })
二、蓝牙功能核心实现
Node 蓝牙开发的核心流程包括设备扫描、连接、数据读写,需要在 Electron 主进程中处理蓝牙操作以避免权限问题。
1. 蓝牙模块封装
创建electron/bluetooth.js封装蓝牙操作:
import noble from '@abandonware/noble' export const BluetoothManager = { // 初始化蓝牙适配器 init() { return new Promise((resolve, reject) => { if (noble.state === 'poweredOn') { resolve() } else { noble.on('stateChange', (state) => { if (state === 'poweredOn') { resolve() } else { reject(new Error(`蓝牙状态异常: ${state}`)) } }) } }) }, // 扫描蓝牙设备 startScanning() { return new Promise((resolve) => { const devices = [] noble.startScanning([], true) // 扫描所有设备,允许重复发现 noble.on('discover', (peripheral) => { // 过滤重复设备 if (!devices.some(d => d.id === peripheral.id)) { devices.push({ id: peripheral.id, name: peripheral.advertisement.localName || '未知设备', rssi: peripheral.rssi // 信号强度 }) resolve([...devices]) // 实时返回设备列表 } }) }) }, // 停止扫描 stopScanning() { noble.stopScanning() }, // 连接设备 connect(deviceId) { return new Promise((resolve, reject) => { noble.startScanning([], false, () => { noble.findAsync(peripheral => peripheral.id === deviceId) .then(peripheral => { if (!peripheral) { reject(new Error('设备未找到')) return } peripheral.connect(error => { if (error) { reject(error) } else { resolve(peripheral) } }) }) }) }) } }
2. 主进程与渲染进程通信
在主进程中注册蓝牙操作接口,通过 IPC 与渲染进程通信:
// electron/main.js中添加 import { ipcMain } from 'electron' import { BluetoothManager } from './bluetooth.js' // 初始化蓝牙 ipcMain.handle('bluetooth:init', async () => { return await BluetoothManager.init() }) // 开始扫描设备 ipcMain.handle('bluetooth:scan', async () => { return await BluetoothManager.startScanning() }) // 停止扫描 ipcMain.on('bluetooth:stop-scan', () => { BluetoothManager.stopScanning() }) // 连接设备 ipcMain.handle('bluetooth:connect', async (_, deviceId) => { return await BluetoothManager.connect(deviceId) })
3. 渲染进程调用实现
在前端页面中通过 IPC 调用蓝牙功能:
// src/main.js import { ipcRenderer } from 'electron' // 页面元素 const scanBtn = document.getElementById('scan-btn') const deviceList = document.getElementById('device-list') const statusEl = document.getElementById('status') // 初始化蓝牙 async function initBluetooth() { try { statusEl.textContent = '初始化蓝牙...' await ipcRenderer.invoke('bluetooth:init') statusEl.textContent = '蓝牙已就绪' scanBtn.disabled = false } catch (error) { statusEl.textContent = `初始化失败: ${error.message}` } } // 扫描设备 async function startScan() { statusEl.textContent = '开始扫描设备...' scanBtn.disabled = true try { const devices = await ipcRenderer.invoke('bluetooth:scan') deviceList.innerHTML = '' devices.forEach(device => { const item = document.createElement('div') item.className = 'device-item' item.innerHTML = ` <h4>${device.name}</h4> <p>ID: ${device.id}</p> <p>信号强度: ${device.rssi}</p> <button onclick="connectDevice('${device.id}')">连接</button> ` deviceList.appendChild(item) }) } catch (error) { statusEl.textContent = `扫描失败: ${error.message}` } } // 连接设备 window.connectDevice = async (deviceId) => { statusEl.textContent = '正在连接设备...' try { await ipcRenderer.invoke('bluetooth:connect', deviceId) statusEl.textContent = '设备连接成功' } catch (error) { statusEl.textContent = `连接失败: ${error.message}` } } // 页面加载时初始化 window.onload = initBluetooth scanBtn.addEventListener('click', startScan)
三、数据读写与设备交互
连接蓝牙设备后,需要通过 GATT 服务读写数据,实现与设备的实际交互。
1. 服务与特征值操作
扩展BluetoothManager添加数据读写功能:
// 在electron/bluetooth.js中添加 async function discoverServicesAndCharacteristics(peripheral) { return new Promise((resolve, reject) => { peripheral.discoverAllServicesAndCharacteristics((error, services, characteristics) => { if (error) { reject(error) } else { resolve({ services, characteristics }) } }) }) } // 写入数据到特征值 async function writeToCharacteristic(characteristic, data) { return new Promise((resolve, reject) => { characteristic.write(Buffer.from(data), false, (error) => { if (error) reject(error) else resolve() }) }) } // 订阅特征值通知 function subscribeToCharacteristic(characteristic, callback) { characteristic.on('data', (data) => { callback(data.toString()) }) characteristic.notify(true) } // 添加到BluetoothManager BluetoothManager.writeData = async (peripheral, serviceUuid, charUuid, data) => { const { characteristics } = await discoverServicesAndCharacteristics(peripheral) const targetChar = characteristics.find(c => c.service.uuid === serviceUuid && c.uuid === charUuid ) if (!targetChar) { throw new Error('未找到目标特征值') } await writeToCharacteristic(targetChar, data) } BluetoothManager.subscribeData = async (peripheral, serviceUuid, charUuid, callback) => { const { characteristics } = await discoverServicesAndCharacteristics(peripheral) const targetChar = characteristics.find(c => c.service.uuid === serviceUuid && c.uuid === charUuid ) if (targetChar) { subscribeToCharacteristic(targetChar, callback) } }
2. 主进程与渲染进程数据交互
// 在electron/main.js中添加数据交互接口 let currentPeripheral = null ipcMain.handle('bluetooth:write', async (_, serviceUuid, charUuid, data) => { if (!currentPeripheral) { throw new Error('未连接设备') } await BluetoothManager.writeData(currentPeripheral, serviceUuid, charUuid, data) }) ipcMain.on('bluetooth:subscribe', (event, serviceUuid, charUuid) => { if (currentPeripheral) { BluetoothManager.subscribeData(currentPeripheral, serviceUuid, charUuid, (data) => { // 发送数据到渲染进程 event.sender.send('bluetooth:data', data) }) } }) // 连接设备时保存当前设备 ipcMain.handle('bluetooth:connect', async (_, deviceId) => { currentPeripheral = await BluetoothManager.connect(deviceId) return true })
3. 渲染进程数据交互界面
// src/main.js中添加数据交互功能 const sendBtn = document.getElementById('send-btn') const dataInput = document.getElementById('data-input') const receiveArea = document.getElementById('receive-area') // 发送数据 sendBtn.addEventListener('click', async () => { const data = dataInput.value if (!data) return try { // 假设已知服务UUID和特征值UUID const serviceUuid = 'ffe0' const charUuid = 'ffe1' await ipcRenderer.invoke('bluetooth:write', serviceUuid, charUuid, data) statusEl.textContent = '数据发送成功' } catch (error) { statusEl.textContent = `发送失败: ${error.message}` } }) // 接收数据 ipcRenderer.on('bluetooth:data', (event, data) => { receiveArea.value += `收到数据: ${data}\n` }) // 订阅数据通知 window.subscribeData = async () => { try { const serviceUuid = 'ffe0' const charUuid = 'ffe1' ipcRenderer.send('bluetooth:subscribe', serviceUuid, charUuid) statusEl.textContent = '已订阅数据通知' } catch (error) { statusEl.textContent = `订阅失败: ${error.message}` } }
四、权限配置与跨平台适配
不同操作系统对蓝牙权限的要求不同,需要在打包配置中进行相应设置。
1. Windows 平台配置
在package.json中添加:
"build": { "win": { "target": "nsis", "icon": "public/icon.ico" }, "nsis": { "permissions": [ "admin" // Windows蓝牙操作可能需要管理员权限 ] } }
2. macOS 平台配置
添加蓝牙权限说明(Info.plist):
<!-- 在electron-builder配置中添加 --> "mac": { "extendInfo": { "NSBluetoothAlwaysUsageDescription": "应用需要蓝牙权限以连接设备", "NSBluetoothPeripheralUsageDescription": "应用需要蓝牙权限以与设备通信" } }
3. Linux 平台配置
Linux 需要额外安装蓝牙工具并配置权限:
# 安装蓝牙工具 sudo apt-get install bluez bluetooth # 添加用户到蓝牙组 sudo usermod -a -G bluetooth $USER
五、调试与打包发布
1. 开发调试脚本
在package.json中添加脚本:
"scripts": { "dev": "vite", "electron:dev": "vite build --watch", "build": "vite build && electron-builder" }
启动开发环境:
npm run dev
2. 打包发布
# 构建并打包应用 npm run build
打包完成后,在dist目录下生成对应平台的安装包。
六、常见问题与解决方案
1. 蓝牙初始化失败
确保蓝牙硬件已开启
Windows 平台可能需要以管理员身份运行
Linux 平台检查蓝牙服务是否启动:sudo service bluetooth start
2. 设备扫描不到
检查设备是否处于可发现模式
部分设备需要配对码,需在系统蓝牙设置中提前配对
尝试重启蓝牙适配器:noble.stopScanning(); noble.startScanning()
3. 数据读写失败
确认服务 UUID 和特征值 UUID 正确(注意大小写和格式)
检查特征值是否支持读写操作(通过characteristic.properties查看)
数据格式需转换为 Buffer:Buffer.from(data)
七、总结
Electron + Vite + Node 蓝牙开发组合,为桌面应用提供了高效的蓝牙设备交互能力。通过本文介绍的方法,开发者可以快速实现从设备扫描、连接到数据交互的完整功能:
利用 Vite 的快速构建提升开发效率
通过 Electron 主进程处理蓝牙操作,规避权限问题
使用 @abandonware/noble 库实现跨平台蓝牙通信
注意不同操作系统的权限配置和适配细节
在实际开发中,还需根据具体蓝牙设备的协议规范,调整服务 UUID、特征值 UUID 和数据解析方式。随着物联网技术的发展,这种桌面应用与硬件设备的交互方式将在智能家居、工业控制等领域发挥重要作用。掌握这套技术栈,能帮助开发者快速构建功能完善的蓝牙交互桌面应用。<|FCResponseEnd|>
转自:https://blog.csdn.net/xhphs23680/article/details/150217543
- 文章2312
- 用户1336
- 访客11619001
书籍是永不过期的护照。
You Only Look Once:Unified, Real-Time Object Detection-CVPR-2016
Thinkpad x1 Extreme黑苹果10.14.5安装完成
C++ 11新语法获取系统盘符
cocos2d-x横版ARPG过关游戏
程序员应该使用Linux的7个理由
去除WPS2016个人版自带广告弹窗
x86 emulation currently requires hardware acceleration
数字证书及CA的通俗介绍
Android c++屏幕实时录制
快速入门-如何在Java上使用Redis
请启用虚拟机平台 windows 功能并确保在 bios 中启用虚拟化
diskgenius 保存分区表时出现错误 代码00000032方法解决
.a静态库创建与合并