diff --git a/ABOUT.md b/ABOUT.md index 5f469c0..5da516e 100644 --- a/ABOUT.md +++ b/ABOUT.md @@ -24,7 +24,11 @@ While most LPF2 components and Hubs are compatible with each other, there are ex | Boost Color and Distance Sensor | Sensor | *Partial (1)* | Yes | Yes | 17101 | | Boost Interactive Motor | Motor/Sensor | *Partial (2)* | Yes | Yes | 17101 | | Powered Up Train Motor | Motor | No | Yes | Yes | 60197
60198 | +<<<<<<< HEAD | Powered Up LED Lights | Lights | Unknown | Unknown | Unknown | 88005 | +======= +| Powered Up LED Lights | Light | Unknown | Unknown | Unknown | 88005 | +>>>>>>> 4988da178b8c48d2c76a5f026c750cf727f5f5f5 (1) Only color mode is supported on the WeDo 2.0 Smart Hub at this point. diff --git a/README.md b/README.md index 9224b56..45af87d 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ While most LPF2 components and Hubs are compatible with each other, there are ex | Boost Color and Distance Sensor | Sensor | *Partial (1)* | Yes | Yes | 17101 | | Boost Interactive Motor | Motor/Sensor | *Partial (2)* | Yes | Yes | 17101 | | Powered Up Train Motor | Motor | No | Yes | Yes | 60197
60198 | -| Powered Up LED Lights | Lights | Unknown | Unknown | Unknown | 88005 | +| Powered Up LED Lights | Light | Unknown | Unknown | Unknown | 88005 | (1) Only color mode is supported on the WeDo 2.0 Smart Hub at this point. diff --git a/consts.ts b/consts.ts index 0bdf702..601ed75 100644 --- a/consts.ts +++ b/consts.ts @@ -9,13 +9,15 @@ export enum Hubs { export enum Devices { UNKNOWN = 0, BASIC_MOTOR = 1, + TRAIN_MOTOR = 2, BOOST_LED = 22, WEDO2_TILT = 34, WEDO2_DISTANCE = 35, BOOST_DISTANCE = 37, BOOST_INTERACTIVE_MOTOR = 38, BOOST_MOVE_HUB_MOTOR = 39, - BOOST_TILT = 40 + BOOST_TILT = 40, + REMOTE_BUTTON = 55 } @@ -35,14 +37,16 @@ export enum Colors { export enum ButtonStates { PRESSED = 0, - RELEASED = 1 + RELEASED = 1, + UP = 2, + DOWN = 3, + STOP = 4 } -export enum BLENames { - WEDO2_SMART_HUB_NAME = "LPF2 Smart Hub 2 I/O", - BOOST_MOVE_HUB_NAME = "LEGO Move Hub", - POWERED_UP_HUB_NAME = "HUB NO.4", - POWERED_UP_REMOTE_NAME = "Handset" +export enum BLEManufacturerData { + BOOST_MOVE_HUB_ID = 64, + POWERED_UP_HUB_ID = 65, + POWERED_UP_REMOTE_ID = 66 } export enum BLEServices { diff --git a/examples/vernie_remote.js b/examples/vernie_remote.js new file mode 100644 index 0000000..ed36b2a --- /dev/null +++ b/examples/vernie_remote.js @@ -0,0 +1,67 @@ +const LPF2 = require(".."); + +const lpf2 = new LPF2.LPF2(); +lpf2.scan(); // Start scanning for Vernie + +console.log("Looking for Vernie and Remote..."); + +let vernie = null; +let remote = null; + +lpf2.on("discover", async (hub) => { // Wait to discover Vernie and Remote + + if (hub.type === LPF2.Consts.Hubs.BOOST_MOVE_HUB) { + vernie = hub; + await vernie.connect(); + console.log("Connected to Vernie!"); + + + } else if (hub.type === LPF2.Consts.Hubs.POWERED_UP_REMOTE) { + remote = hub; + + remote.on("button", async (button, state) => { + if (vernie) { + switch (state) { + case LPF2.Consts.ButtonStates.UP: + { + vernie.setMotorSpeed(button === "LEFT" ? "A" : "B", 50); + break; + } + case LPF2.Consts.ButtonStates.DOWN: + { + vernie.setMotorSpeed(button === "LEFT" ? "A" : "B", -50); + break; + } + case LPF2.Consts.ButtonStates.RELEASED: + { + if (button !== "GREEN") { + vernie.setMotorSpeed(button === "LEFT" ? "A" : "B", 0); + } + break; + } + case LPF2.Consts.ButtonStates.STOP: + { + await vernie.setMotorAngle("D", 35, button === "LEFT" ? -20 : 20); + break; + } + case LPF2.Consts.ButtonStates.PRESSED: + { + if (button === "GREEN") { + await vernie.setMotorAngle("D", 80, 20); + await vernie.setMotorAngle("D", 80, -20); + } + break; + } + } + } + }) + + await remote.connect(); + console.log("Connected to Powered Up Remote!"); + } + + if (vernie && remote) { + console.log("You're now ready to go!"); + } + +}); \ No newline at end of file diff --git a/hub.ts b/hub.ts index 6ecee48..3d92d2e 100644 --- a/hub.ts +++ b/hub.ts @@ -276,6 +276,8 @@ export class Hub extends EventEmitter { switch (type) { case Consts.Devices.BASIC_MOTOR: return 0x02; + case Consts.Devices.TRAIN_MOTOR: + return 0x02; case Consts.Devices.BOOST_INTERACTIVE_MOTOR: return 0x02; case Consts.Devices.BOOST_MOVE_HUB_MOTOR: @@ -284,6 +286,8 @@ export class Hub extends EventEmitter { return (this.type === Consts.Hubs.WEDO2_SMART_HUB ? 0x00 : 0x08); case Consts.Devices.BOOST_TILT: return 0x04; + case Consts.Devices.REMOTE_BUTTON: + return 0x00; default: return 0x00; } diff --git a/lpf2.ts b/lpf2.ts index cad88c4..8493bb4 100644 --- a/lpf2.ts +++ b/lpf2.ts @@ -64,8 +64,6 @@ export class LPF2 extends EventEmitter { return; } - console.log(peripheral); - peripheral.removeAllListeners(); noble.stopScanning(); noble.startScanning(); diff --git a/lpf2hub.ts b/lpf2hub.ts index ba29675..7f7277c 100644 --- a/lpf2hub.ts +++ b/lpf2hub.ts @@ -25,24 +25,31 @@ export class LPF2Hub extends Hub { private _lastTiltX: number = 0; private _lastTiltY: number = 0; + private _incomingData: Buffer = Buffer.alloc(0); + private _outgoingData: Buffer = Buffer.alloc(0); + constructor (peripheral: Peripheral, autoSubscribe: boolean = true) { super(peripheral, autoSubscribe); - switch (peripheral.advertisement.localName) { - case Consts.BLENames.POWERED_UP_HUB_NAME: + switch (peripheral.advertisement.manufacturerData[3]) { + case Consts.BLEManufacturerData.POWERED_UP_HUB_ID: { this.type = Consts.Hubs.POWERED_UP_HUB; this._ports = { - "A": new Port("A", 55), - "B": new Port("B", 56), + "A": new Port("A", 0), + "B": new Port("B", 1), "AB": new Port("AB", 57) }; debug("Discovered Powered Up Hub"); break; } - case Consts.BLENames.POWERED_UP_REMOTE_NAME: + case Consts.BLEManufacturerData.POWERED_UP_REMOTE_ID: { this.type = Consts.Hubs.POWERED_UP_REMOTE; + this._ports = { + "LEFT": new Port("LEFT", 0), + "RIGHT": new Port("RIGHT", 1) + }; debug("Discovered Powered Up Remote"); break; } @@ -66,11 +73,11 @@ export class LPF2Hub extends Hub { public connect () { return new Promise(async (resolve, reject) => { - debug("Connecting to Boost Move Hub"); + debug("Connecting to Hub"); await super.connect(); const characteristic = this._characteristics[Consts.BLECharacteristics.BOOST_ALL]; this._subscribeToCharacteristic(characteristic, this._parseMessage.bind(this)); - characteristic.write(Buffer.from([0x05, 0x00, 0x01, 0x02, 0x02]), false); + this._writeMessage(Consts.BLECharacteristics.BOOST_ALL, Buffer.from([0x05, 0x00, 0x01, 0x02, 0x02])); debug("Connect completed"); return resolve(); }); @@ -85,32 +92,18 @@ export class LPF2Hub extends Hub { */ public setLEDColor (color: number | boolean) { return new Promise((resolve, reject) => { - const characteristic = this._characteristics[Consts.BLECharacteristics.BOOST_ALL]; - if (characteristic) { - let data = Buffer.from([0x05, 0x00, 0x01, 0x02, 0x02]); - characteristic.write(data, false); - if (color === false) { - color = 0; - } - data = Buffer.from([0x08, 0x00, 0x81, 0x32, 0x11, 0x51, 0x00, color]); - characteristic.write(data, false); + let data = Buffer.from([0x05, 0x00, 0x01, 0x02, 0x02]); + this._writeMessage(Consts.BLECharacteristics.BOOST_ALL, data); + if (color === false) { + color = 0; } + data = Buffer.from([0x08, 0x00, 0x81, 0x32, 0x11, 0x51, 0x00, color]); + this._writeMessage(Consts.BLECharacteristics.BOOST_ALL, data); return resolve(); }); } - // setLEDRGB (red, green, blue) { - // const characteristic = this._characteristics[Consts.BLE.Characteristics.Boost.ALL]; - // if (characteristic) { - // let data = Buffer.from([0x05, 0x00, 0x01, 0x02, 0x03]); - // characteristic.write(data); - // data = Buffer.from([0x0a, 0x00, 0x81, 0x32, 0x11, 0x51, 0x00, red, green, blue]); - // characteristic.write(data); - // } - // } - - /** * Set the motor speed on a given port. * @method LPF2Hub#setMotorSpeed @@ -121,22 +114,23 @@ export class LPF2Hub extends Hub { */ public setMotorSpeed (port: string, speed: number, time: number) { return new Promise((resolve, reject) => { - const characteristic = this._characteristics[Consts.BLECharacteristics.BOOST_ALL]; - if (characteristic) { - const portObj = this._ports[port]; - if (time) { - portObj.busy = true; - const data = Buffer.from([0x0c, 0x00, 0x81, portObj.value, 0x11, 0x09, 0x00, 0x00, this._mapSpeed(speed), 0x64, 0x7f, 0x03]); - data.writeUInt16LE(time > 65535 ? 65535 : time, 6); - characteristic.write(data, false); - portObj.finished = () => { - return resolve(); - }; - } else { - const data = Buffer.from([0x0a, 0x00, 0x81, portObj.value, 0x11, 0x01, this._mapSpeed(speed), 0x64, 0x7f, 0x03]); - characteristic.write(data, false); + const portObj = this._ports[port]; + if (portObj.type === Consts.Devices.TRAIN_MOTOR) { + const data = Buffer.from([0x08, 0x00, 0x81, portObj.value, 0x11, 0x51, 0x00, this._mapSpeed(speed)]); + this._writeMessage(Consts.BLECharacteristics.BOOST_ALL, data); + return resolve(); + } else if (time) { + portObj.busy = true; + const data = Buffer.from([0x0c, 0x00, 0x81, portObj.value, 0x11, 0x09, 0x00, 0x00, this._mapSpeed(speed), 0x64, 0x7f, 0x03]); + data.writeUInt16LE(time > 65535 ? 65535 : time, 6); + this._writeMessage(Consts.BLECharacteristics.BOOST_ALL, data); + portObj.finished = () => { return resolve(); - } + }; + } else { + const data = Buffer.from([0x0a, 0x00, 0x81, portObj.value, 0x11, 0x01, this._mapSpeed(speed), 0x64, 0x7f, 0x03]); + this._writeMessage(Consts.BLECharacteristics.BOOST_ALL, data); + return resolve(); } }); } @@ -152,61 +146,80 @@ export class LPF2Hub extends Hub { */ public setMotorAngle (port: string, angle: number, speed: number = 100) { return new Promise((resolve, reject) => { - const characteristic = this._characteristics[Consts.BLECharacteristics.BOOST_ALL]; - if (characteristic) { - const portObj = this._ports[port]; - portObj.busy = true; - const data = Buffer.from([0x0e, 0x00, 0x81, portObj.value, 0x11, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x7f, 0x03]); - data.writeUInt32LE(angle, 6); - data.writeUInt8(this._mapSpeed(speed), 10); - characteristic.write(data, false); - portObj.finished = () => { - return resolve(); - }; - } + const portObj = this._ports[port]; + portObj.busy = true; + const data = Buffer.from([0x0e, 0x00, 0x81, portObj.value, 0x11, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x7f, 0x03]); + data.writeUInt32LE(angle, 6); + data.writeUInt8(this._mapSpeed(speed), 10); + this._writeMessage(Consts.BLECharacteristics.BOOST_ALL, data); + portObj.finished = () => { + return resolve(); + }; }); } protected _activatePortDevice (port: number, type: number, mode: number, format: number, callback: () => void) { - const characteristic = this._characteristics[Consts.BLECharacteristics.BOOST_ALL]; - if (characteristic) { - characteristic.write(Buffer.from([0x0a, 0x00, 0x41, port, mode, 0x01, 0x00, 0x00, 0x00, 0x01]), false, callback); - } + this._writeMessage(Consts.BLECharacteristics.BOOST_ALL, Buffer.from([0x0a, 0x00, 0x41, port, mode, 0x01, 0x00, 0x00, 0x00, 0x01]), callback); } protected _deactivatePortDevice (port: number, type: number, mode: number, format: number, callback: () => void) { - const characteristic = this._characteristics[Consts.BLECharacteristics.BOOST_ALL]; + this._writeMessage(Consts.BLECharacteristics.BOOST_ALL, Buffer.from([0x0a, 0x00, 0x41, port, mode, 0x01, 0x00, 0x00, 0x00, 0x00]), callback); + } + + + private _writeMessage (uuid: string, message: Buffer, callback?: () => void) { + const characteristic = this._characteristics[uuid]; if (characteristic) { - characteristic.write(Buffer.from([0x0a, 0x00, 0x41, port, mode, 0x01, 0x00, 0x00, 0x00, 0x00]), false, callback); + characteristic.write(message, false, callback); } } - private _parseMessage (data: Buffer) { + private _parseMessage (data?: Buffer) { - switch (data[2]) { - case 0x01: - { - this._parseDeviceInfo(data); - break; + if (data) { + this._incomingData = Buffer.concat([this._incomingData, data]); + } + + if (this._incomingData.length <= 0) { + return; + } + + const len = this._incomingData[0]; + if (len >= this._incomingData.length) { + + const message = this._incomingData.slice(0, len); + this._incomingData = this._incomingData.slice(len); + + switch (message[2]) { + case 0x01: + { + this._parseDeviceInfo(message); + break; + } + case 0x04: + { + this._parsePortMessage(message); + break; + } + case 0x45: + { + this._parseSensorMessage(message); + break; + } + case 0x82: + { + this._parsePortAction(message); + break; + } } - case 0x04: - { - this._parsePortMessage(data); - break; - } - case 0x45: - { - this._parseSensorMessage(data); - break; - } - case 0x82: - { - this._parsePortAction(data); - break; + + if (this._incomingData.length > 0) { + this._parseMessage(); } + } } @@ -341,6 +354,12 @@ export class LPF2Hub extends Hub { this.emit("rotate", port.id, rotation); break; } + case Consts.Devices.BOOST_MOVE_HUB_MOTOR: + { + const rotation = data.readInt32LE(2); + this.emit("rotate", port.id, rotation); + break; + } case Consts.Devices.BOOST_TILT: { const tiltX = data[4] > 160 ? data[4] - 255 : data[4]; @@ -348,6 +367,32 @@ export class LPF2Hub extends Hub { this.emit("tilt", port.id, tiltX, tiltY); break; } + case Consts.Devices.REMOTE_BUTTON: + { + switch (data[4]) { + case 0x01: + { + this.emit("button", port.id, Consts.ButtonStates.UP); + break; + } + case 0xff: + { + this.emit("button", port.id, Consts.ButtonStates.DOWN); + break; + } + case 0x7f: + { + this.emit("button", port.id, Consts.ButtonStates.STOP); + break; + } + case 0x00: + { + this.emit("button", port.id, Consts.ButtonStates.RELEASED); + break; + } + } + break; + } } } diff --git a/wedo2hub.ts b/wedo2hub.ts index f74751e..581d414 100644 --- a/wedo2hub.ts +++ b/wedo2hub.ts @@ -58,18 +58,14 @@ export class WeDo2Hub extends Hub { */ public setLEDColor (color: number | boolean) { return new Promise((resolve, reject) => { - const motorCharacteristic = this._characteristics[Consts.BLECharacteristics.WEDO2_MOTOR_VALUE_WRITE]; - const portCharacteristic = this._characteristics[Consts.BLECharacteristics.WEDO2_PORT_TYPE_WRITE]; - if (motorCharacteristic && portCharacteristic) { - let data = Buffer.from([0x06, 0x17, 0x01, 0x01]); - portCharacteristic.write(data, false); - if (color === false) { - color = 0; - } - data = Buffer.from([0x06, 0x04, 0x01, color]); - motorCharacteristic.write(data, false); - return resolve(); + let data = Buffer.from([0x06, 0x17, 0x01, 0x01]); + this._writeMessage(Consts.BLECharacteristics.WEDO2_PORT_TYPE_WRITE, data); + if (color === false) { + color = 0; } + data = Buffer.from([0x06, 0x04, 0x01, color]); + this._writeMessage(Consts.BLECharacteristics.WEDO2_MOTOR_VALUE_WRITE, data); + return resolve(); }); } @@ -84,15 +80,11 @@ export class WeDo2Hub extends Hub { */ public setLEDRGB (red: number, green: number, blue: number) { return new Promise((resolve, reject) => { - const motorCharacteristic = this._characteristics[Consts.BLECharacteristics.WEDO2_MOTOR_VALUE_WRITE]; - const portCharacteristic = this._characteristics[Consts.BLECharacteristics.WEDO2_PORT_TYPE_WRITE]; - if (motorCharacteristic && portCharacteristic) { - const data1 = Buffer.from([0x01, 0x02, 0x06, 0x17, 0x01, 0x02]); - portCharacteristic.write(data1, false); - const data2 = Buffer.from([0x06, 0x04, 0x03, red, green, blue]); - motorCharacteristic.write(data2, false); - return resolve(); - } + let data = Buffer.from([0x01, 0x02, 0x06, 0x17, 0x01, 0x02]); + this._writeMessage(Consts.BLECharacteristics.WEDO2_MOTOR_VALUE_WRITE, data); + data = Buffer.from([0x06, 0x04, 0x03, red, green, blue]); + this._writeMessage(Consts.BLECharacteristics.WEDO2_PORT_TYPE_WRITE, data); + return resolve(); }); } @@ -106,27 +98,26 @@ export class WeDo2Hub extends Hub { */ public setMotorSpeed (port: string, speed: number) { return new Promise((resolve, reject) => { - const characteristic = this._characteristics[Consts.BLECharacteristics.WEDO2_MOTOR_VALUE_WRITE]; - if (characteristic) { - characteristic.write(Buffer.from([this._ports[port].value, 0x01, 0x02, this._mapSpeed(speed)]), false); - return resolve(); - } + this._writeMessage(Consts.BLECharacteristics.WEDO2_MOTOR_VALUE_WRITE, Buffer.from([this._ports[port].value, 0x01, 0x02, this._mapSpeed(speed)])); + return resolve(); }); } protected _activatePortDevice (port: number, type: number, mode: number, format: number, callback: () => void) { - const characteristic = this._characteristics[Consts.BLECharacteristics.WEDO2_PORT_TYPE_WRITE]; - if (characteristic) { - characteristic.write(Buffer.from([0x01, 0x02, port, type, mode, 0x01, 0x00, 0x00, 0x00, format, 0x01]), false, callback); - } + this._writeMessage(Consts.BLECharacteristics.WEDO2_PORT_TYPE_WRITE, Buffer.from([0x01, 0x02, port, type, mode, 0x01, 0x00, 0x00, 0x00, format, 0x01]), callback); } protected _deactivatePortDevice (port: number, type: number, mode: number, format: number, callback: () => void) { - const characteristic = this._characteristics[Consts.BLECharacteristics.WEDO2_PORT_TYPE_WRITE]; + this._writeMessage(Consts.BLECharacteristics.WEDO2_PORT_TYPE_WRITE, Buffer.from([0x01, 0x02, port, type, mode, 0x01, 0x00, 0x00, 0x00, format, 0x00]), callback); + } + + + private _writeMessage (uuid: string, message: Buffer, callback?: () => void) { + const characteristic = this._characteristics[uuid]; if (characteristic) { - characteristic.write(Buffer.from([0x01, 0x02, port, type, mode, 0x01, 0x00, 0x00, 0x00, format, 0x00]), false, callback); + characteristic.write(message, false, callback); } }