Electron + Vite 集成 Node 蓝牙开发实战:从环境搭建到设备通信的全流程

Home / Article MrLee 18天前 48

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

本文链接:http://it72.com/12789.htm

推荐阅读
最新回复 (0)
返回