Reputation: 83
I'm trying to develop an application which communicates with Bluetooth Low Energy Devices. I established a working "website" with the Web Bluetooth API. Everything works fine, so I used the Electron framework, to build an application.
The issue is known - if you start navigator.bluetooth.requestDevice()
, you get this error message:
User cancelled the requestDevice() chooser.
.
This causes due to the missing device chooser in Chromium. There are several topics about workarounds I found, but no examples. This is my first Electron project. Maybe somebody solved this problem and can give me a hint :-)
Upvotes: 2
Views: 6904
Reputation: 83
thank you very much for your support. According to your suggestions and some research, I developed a working solution and like to share it with you.
These two links helped me a lot:
https://github.com/electron/electron/issues/11865
https://github.com/electron/electron/issues/10764
Especially this post from MarshallOfSound – well described:
To get more Information about the main process and renderer process, events and their API, read this:
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 (already posted by Gerrit)
https://www.electronjs.org/docs/api/structures/bluetooth-device
For my application I wanted a device picker, as seen in Chrome. The sequence I wanted to implement is:
A reference to the code for the processes of the tutorial and the code snippet:
electron application: main.js (main process) renderer.js (render process) devicepicker GUI: devicepicker.js (renderer process) devicepicker.html & layout.css (GUI)
1) Create devicepicker with a GUI (I used and two ) and a script
2) In your main.js create a select-bluetooth-device
event inside the 'ready'
event of your application object (docs in links above) When you start navigator.bluetooth.requestDevice()
in your renderer.js, the event get’s fired and the devicelist is in the main process. With console.log(deviceList)
it's visible in the shell. To handle it you need to send it to the renderer process (your application window).
3) To achieve this, we implement webContents.send
of our BrowserWindow object inside the webContents.on event. Now the main process sends a devicelist every time he found new devices through the channel channelForBluetoothDeviceList
4) Create in renderer.js startDevicePicker()
. devicePicker()
must be started in the same function as navigator.bluetooth.requestDevice()
. startDevicePicker()
instantiates a new BrowserWindow()
object which loads devicepicker.html
5) To get the list from the main process a ipcRenderer.on()
listener must be implemented in startDevicePicker()
that listens to the channelForBluetoothDeviceList
channel of our main process. Now we can get the list in our electron application (renderer prcoess). To send it to the devicepicker UI, we need to forward it from our electron application (renderer process) to the devicepicker (also a renderer process)
6) To achieve this, we need the ipcRenderer.sendTo()
sender in devicePicker()
, which forwards messages between from a renderer process to a specific other renderer process. Additional to the channel bluetoothDeviceDiscoverList
we need the BrowserWindow.id
of the devicepicker. Since we just instantiated it, we can use our devicepicker object. I had a device which sended only once, the main process was faster than the build of the devicepicker and my list was never sent to the devicepicker. So I used a Promise()
to wait with ipcRenderer.sendTo()
until the devicepicker was ready to use.
7) To receive the devicelist at our devicepicker GUI, we need to listen to the bluetoothDeviceDiscoverList
with ipcRenderer.on()
(devicepicker.js). I inserted the devicelist now to the <option>
of the devicepicker, you can use of course other elements (devicepicker.html). Please note: implement a query that compares the sended list to the current. Otherwise you get multiple devices and your selection gets loooong. I still need to do that, it's not finished yet :-)
8) To select a device that navigator.bluetooth.requestDevice()
(renderer.js) gets resolved, we need to send back the BluetoothDevice.deviceId
of our selected device to the main process where we callback ‚callback‘ with deviceId as callback parameter (main.js).
9) Now we can use ipcRenderer.sendTo()
send the selected BluetoothDevice.deviceId
to the mainprocess (devicepicker.js).
10) In the main process (main.js) of our electron application we listen to the channel channelForSelectingDevice
with ipcMain.on()
and callback with the received BluetoothDevice.deviceId
. The device discovery gets stopped, navigator.bluetooth.requestDevice()
gets resolved and we receive data from our device in our application (renderer.js). To cancel the discovery of devices, listen with ipcMain.on()
in another channel channelForTerminationSignal
just a signal to the main process (main.js), for example after a click (devicepicker.js) and call the callback with an empty string (as written in the docs)
I admit it could be done much simpler without a devicepicker. Then just send the devicelist from the main process (main.js) to your application (renderer process). But this helped me a lot to understand the processes in electron. I hope this tutorial is useful for you :-) !
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();
}}
Upvotes: 6
Reputation: 4844
In your main.js Add this code snippet
if (process.platform === "linux"){
app.commandLine.appendSwitch("enable-experimental-web-platform-features", true);
} else {
app.commandLine.appendSwitch("enable-web-bluetooth", true);
}
This will enable the bluetooth at your Electron app. And use this as reference
https://github.com/electron/electron/issues/11865
https://github.com/electron/electron/issues/7367
https://github.com/aalhaimi/electron-web-bluetooth
But I'd suggest you to consider your Electron version.
Upvotes: 3
Reputation: 852
Here is a code sample that will just return the first device instead of having to implement a device chooser:
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);
}
});
Source: https://electronjs.org/docs/api/web-contents#event-select-bluetooth-device
Upvotes: 0