Electron Web Bluetooth API requestDevice() 错误
Electron Web Bluetooth API requestDevice() Error
我正在尝试开发一个与低功耗蓝牙设备通信的应用程序。我使用 Web 蓝牙 API 建立了一个工作 "website"。一切正常,所以我使用 Electron 框架来构建应用程序。
问题已知 - 如果您启动 navigator.bluetooth.requestDevice()
,您会收到此错误消息:
User cancelled the requestDevice() chooser.
.
这是由于 Chromium 中缺少设备选择器造成的。我发现了几个关于解决方法的主题,但没有示例。这是我的第一个 Electron 项目。也许有人解决了这个问题并且可以给我提示 :-)
在你的main.js中添加这段代码
if (process.platform === "linux"){
app.commandLine.appendSwitch("enable-experimental-web-platform-features", true);
} else {
app.commandLine.appendSwitch("enable-web-bluetooth", true);
}
这将在您的 Electron 应用程序中启用蓝牙。
并以此作为参考
https://github.com/electron/electron/issues/11865
https://github.com/electron/electron/issues/7367
https://github.com/aalhaimi/electron-web-bluetooth
但我建议您考虑一下您的 Electron 版本。
这里是一个代码示例,它将 return 第一个设备,而不是必须实现设备选择器:
mainWindow.webContents.on('select-bluetooth-device', (event, deviceList, callback) => {
event.preventDefault();
console.log('Device list:', deviceList);
let result = deviceList[0];
if (!result) {
callback('');
} else {
callback(result.deviceId);
}
});
来源:https://electronjs.org/docs/api/web-contents#event-select-bluetooth-device
非常感谢您的支持。根据您的建议和一些研究,我开发了一个可行的解决方案并愿意与您分享。
这两个链接对我帮助很大:
https://github.com/electron/electron/issues/11865
https://github.com/electron/electron/issues/10764
尤其是来自 MarshallOfSound 的 post – 描述得很好:
- 在主进程中挂钩事件
- 使用设备列表向渲染器进程发送消息
- 在渲染器进程中显示UI
- 发送selected设备到主进程
- 调用回调
要获取有关主进程和渲染器进程、事件及其 API 的更多信息,请阅读:
https://www.electronjs.org/docs/tutorial/application-architecture#main-and-renderer-processes
https://www.electronjs.org/docs/api/ipc-main
https://www.electronjs.org/docs/api/web-contents#contentssendchannel-args
https://www.electronjs.org/docs/api/ipc-renderer
https://electronjs.org/docs/api/web-contents#event-select-bluetooth-device(已被 Gerrit post编辑)
https://www.electronjs.org/docs/api/structures/bluetooth-device
对于我的应用程序,我想要一个设备选择器,如 Chrome 中所示。我想实现的顺序是:
- 开始申请
- 搜索设备
- Devicepicker 弹出
- Select 一台设备
- Devicepicker 关闭
- 查看申请中的数据
参考教程流程和代码片段的代码:
electron应用程序:main.js(主进程)renderer.js(渲染进程)devicepicker GUI:devicepicker.js(渲染进程)devicepicker.html & layout.css (GUI)
1) 用一个 GUI(我用了两个)和一个脚本创建 devicepicker
2) 在您的 main.js 中,在应用程序对象的 'ready'
事件中创建一个 select-bluetooth-device
事件(上面链接中的文档)当您在 renderer.js 中启动 navigator.bluetooth.requestDevice()
时,事件被触发并且设备列表在主进程中。使用 console.log(deviceList)
它在 shell 中可见。要处理它,您需要将它发送到渲染器进程(您的应用程序 window)。
3) 为了实现这一点,我们在 webContents.on 事件中实现了 BrowserWindow 对象的 webContents.send
。现在主进程每次通过通道 channelForBluetoothDeviceList
发现新设备时都会发送一个设备列表
4) 在 renderer.js startDevicePicker()
中创建。 devicePicker()
必须在与 navigator.bluetooth.requestDevice()
相同的函数中启动。 startDevicePicker()
实例化一个新的 BrowserWindow()
对象,它加载 devicepicker.html
5) 要从主进程获取列表,必须在 startDevicePicker()
中实现一个 ipcRenderer.on()
侦听器来侦听 channelForBluetoothDeviceList
我们主要过程的通道。现在我们可以在我们的电子应用程序(渲染器进程)中获取列表。要将它发送到 devicepicker UI,我们需要将它从我们的电子应用程序(渲染器进程)转发到 devicepicker(也是一个渲染器进程)
6) 为了实现这一点,我们需要 devicePicker()
中的 ipcRenderer.sendTo()
发送器,它将消息从渲染器进程转发到特定的其他渲染器过程。除了通道 bluetoothDeviceDiscoverList
,我们还需要设备选择器的 BrowserWindow.id
。因为我们刚刚实例化了它,所以我们可以使用我们的 devicepicker 对象。我有一个只发送一次的设备,主要过程比 devicepicker 的构建更快,而且我的列表从未发送到 devicepicker。所以我用 Promise()
和 ipcRenderer.sendTo()
等待,直到设备选择器准备好使用。
7) 要在我们的设备选择器 GUI 接收设备列表,我们需要使用 ipcRenderer.on()
(devicepicker.js).我现在将设备列表插入到设备选择器的 <option>
中,您当然可以使用其他元素 (devicepicker.html)。请注意:实施一个查询,将发送的列表与当前列表进行比较。否则你会得到多个设备并且你的 selection 会变得很长。我还需要这样做,还没有完成:-)
8) 到 select navigator.bluetooth.requestDevice()
(renderer.js) 得到解析的设备,我们需要发回 BluetoothDevice.deviceId
我们的 selected 设备到主进程,我们在主进程中使用 deviceId 作为回调参数 (main.js) 回调“回调”。
9) 现在我们可以使用 ipcRenderer.sendTo()
将 selected BluetoothDevice.deviceId
发送到主进程 (devicepicker.js)。
10) 在电子应用程序的主进程 (main.js) 中,我们使用 ipcMain.on()
和回调监听通道 channelForSelectingDevice
收到 BluetoothDevice.deviceId
。设备发现停止,navigator.bluetooth.requestDevice()
得到解决,我们在应用程序中从我们的设备接收数据 (renderer.js)。要取消发现设备,请在另一个频道中用 ipcMain.on()
收听 channelForTerminationSignal
只是一个信号到主进程 (main.js),例如在单击后 (devicepicker.js) 和使用空字符串调用回调(如文档中所写)
我承认如果没有设备选择器,它可以做得更简单。然后只需将设备列表从主进程 (main.js) 发送到您的应用程序(渲染器进程)。但这对我理解电子过程有很大帮助。我希望本教程对您有用:-)!
片段:
main.js
const { ipcMain, app, BrowserWindow } = require('electron')
let win = null;
var callbackForBluetoothEvent = null;
// Create the browser window.
function createWindow () {
win = new BrowserWindow({
webPreferences: {
nodeIntegration: true //to activate require()
}
})
win.maximize()
win.show()
//This sender sends the devicelist from the main process to all renderer processes
win.webContents.on('select-bluetooth-device', (event, deviceList, callback) => {
event.preventDefault(); //important, otherwise first available device will be selected
console.log(deviceList); //if you want to see the devices in the shell
let bluetoothDeviceList = deviceList;
callbackForBluetoothEvent = callback; //to make it accessible outside createWindow()
win.webContents.send('channelForBluetoothDeviceList', bluetoothDeviceList);
});
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on('ready', createWindow)
//cancels Discovery
ipcMain.on('channelForTerminationSignal', _ => {
callbackForBluetoothEvent(''); //reference to callback of win.webContents.on('select-bluetooth-device'...)
console.log("Discovery cancelled");
});
//resolves navigator.bluetooth.requestDevice() and stops device discovery
ipcMain.on('channelForSelectingDevice', (event, DeviceId) => {
callbackForBluetoothEvent(sentDeviceId); //reference to callback of win.webContents.on('select-bluetooth-device'...)
console.log("Device selected, discovery finished");
})
renderer.js
function discoverDevice() {
navigator.bluetooth.requestDevice()
startDevicepicker()
}
function startDevicepicker(){
let devicepicker = null;
let mainProcessDeviceList = null;
devicepicker = new BrowserWindow({
width: 350,
height: 270,
show: false, //needed to resolve promise devicepickerStarted()
webPreferences: {
nodeIntegration: true
}
})
devicepicker.loadFile('devicePicker.html');
//electron application listens for the devicelist from main process
ipcRenderer.on('channelForBluetoothDeviceList', (event, list) => {
mainProcessDeviceList = list;
devicepickerStarted.then(_=> {
console.log("Promise resolved!");
ipcRenderer.sendTo(devicepicker.webContents.id, 'bluetoothDeviceDiscoverList', mainProcessDeviceList);
})
})
//Promise that ensures that devicepicker GUI gets the list if the device only sends once
var devicepickerStarted = new Promise(
function (resolve, reject) {
console.log("Promise started");
devicepicker.once('ready-to-show', () => {
devicepicker.show();
resolve();
console.log("Devicepicker is ready!")
})
}
)
//remove listeners after closing devicepicker
devicepicker.on('closed', _ => {
devicepicker = null;
ipcRenderer.removeAllListeners('channelForBluetoothDeviceList');
ipcRenderer.removeAllListeners('currentWindowId');
ipcRenderer.removeAllListeners('receivedDeviceList');
})
}
devicepicker.js
//save received list here
var myDeviceList = new Array();
//Html elements
const devicePickerSelection = document.getElementById("devicePickerSelection");
const buttonSelect = document.getElementById("Select");
const buttonCancel = document.getElementById("Cancel");
//eventListeners for buttons
buttonSelect.addEventListener('click', selectFromDevicePicker);
buttonCancel.addEventListener('click', cancelDevicePicker);
//listens for deviceList
ipcRenderer.on('receivedDeviceList', (event, bluetoothDeviceDiscoverList) => {
console.log("list arrived!")
//code: add list to html element
});
function selectFromDevicePicker() {
let selectedDevice = devicePickerSelection.value;
let deviceId = //depends on where you save the BluetoothDevice.deviceId values
//sends deviceId to main process for callback to resolve navigator.bluetooth.requestDevice()
ipcRenderer.send('channelForSelectingDevice', deviceId);
ipcRenderer.removeAllListeners('receivedDeviceList');
closeDevicePicker();
}
function cancelDevicePicker() {
ipcRenderer.send('channelForTerminationSignal');
closeDevicePicker();
}
function closeDevicePicker() {
myDevicePicker.close();
}}
我正在尝试开发一个与低功耗蓝牙设备通信的应用程序。我使用 Web 蓝牙 API 建立了一个工作 "website"。一切正常,所以我使用 Electron 框架来构建应用程序。
问题已知 - 如果您启动 navigator.bluetooth.requestDevice()
,您会收到此错误消息:
User cancelled the requestDevice() chooser.
.
这是由于 Chromium 中缺少设备选择器造成的。我发现了几个关于解决方法的主题,但没有示例。这是我的第一个 Electron 项目。也许有人解决了这个问题并且可以给我提示 :-)
在你的main.js中添加这段代码
if (process.platform === "linux"){
app.commandLine.appendSwitch("enable-experimental-web-platform-features", true);
} else {
app.commandLine.appendSwitch("enable-web-bluetooth", true);
}
这将在您的 Electron 应用程序中启用蓝牙。 并以此作为参考
https://github.com/electron/electron/issues/11865
https://github.com/electron/electron/issues/7367
https://github.com/aalhaimi/electron-web-bluetooth
但我建议您考虑一下您的 Electron 版本。
这里是一个代码示例,它将 return 第一个设备,而不是必须实现设备选择器:
mainWindow.webContents.on('select-bluetooth-device', (event, deviceList, callback) => {
event.preventDefault();
console.log('Device list:', deviceList);
let result = deviceList[0];
if (!result) {
callback('');
} else {
callback(result.deviceId);
}
});
来源:https://electronjs.org/docs/api/web-contents#event-select-bluetooth-device
非常感谢您的支持。根据您的建议和一些研究,我开发了一个可行的解决方案并愿意与您分享。
这两个链接对我帮助很大:
https://github.com/electron/electron/issues/11865
https://github.com/electron/electron/issues/10764
尤其是来自 MarshallOfSound 的 post – 描述得很好:
- 在主进程中挂钩事件
- 使用设备列表向渲染器进程发送消息
- 在渲染器进程中显示UI
- 发送selected设备到主进程
- 调用回调
要获取有关主进程和渲染器进程、事件及其 API 的更多信息,请阅读:
https://www.electronjs.org/docs/tutorial/application-architecture#main-and-renderer-processes
https://www.electronjs.org/docs/api/ipc-main
https://www.electronjs.org/docs/api/web-contents#contentssendchannel-args
https://www.electronjs.org/docs/api/ipc-renderer
https://electronjs.org/docs/api/web-contents#event-select-bluetooth-device(已被 Gerrit post编辑)
https://www.electronjs.org/docs/api/structures/bluetooth-device
对于我的应用程序,我想要一个设备选择器,如 Chrome 中所示。我想实现的顺序是:
- 开始申请
- 搜索设备
- Devicepicker 弹出
- Select 一台设备
- Devicepicker 关闭
- 查看申请中的数据
参考教程流程和代码片段的代码:
electron应用程序:main.js(主进程)renderer.js(渲染进程)devicepicker GUI:devicepicker.js(渲染进程)devicepicker.html & layout.css (GUI)
1) 用一个 GUI(我用了两个)和一个脚本创建 devicepicker
2) 在您的 main.js 中,在应用程序对象的 'ready'
事件中创建一个 select-bluetooth-device
事件(上面链接中的文档)当您在 renderer.js 中启动 navigator.bluetooth.requestDevice()
时,事件被触发并且设备列表在主进程中。使用 console.log(deviceList)
它在 shell 中可见。要处理它,您需要将它发送到渲染器进程(您的应用程序 window)。
3) 为了实现这一点,我们在 webContents.on 事件中实现了 BrowserWindow 对象的 webContents.send
。现在主进程每次通过通道 channelForBluetoothDeviceList
4) 在 renderer.js startDevicePicker()
中创建。 devicePicker()
必须在与 navigator.bluetooth.requestDevice()
相同的函数中启动。 startDevicePicker()
实例化一个新的 BrowserWindow()
对象,它加载 devicepicker.html
5) 要从主进程获取列表,必须在 startDevicePicker()
中实现一个 ipcRenderer.on()
侦听器来侦听 channelForBluetoothDeviceList
我们主要过程的通道。现在我们可以在我们的电子应用程序(渲染器进程)中获取列表。要将它发送到 devicepicker UI,我们需要将它从我们的电子应用程序(渲染器进程)转发到 devicepicker(也是一个渲染器进程)
6) 为了实现这一点,我们需要 devicePicker()
中的 ipcRenderer.sendTo()
发送器,它将消息从渲染器进程转发到特定的其他渲染器过程。除了通道 bluetoothDeviceDiscoverList
,我们还需要设备选择器的 BrowserWindow.id
。因为我们刚刚实例化了它,所以我们可以使用我们的 devicepicker 对象。我有一个只发送一次的设备,主要过程比 devicepicker 的构建更快,而且我的列表从未发送到 devicepicker。所以我用 Promise()
和 ipcRenderer.sendTo()
等待,直到设备选择器准备好使用。
7) 要在我们的设备选择器 GUI 接收设备列表,我们需要使用 ipcRenderer.on()
(devicepicker.js).我现在将设备列表插入到设备选择器的 <option>
中,您当然可以使用其他元素 (devicepicker.html)。请注意:实施一个查询,将发送的列表与当前列表进行比较。否则你会得到多个设备并且你的 selection 会变得很长。我还需要这样做,还没有完成:-)
8) 到 select navigator.bluetooth.requestDevice()
(renderer.js) 得到解析的设备,我们需要发回 BluetoothDevice.deviceId
我们的 selected 设备到主进程,我们在主进程中使用 deviceId 作为回调参数 (main.js) 回调“回调”。
9) 现在我们可以使用 ipcRenderer.sendTo()
将 selected BluetoothDevice.deviceId
发送到主进程 (devicepicker.js)。
10) 在电子应用程序的主进程 (main.js) 中,我们使用 ipcMain.on()
和回调监听通道 channelForSelectingDevice
收到 BluetoothDevice.deviceId
。设备发现停止,navigator.bluetooth.requestDevice()
得到解决,我们在应用程序中从我们的设备接收数据 (renderer.js)。要取消发现设备,请在另一个频道中用 ipcMain.on()
收听 channelForTerminationSignal
只是一个信号到主进程 (main.js),例如在单击后 (devicepicker.js) 和使用空字符串调用回调(如文档中所写)
我承认如果没有设备选择器,它可以做得更简单。然后只需将设备列表从主进程 (main.js) 发送到您的应用程序(渲染器进程)。但这对我理解电子过程有很大帮助。我希望本教程对您有用:-)!
片段:
main.js
const { ipcMain, app, BrowserWindow } = require('electron')
let win = null;
var callbackForBluetoothEvent = null;
// Create the browser window.
function createWindow () {
win = new BrowserWindow({
webPreferences: {
nodeIntegration: true //to activate require()
}
})
win.maximize()
win.show()
//This sender sends the devicelist from the main process to all renderer processes
win.webContents.on('select-bluetooth-device', (event, deviceList, callback) => {
event.preventDefault(); //important, otherwise first available device will be selected
console.log(deviceList); //if you want to see the devices in the shell
let bluetoothDeviceList = deviceList;
callbackForBluetoothEvent = callback; //to make it accessible outside createWindow()
win.webContents.send('channelForBluetoothDeviceList', bluetoothDeviceList);
});
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on('ready', createWindow)
//cancels Discovery
ipcMain.on('channelForTerminationSignal', _ => {
callbackForBluetoothEvent(''); //reference to callback of win.webContents.on('select-bluetooth-device'...)
console.log("Discovery cancelled");
});
//resolves navigator.bluetooth.requestDevice() and stops device discovery
ipcMain.on('channelForSelectingDevice', (event, DeviceId) => {
callbackForBluetoothEvent(sentDeviceId); //reference to callback of win.webContents.on('select-bluetooth-device'...)
console.log("Device selected, discovery finished");
})
renderer.js
function discoverDevice() {
navigator.bluetooth.requestDevice()
startDevicepicker()
}
function startDevicepicker(){
let devicepicker = null;
let mainProcessDeviceList = null;
devicepicker = new BrowserWindow({
width: 350,
height: 270,
show: false, //needed to resolve promise devicepickerStarted()
webPreferences: {
nodeIntegration: true
}
})
devicepicker.loadFile('devicePicker.html');
//electron application listens for the devicelist from main process
ipcRenderer.on('channelForBluetoothDeviceList', (event, list) => {
mainProcessDeviceList = list;
devicepickerStarted.then(_=> {
console.log("Promise resolved!");
ipcRenderer.sendTo(devicepicker.webContents.id, 'bluetoothDeviceDiscoverList', mainProcessDeviceList);
})
})
//Promise that ensures that devicepicker GUI gets the list if the device only sends once
var devicepickerStarted = new Promise(
function (resolve, reject) {
console.log("Promise started");
devicepicker.once('ready-to-show', () => {
devicepicker.show();
resolve();
console.log("Devicepicker is ready!")
})
}
)
//remove listeners after closing devicepicker
devicepicker.on('closed', _ => {
devicepicker = null;
ipcRenderer.removeAllListeners('channelForBluetoothDeviceList');
ipcRenderer.removeAllListeners('currentWindowId');
ipcRenderer.removeAllListeners('receivedDeviceList');
})
}
devicepicker.js
//save received list here
var myDeviceList = new Array();
//Html elements
const devicePickerSelection = document.getElementById("devicePickerSelection");
const buttonSelect = document.getElementById("Select");
const buttonCancel = document.getElementById("Cancel");
//eventListeners for buttons
buttonSelect.addEventListener('click', selectFromDevicePicker);
buttonCancel.addEventListener('click', cancelDevicePicker);
//listens for deviceList
ipcRenderer.on('receivedDeviceList', (event, bluetoothDeviceDiscoverList) => {
console.log("list arrived!")
//code: add list to html element
});
function selectFromDevicePicker() {
let selectedDevice = devicePickerSelection.value;
let deviceId = //depends on where you save the BluetoothDevice.deviceId values
//sends deviceId to main process for callback to resolve navigator.bluetooth.requestDevice()
ipcRenderer.send('channelForSelectingDevice', deviceId);
ipcRenderer.removeAllListeners('receivedDeviceList');
closeDevicePicker();
}
function cancelDevicePicker() {
ipcRenderer.send('channelForTerminationSignal');
closeDevicePicker();
}
function closeDevicePicker() {
myDevicePicker.close();
}}