0

我正在尝试开发一个与蓝牙低功耗设备通信的应用程序。我使用 Web Bluetooth API 建立了一个有效的“网站”。一切正常,所以我使用了 Electron 框架来构建一个应用程序。

该问题是已知的 - 如果您开始navigator.bluetooth.requestDevice(),您会收到以下错误消息: User cancelled the requestDevice() chooser..

这是由于 Chromium 中缺少设备选择器造成的。我找到了几个关于解决方法的主题,但没有示例。这是我的第一个电子项目。也许有人解决了这个问题,可以给我一个提示:-)

4

3 回答 3

2

非常感谢您的支持。根据您的建议和一些研究,我开发了一个可行的解决方案,并希望与您分享。

这两个链接对我帮助很大:

https://github.com/electron/electron/issues/11865

https://github.com/electron/electron/issues/10764

尤其是 MarshallOfSound 的这篇文章——描述得很好:

  • 主进程挂钩事件
  • 使用设备列表向渲染器进程发送消息
  • 在渲染器进程中显示 UI
  • 将选定的设备发送到主进程
  • 来电回拨

要获取有关主进程和渲染器进程、事件及其 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 发布)

https://www.electronjs.org/docs/api/structures/bluetooth-device

对于我的应用程序,我想要一个设备选择器,如 Chrome 中所示。我想实现的顺序是:

  • 开始申请
  • 搜索设备
  • 设备选择器弹出
  • 选择设备
  • Devicepicker 关闭
  • 查看应用程序中的数据

参考教程流程的代码和代码片段:

电子应用程序:main.js(主进程)renderer.js(渲染进程)devicepicker GUI:devicepicker.js(渲染进程)devicepicker.html & layout.css(GUI)

1)用一个GUI(我用过两个)和一个脚本创建devicepicker

2)在您的 main.js 中,在您的应用程序对象的事件中创建一个select-bluetooth-device事件'ready'(上面链接中的文档)当您navigator.bluetooth.requestDevice()在 renderer.js 中启动时,该事件被触发并且设备列表位于主进程中。它在console.log(deviceList)外壳中可见。要处理它,您需要将其发送到渲染器进程(您的应用程序窗口)。

3)为此,我们webContents.send在 webContents.on 事件中实现 BrowserWindow 对象。现在主进程每次通过通道发现新设备时都会发送一个设备列表channelForBluetoothDeviceList

4)在 renderer.js 中创建startDevicePicker()devicePicker()必须在与 相同的功能中启动navigator.bluetooth.requestDevice()startDevicePicker()实例化一个BrowserWindow()加载的新对象devicepicker.html

5)要从主进程中获取列表,ipcRenderer.on()必须在其中实现一个监听startDevicePicker()器来监听channelForBluetoothDeviceList我们主进程的通道。现在我们可以在我们的电子应用程序(渲染器进程)中获取列表。要将其发送到 devicepicker UI,我们需要将其从电子应用程序(渲染器进程)转发到 devicepicker(也是渲染器进程)

6)为此,我们需要ipcRenderer.sendTo()发送者 in devicePicker(),它将消息从渲染器进程转发到特定的其他渲染器进程。除了bluetoothDeviceDiscoverList我们需要BrowserWindow.iddevicepicker 的频道之外。由于我们只是实例化了它,我们可以使用我们的 devicepicker 对象。我有一个只发送一次的设备,主要过程比构建 devicepicker 更快,我的列表从未发送到 devicepicker。所以我用 aPromise()等待ipcRenderer.sendTo()直到设备选择器准备好使用。

7)要在我们的 devicepicker GUI 上接收设备列表,我们需要监听bluetoothDeviceDiscoverListwith ipcRenderer.on()(devicepicker.js)。我现在将 devicelist 插入到<option>了 devicepicker 中,当然你可以使用其他元素(devicepicker.html)。请注意:实现一个将发送列表与当前列表进行比较的查询。否则,您将获得多个设备,并且您的选择会变得冗长。我仍然需要这样做,它还没有完成:-)

8)要选择一个navigator.bluetooth.requestDevice()(renderer.js)被解析的设备,我们需要将BluetoothDevice.deviceId我们选择的设备发送回主进程,在主进程中我们用deviceId作为回调参数(main.js)回调“callback”。

9)现在我们可以使用ipcRenderer.sendTo()将选择BluetoothDevice.deviceId的内容发送到主进程(devicepicker.js)。

10)在我们的电子应用程序的主进程(main.js)中,我们用接收到的来监听频道channelForSelectingDeviceipcMain.on()回调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();
}}
于 2020-01-28T00:36:34.570 回答
2

在你的 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 版本。

于 2020-01-03T03:58:31.627 回答
0

这是一个代码示例,它将只返回第一个设备,而不必实现设备选择器:

  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

于 2020-01-20T11:00:18.037 回答