import { WebBLEDevice } from "./webbleabstraction"; import { BoostMoveHub } from "./hubs/boostmovehub"; import { ControlPlusHub } from "./hubs/controlplushub"; import { DuploTrainBase } from "./hubs/duplotrainbase"; import { Hub } from "./hubs/hub"; import { PUPHub } from "./hubs/puphub"; import { PUPRemote } from "./hubs/pupremote"; import { WeDo2SmartHub } from "./hubs/wedo2smarthub"; import * as Consts from "./consts"; import { EventEmitter } from "events"; import Debug = require("debug"); import { IBLEAbstraction } from "./interfaces"; const debug = Debug("poweredup"); /** * @class PoweredUP * @extends EventEmitter */ export class PoweredUP extends EventEmitter { private _connectedHubs: {[uuid: string]: Hub} = {}; constructor () { super(); this._discoveryEventHandler = this._discoveryEventHandler.bind(this); } /** * Begin scanning for Powered UP Hub devices. * @method PoweredUP#scan */ public async scan () { try { const device = await navigator.bluetooth.requestDevice({ filters: [ { services: [ Consts.BLEService.WEDO2_SMART_HUB ] }, { services: [ Consts.BLEService.LPF2_HUB ] } ], optionalServices: [ Consts.BLEService.WEDO2_SMART_HUB_2, "battery_service", "device_information" ] }); // @ts-ignore const server = await device.gatt.connect(); this._discoveryEventHandler.call(this, server); return true; } catch (err) { return false; } } /** * Retrieve a list of Powered UP Hubs. * @method PoweredUP#getHubs * @returns {Hub[]} */ public getHubs () { return Object.values(this._connectedHubs); } /** * Retrieve a Powered UP Hub by UUID. * @method PoweredUP#getHubByUUID * @param {string} uuid * @returns {Hub | null} */ public getHubByUUID (uuid: string) { return this._connectedHubs[uuid]; } /** * Retrieve a Powered UP Hub by primary MAC address. * @method PoweredUP#getHubByPrimaryMACAddress * @param {string} address * @returns {Hub} */ public getHubByPrimaryMACAddress (address: string) { return Object.values(this._connectedHubs).filter((hub) => hub.primaryMACAddress === address)[0]; } /** * Retrieve a list of Powered UP Hub by name. * @method PoweredUP#getHubsByName * @param {string} name * @returns {Hub[]} */ public getHubsByName (name: string) { return Object.values(this._connectedHubs).filter((hub) => hub.name === name); } /** * Retrieve a list of Powered UP Hub by type. * @method PoweredUP#getHubsByType * @param {string} name * @returns {Hub[]} */ public getHubsByType (hubType: number) { return Object.values(this._connectedHubs).filter((hub) => hub.type === hubType); } private _determineLPF2HubType (device: IBLEAbstraction): Promise { return new Promise((resolve, reject) => { let buf: Buffer = Buffer.alloc(0); device.subscribeToCharacteristic(Consts.BLECharacteristic.LPF2_ALL, (data: Buffer) => { buf = Buffer.concat([buf, data]); const len = buf[0]; if (len >= buf.length) { const message = buf.slice(0, len); buf = buf.slice(len); if (message[2] === 0x01 && message[3] === 0x0b) { process.nextTick(() => { switch (message[5]) { case Consts.BLEManufacturerData.POWERED_UP_REMOTE_ID: resolve(Consts.HubType.POWERED_UP_REMOTE); break; case Consts.BLEManufacturerData.BOOST_MOVE_HUB_ID: resolve(Consts.HubType.BOOST_MOVE_HUB); break; case Consts.BLEManufacturerData.POWERED_UP_HUB_ID: resolve(Consts.HubType.POWERED_UP_HUB); break; case Consts.BLEManufacturerData.DUPLO_TRAIN_HUB_ID: resolve(Consts.HubType.DUPLO_TRAIN_HUB); break; case Consts.BLEManufacturerData.CONTROL_PLUS_LARGE_HUB: resolve(Consts.HubType.CONTROL_PLUS_HUB); break; } }); } else { device.addToCharacteristicMailbox(Consts.BLECharacteristic.LPF2_ALL, message); } } }); device.writeToCharacteristic(Consts.BLECharacteristic.LPF2_ALL, Buffer.from([0x05, 0x00, 0x01, 0x0b, 0x05])); }); } private async _discoveryEventHandler (server: BluetoothRemoteGATTServer) { const device = new WebBLEDevice(server); let hub: Hub; let hubType = Consts.HubType.UNKNOWN; let isLPF2Hub = false; try { await device.discoverCharacteristicsForService(Consts.BLEService.WEDO2_SMART_HUB); hubType = Consts.HubType.WEDO2_SMART_HUB; // tslint:disable-next-line } catch (error) {} try { if (hubType !== Consts.HubType.WEDO2_SMART_HUB) { await device.discoverCharacteristicsForService(Consts.BLEService.LPF2_HUB); isLPF2Hub = true; } // tslint:disable-next-line } catch (error) {} if (isLPF2Hub) { hubType = await this._determineLPF2HubType(device); } switch (hubType) { case Consts.HubType.WEDO2_SMART_HUB: hub = new WeDo2SmartHub(device); break; case Consts.HubType.BOOST_MOVE_HUB: hub = new BoostMoveHub(device); break; case Consts.HubType.POWERED_UP_HUB: hub = new PUPHub(device); break; case Consts.HubType.POWERED_UP_REMOTE: hub = new PUPRemote(device); break; case Consts.HubType.DUPLO_TRAIN_HUB: hub = new DuploTrainBase(device); break; case Consts.HubType.CONTROL_PLUS_HUB: hub = new ControlPlusHub(device); break; default: return; } device.on("discoverComplete", () => { hub.on("connect", () => { debug(`Hub ${hub.uuid} connected`); this._connectedHubs[hub.uuid] = hub; }); hub.on("disconnect", () => { debug(`Hub ${hub.uuid} disconnected`); delete this._connectedHubs[hub.uuid]; }); debug(`Hub ${hub.uuid} discovered`); /** * Emits when a Powered UP Hub device is found. * @event PoweredUP#discover * @param {WeDo2SmartHub | BoostMoveHub | ControlPlusHub | PUPHub | PUPRemote | DuploTrainBase} hub */ this.emit("discover", hub); }); } }