【问题标题】:Electron Web Bluetooth API requestDevice() ErrorElectron Web 蓝牙 API requestDevice() 错误
【发布时间】:2022-01-31 06:45:57
【问题描述】:

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

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

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

【问题讨论】:

    标签: electron bluetooth-lowenergy web-bluetooth


    【解决方案1】:

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

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

    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 关闭
    • 查看应用程序中的数据

    教程流程的代码和sn-p的代码参考:

    电子应用: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() 时,事件 get 被触发并且设备列表在主进程中。使用console.log(deviceList),它在外壳中可见。要处理它,您需要将其发送到渲染器进程(您的应用程序窗口)。

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

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

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

    10) 在我们的电子应用程序的主进程(main.js)中,我们用ipcMain.on() 监听频道channelForSelectingDevice,并用收到的BluetoothDevice.deviceId 回调。设备发现停止,navigator.bluetooth.requestDevice() 得到解决,我们在应用程序 (renderer.js) 中接收来自设备的数据。要取消对设备的发现,请在另一个频道 channelForTerminationSignal 中使用 ipcMain.on() 侦听,这只是向主进程 (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();
    }}
    

    【讨论】:

      【解决方案2】:

      在你的 main.js 中添加这段代码 sn -p

      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 版本。

      【讨论】:

        【解决方案3】:

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

          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

        【讨论】:

        • 谢谢你,这对我帮助很大! :-) 正如您在我发布的解决方案中看到的那样,我使用此事件来构建设备选择器。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2021-12-20
        • 2020-10-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-10-25
        • 2011-06-26
        相关资源
        最近更新 更多