Search code examples
node.jsbluetooth-lowenergyibeaconanki

Cannot connect my Windows laptop with BLE adapter to BLE device


I am trying to set up a demo based on Nodejs on my Windows laptop with an extra BLE adapter to connect my laptop to another BLE device (Anki Overdrive). I have seen this demo on the internet and was curious, if I could set it up, too. I have followed all the instructions, but failed, because the original demo was made with a MacBook which is using a different kind of build-in BLE adapter. I tried it with a MacBook, too, and it worked more than fine, but I'd like to set it up with a Windows device. I attached a part of the Nodejs code which should return the peripheral ID of the BLE device:

//UUID for Anki Overdrive Car be15beef6186407e83810bd89c4d8df4

var noble = require('noble');

noble.on('stateChange', function(state) {
  if (state === 'poweredOn') {
    noble.startScanning();

    setTimeout(function() {
       noble.stopScanning();
       process.exit(0);
     }, 2000);
  } else {
    noble.stopScanning();
  }
});

noble.on('discover', function(peripheral) {
  var serviceUuids = JSON.stringify(peripheral.advertisement.serviceUuids);
  if(serviceUuids.indexOf("be15beef6186407e83810bd89c4d8df4") > -1) {
    console.log('Car discovered. ID: ' + peripheral.id); 
  }
});

What I figured out so far is that the built-in BLE from the MacBook sends back the peripheral ID. In comparison, the Windows BLE Adapter only returns the MAC address from the other BLE device. Can anyone help me? I have really no idea what I could do to solve the problem.

For further understanding, I have provided the link to the original demo which was set up with a MacBook. https://github.com/IBM-Bluemix/node-mqtt-for-anki-overdrive.

Many thanks in advance.


Solution

  • Working with the Anki Overdrive cars always is a hassle.

    The following snippets show the relevant code regarding you question.

    Disclaimer: I created a WIP Anki Overdrive SDK [1] that tries to make life easier (The snippets are copied from there)

    1. Scan for devices using noble (scanner.js)
    module.exports = class Scanner {
        constructor() {
            this.peripherals = []
            this.state = undefined
        }
    
    /**
     * Set up noble listeners. Mandatory before scan.
     */
    async setUpNoble () {
        try {
            await noble.on('discover', (peripheral) => {
                const isAnkiDevice = function(peripheral) {
                    const serviceUuids = JSON.stringify(peripheral.advertisement.serviceUuids)
                    return (serviceUuids.indexOf("be15beef6186407e83810bd89c4d8df4") > -1)
                }
    
                if(isAnkiDevice(peripheral)) {
                    this.peripherals.push(peripheral)
                }
            })
            await noble.on('stateChange', (status) => {
                this.state = status
            })
        } catch (err) {
            throw new Error(err)
        }
    }
    
    /**
     * Scans for devices and returns peripherals. Scans for 20 seconds before stopping.
     */
    async scan() {
        try {
            let that = this
            await waitUntil(() => {
                return (that.state === 'poweredOn')
            }, 20000)
            console.log('Starting to scan...')
            return await waitUntil(() => {
                noble.stopScanning()
                if(that.peripherals.length <= 0) {
                    console.log('Scanning...')
                    noble.startScanning()
                } else {
                    console.log('Found ' + that.peripherals.length + ' devices.')
                    return that.peripherals
                }
            }, 20000, 4000)
        } catch (err) {
            throw new Error(err)
        }
    }
    }
    
    1. Trigger scan and create device object (anki.js)
    async scanDevices() {
        try {
            const that = this
            const scanner = new Scanner()
            scanner.setUpNoble()
    
            const peripherals = await scanner.scan()  
            const devices = peripherals.map((peripheral) => {
                return new Device(peripheral)
            })
    
            return devices
        } catch (err) {
            throw new Error(err)
        }
    
    }
    
    1. Set id and serviceUUids from peripheral in constructor of Device object (device.js)
    module.exports = class AnkiDevice {
        constructor (peripheral) {
            this.peripheral = peripheral
            this.id = peripheral.uuid
            this.serviceUuids = JSON.stringify(peripheral.advertisement.serviceUuids)
    ....
    
    1. Connect to the car (device.js)
    connect() {
        try {
            const that = this
            console.log('Connecting with ' + this.id)
            const getService = function(services){
                if (os.platform() === 'win32' || os.platform() === 'linux') {
                    return services[2]
                } else {
                    return service[0] // macOS
                }
            }
    
            const setCharacteristics = function(characteristics) {
                for(let i in characteristics) {
                    const characteristic = characteristics[i]
                    if (characteristic.uuid == 'be15bee06186407e83810bd89c4d8df4') {
                        that.readCharacteristic = characteristic
                    }
    
                    if (characteristic.uuid == 'be15bee16186407e83810bd89c4d8df4') {
                        that.writeCharacteristic = characteristic
                    } 
                }                
            }
    
            const onConnect = async function() {
                const services = await that.peripheral.discoverServices([])
                const service = await getService(services)
                const characteristics = await service.discoverCharacteristics([])
                await setCharacteristics(characteristics)
                // Listen to own disconnect
                that.peripheral.once('disconnect', () => {
                    mediator.private.emit('deviceDisconnected', this)
                })
                mediator.private.emit('deviceConnected', that)
            }
    
            this.peripheral.once('connect', onConnect)
            this.peripheral.connect()
        } catch(err) {
            throw new Error(err)
        }
    }
    
    1. THEN: Activate SDK Mode (device.js)
    activateSDKMode() {
        console.log('Activating SDKMode for ' + this.id)
        const that = this
        if(!this.isConnected) {
            return new Error('Car is not connected yet.')
        }
        const message = coder.encodeSDKActivation()
        this._writeMessage(message)
        .then(() => {
            mediator.private.emit('SDKModeOn', that)
        })
    }
    
    1. THEN: Turn on Logging (device.js)
    turnOnLogging() {
        console.log('Turning logging on for ' + this.id)
        let that = this
        if(!that.isConnected) {
            return new Error('Car is not connected yet.')
        }
    
        that.readCharacteristic.notify(true)
        that.readCharacteristic.on('read', (data) => {
            that._onMessage(data)
        })
        mediator.private.emit('loggingOn', that)
    }
    

    [1] https://github.com/steinroe/anki-overdrive-sdk