diff --git a/examples/new_device_test.js b/examples/new_device_test.js new file mode 100644 index 0000000..3eb8cf8 --- /dev/null +++ b/examples/new_device_test.js @@ -0,0 +1,43 @@ +/* + * + * This demonstrates connecting multiple hubs to your laptop. Once connected, all the hubs LED lights will cycle through the same colors simultaneously. + * + */ + +const PoweredUP = require(".."); + +const poweredUP = new PoweredUP.PoweredUP(); +poweredUP.scan(); // Start scanning for hubs + +console.log("Looking for Hubs..."); + +poweredUP.on("discover", async (hub) => { // Wait to discover hubs + + await hub.connect(); // Connect to hub + console.log(`Connected to ${hub.name}!`); + + hub.on("attach", (device) => { + + if (device instanceof PoweredUP.ControlPlusLargeMotor) { + const motor = device; + motor.setSpeed(30); + } + + if (device instanceof PoweredUP.ColorDistanceSensor) { + const sensor = device; + sensor.on("distance", (distance) => { // Adding an event handler for distance automatically subscribes to distance notifications + console.log(`Distance ${distance}`); + }) + } + + device.on("detach", () => { + console.log(device.connected); + }) + + }); + + hub.on("disconnect", () => { + console.log("Hub disconnected"); + }) + +}); \ No newline at end of file diff --git a/src/basicmotor.ts b/src/basicmotor.ts index a9bd2cd..b0b3a93 100644 --- a/src/basicmotor.ts +++ b/src/basicmotor.ts @@ -3,6 +3,8 @@ import { Hub } from "./hub"; import * as Consts from "./consts"; +import { mapSpeed } from "./utils"; + export class BasicMotor extends Device { @@ -19,7 +21,7 @@ export class BasicMotor extends Device { */ public setSpeed (speed: number) { return new Promise((resolve) => { - const data = Buffer.from([0x81, this.portId, 0x11, 0x51, 0x00, speed]); + const data = Buffer.from([0x81, this.portId, 0x11, 0x51, 0x00, mapSpeed(speed)]); this.send(data); return resolve(); }); diff --git a/src/boostmovehub.ts b/src/boostmovehub.ts index 784e0ed..e363eb5 100644 --- a/src/boostmovehub.ts +++ b/src/boostmovehub.ts @@ -36,17 +36,14 @@ export class BoostMoveHub extends LPF2Hub { constructor (device: IBLEAbstraction, autoSubscribe: boolean = true) { super(device, autoSubscribe); - this.type = Consts.HubType.BOOST_MOVE_HUB; - this._ports = { - "A": new Port("A", 0), - "B": new Port("B", 1), - "C": new Port("C", 2), - "D": new Port("D", 3), - "TILT": new Port("TILT", 58) + this._type = Consts.HubType.BOOST_MOVE_HUB; + this._portNames = { + "A": 0, + "B": 1, + "C": 2, + "D": 3, + "TILT": 58 }; - this.on("attach", (port, type) => { - this._combinePorts(port, type); - }); debug("Discovered Boost Move Hub"); } @@ -61,253 +58,253 @@ export class BoostMoveHub extends LPF2Hub { } - /** - * Set the motor speed on a given port. - * @method BoostMoveHub#setMotorSpeed - * @param {string} port - * @param {number | Array.} speed For forward, a value between 1 - 100 should be set. For reverse, a value between -1 to -100. Stop is 0. If you are specifying port AB to control both motors, you can optionally supply a tuple of speeds. - * @param {number} [time] How long to activate the motor for (in milliseconds). Leave empty to turn the motor on indefinitely. - * @returns {Promise} Resolved upon successful completion of command. If time is specified, this is once the motor is finished. - */ - public setMotorSpeed (port: string, speed: number | [number, number], time?: number | boolean) { - const portObj = this._portLookup(port); - if (!this._virtualPorts[portObj.id] && speed instanceof Array) { - throw new Error(`Port ${portObj.id} can only accept a single speed`); - } - let cancelEventTimer = true; - if (typeof time === "boolean") { - if (time === true) { - cancelEventTimer = false; - } - time = undefined; - } - if (cancelEventTimer) { - portObj.cancelEventTimer(); - } - return new Promise((resolve, reject) => { - if (time && typeof time === "number") { + // /** + // * Set the motor speed on a given port. + // * @method BoostMoveHub#setMotorSpeed + // * @param {string} port + // * @param {number | Array.} speed For forward, a value between 1 - 100 should be set. For reverse, a value between -1 to -100. Stop is 0. If you are specifying port AB to control both motors, you can optionally supply a tuple of speeds. + // * @param {number} [time] How long to activate the motor for (in milliseconds). Leave empty to turn the motor on indefinitely. + // * @returns {Promise} Resolved upon successful completion of command. If time is specified, this is once the motor is finished. + // */ + // public setMotorSpeed (port: string, speed: number | [number, number], time?: number | boolean) { + // const portObj = this._portLookup(port); + // if (!this._virtualPorts[portObj.id] && speed instanceof Array) { + // throw new Error(`Port ${portObj.id} can only accept a single speed`); + // } + // let cancelEventTimer = true; + // if (typeof time === "boolean") { + // if (time === true) { + // cancelEventTimer = false; + // } + // time = undefined; + // } + // if (cancelEventTimer) { + // portObj.cancelEventTimer(); + // } + // return new Promise((resolve, reject) => { + // if (time && typeof time === "number") { - if ( - portObj.type === Consts.DeviceType.BOOST_TACHO_MOTOR || - portObj.type === Consts.DeviceType.BOOST_MOVE_HUB_MOTOR || - portObj.type === Consts.DeviceType.CONTROL_PLUS_LARGE_MOTOR || - portObj.type === Consts.DeviceType.CONTROL_PLUS_XLARGE_MOTOR - ) { - portObj.busy = true; - let data = null; - if (this._virtualPorts[portObj.id]) { - data = Buffer.from([0x81, portObj.value, 0x11, 0x0a, 0x00, 0x00, this._mapSpeed(speed instanceof Array ? speed[0] : speed), this._mapSpeed(speed instanceof Array ? speed[1] : speed), 0x64, 0x7f, 0x03]); - } else { - // @ts-ignore: The type of speed is properly checked at the start - data = Buffer.from([0x81, portObj.value, 0x11, 0x09, 0x00, 0x00, this._mapSpeed(speed), 0x64, 0x7f, 0x03]); - } - data.writeUInt16LE(time > 65535 ? 65535 : time, 4); - this.send(Consts.BLECharacteristic.LPF2_ALL, data); - portObj.finished = () => { - return resolve(); - }; - } else { - // @ts-ignore: The type of speed is properly checked at the start - const data = Buffer.from([0x81, portObj.value, 0x11, 0x51, 0x00, this._mapSpeed(speed)]); - this.send(Consts.BLECharacteristic.LPF2_ALL, data); - const timeout = global.setTimeout(() => { - const data = Buffer.from([0x81, portObj.value, 0x11, 0x51, 0x00, 0x00]); - this.send(Consts.BLECharacteristic.LPF2_ALL, data); - return resolve(); - // @ts-ignore: The type of time is properly checked at the start - }, time); - portObj.setEventTimer(timeout); - } + // if ( + // portObj.type === Consts.DeviceType.BOOST_TACHO_MOTOR || + // portObj.type === Consts.DeviceType.BOOST_MOVE_HUB_MOTOR || + // portObj.type === Consts.DeviceType.CONTROL_PLUS_LARGE_MOTOR || + // portObj.type === Consts.DeviceType.CONTROL_PLUS_XLARGE_MOTOR + // ) { + // portObj.busy = true; + // let data = null; + // if (this._virtualPorts[portObj.id]) { + // data = Buffer.from([0x81, portObj.value, 0x11, 0x0a, 0x00, 0x00, this._mapSpeed(speed instanceof Array ? speed[0] : speed), this._mapSpeed(speed instanceof Array ? speed[1] : speed), 0x64, 0x7f, 0x03]); + // } else { + // // @ts-ignore: The type of speed is properly checked at the start + // data = Buffer.from([0x81, portObj.value, 0x11, 0x09, 0x00, 0x00, this._mapSpeed(speed), 0x64, 0x7f, 0x03]); + // } + // data.writeUInt16LE(time > 65535 ? 65535 : time, 4); + // this.send(data, Consts.BLECharacteristic.LPF2_ALL); + // portObj.finished = () => { + // return resolve(); + // }; + // } else { + // // @ts-ignore: The type of speed is properly checked at the start + // const data = Buffer.from([0x81, portObj.value, 0x11, 0x51, 0x00, this._mapSpeed(speed)]); + // this.send(data, Consts.BLECharacteristic.LPF2_ALL); + // const timeout = global.setTimeout(() => { + // const data = Buffer.from([0x81, portObj.value, 0x11, 0x51, 0x00, 0x00]); + // this.send(data, Consts.BLECharacteristic.LPF2_ALL); + // return resolve(); + // // @ts-ignore: The type of time is properly checked at the start + // }, time); + // portObj.setEventTimer(timeout); + // } - } else { + // } else { - if ( - portObj.type === Consts.DeviceType.BOOST_TACHO_MOTOR || - portObj.type === Consts.DeviceType.BOOST_MOVE_HUB_MOTOR || - portObj.type === Consts.DeviceType.CONTROL_PLUS_LARGE_MOTOR || - portObj.type === Consts.DeviceType.CONTROL_PLUS_XLARGE_MOTOR - ) { - portObj.busy = true; - let data = null; - if (this._virtualPorts[portObj.id]) { - data = Buffer.from([0x81, portObj.value, 0x11, 0x02, this._mapSpeed(speed instanceof Array ? speed[0] : speed), this._mapSpeed(speed instanceof Array ? speed[1] : speed), 0x64, 0x7f, 0x03]); - } else { - // @ts-ignore: The type of speed is properly checked at the start - data = Buffer.from([0x81, portObj.value, 0x11, 0x01, this._mapSpeed(speed), 0x64, 0x7f, 0x03]); - } - this.send(Consts.BLECharacteristic.LPF2_ALL, data); - portObj.finished = () => { - return resolve(); - }; - } else { - // @ts-ignore: The type of speed is properly checked at the start - const data = Buffer.from([0x81, portObj.value, 0x11, 0x51, 0x00, this._mapSpeed(speed)]); - this.send(Consts.BLECharacteristic.LPF2_ALL, data); - } + // if ( + // portObj.type === Consts.DeviceType.BOOST_TACHO_MOTOR || + // portObj.type === Consts.DeviceType.BOOST_MOVE_HUB_MOTOR || + // portObj.type === Consts.DeviceType.CONTROL_PLUS_LARGE_MOTOR || + // portObj.type === Consts.DeviceType.CONTROL_PLUS_XLARGE_MOTOR + // ) { + // portObj.busy = true; + // let data = null; + // if (this._virtualPorts[portObj.id]) { + // data = Buffer.from([0x81, portObj.value, 0x11, 0x02, this._mapSpeed(speed instanceof Array ? speed[0] : speed), this._mapSpeed(speed instanceof Array ? speed[1] : speed), 0x64, 0x7f, 0x03]); + // } else { + // // @ts-ignore: The type of speed is properly checked at the start + // data = Buffer.from([0x81, portObj.value, 0x11, 0x01, this._mapSpeed(speed), 0x64, 0x7f, 0x03]); + // } + // this.send(data, Consts.BLECharacteristic.LPF2_ALL); + // portObj.finished = () => { + // return resolve(); + // }; + // } else { + // // @ts-ignore: The type of speed is properly checked at the start + // const data = Buffer.from([0x81, portObj.value, 0x11, 0x51, 0x00, this._mapSpeed(speed)]); + // this.send(data, Consts.BLECharacteristic.LPF2_ALL); + // } - } - }); - } + // } + // }); + // } - /** - * Ramp the motor speed on a given port. - * @method BoostMoveHub#rampMotorSpeed - * @param {string} port - * @param {number} fromSpeed For forward, a value between 1 - 100 should be set. For reverse, a value between -1 to -100. Stop is 0. - * @param {number} toSpeed For forward, a value between 1 - 100 should be set. For reverse, a value between -1 to -100. Stop is 0. - * @param {number} time How long the ramp should last (in milliseconds). - * @returns {Promise} Resolved upon successful completion of command. - */ - public rampMotorSpeed (port: string, fromSpeed: number, toSpeed: number, time: number) { - const portObj = this._portLookup(port); - portObj.cancelEventTimer(); - return new Promise((resolve, reject) => { - this._calculateRamp(fromSpeed, toSpeed, time, portObj) - .on("changeSpeed", (speed) => { - this.setMotorSpeed(port, speed, true); - }) - .on("finished", resolve); - }); - } + // /** + // * Ramp the motor speed on a given port. + // * @method BoostMoveHub#rampMotorSpeed + // * @param {string} port + // * @param {number} fromSpeed For forward, a value between 1 - 100 should be set. For reverse, a value between -1 to -100. Stop is 0. + // * @param {number} toSpeed For forward, a value between 1 - 100 should be set. For reverse, a value between -1 to -100. Stop is 0. + // * @param {number} time How long the ramp should last (in milliseconds). + // * @returns {Promise} Resolved upon successful completion of command. + // */ + // public rampMotorSpeed (port: string, fromSpeed: number, toSpeed: number, time: number) { + // const portObj = this._portLookup(port); + // portObj.cancelEventTimer(); + // return new Promise((resolve, reject) => { + // this._calculateRamp(fromSpeed, toSpeed, time, portObj) + // .on("changeSpeed", (speed) => { + // this.setMotorSpeed(port, speed, true); + // }) + // .on("finished", resolve); + // }); + // } - /** - * Rotate a motor by a given angle. - * @method BoostMoveHub#setMotorAngle - * @param {string} port - * @param {number} angle How much the motor should be rotated (in degrees). - * @param {number | Array.} [speed=100] For forward, a value between 1 - 100 should be set. For reverse, a value between -1 to -100. Stop is 0. If you are specifying port AB to control both motors, you can optionally supply a tuple of speeds. - * @returns {Promise} Resolved upon successful completion of command (ie. once the motor is finished). - */ - public setMotorAngle (port: string, angle: number, speed: number | [number, number] = 100) { - const portObj = this._portLookup(port); - if (!( - portObj.type === Consts.DeviceType.BOOST_TACHO_MOTOR || - portObj.type === Consts.DeviceType.BOOST_MOVE_HUB_MOTOR || - portObj.type === Consts.DeviceType.CONTROL_PLUS_LARGE_MOTOR || - portObj.type === Consts.DeviceType.CONTROL_PLUS_XLARGE_MOTOR - )) { - throw new Error("Angle rotation is only available when using a Boost Tacho Motor, Boost Move Hub Motor, Control+ Medium Motor, or Control+ Large Motor"); - } - if (!this._virtualPorts[portObj.id] && speed instanceof Array) { - throw new Error(`Port ${portObj.id} can only accept a single speed`); - } - portObj.cancelEventTimer(); - return new Promise((resolve, reject) => { - portObj.busy = true; - let data = null; - if (this._virtualPorts[portObj.id]) { - data = Buffer.from([0x81, portObj.value, 0x11, 0x0c, 0x00, 0x00, 0x00, 0x00, this._mapSpeed(speed instanceof Array ? speed[0] : speed), this._mapSpeed(speed instanceof Array ? speed[1] : speed), 0x64, 0x7f, 0x03]); - } else { - // @ts-ignore: The type of speed is properly checked at the start - data = Buffer.from([0x81, portObj.value, 0x11, 0x0b, 0x00, 0x00, 0x00, 0x00, this._mapSpeed(speed), 0x64, 0x7f, 0x03]); - } - data.writeUInt32LE(angle, 4); - this.send(Consts.BLECharacteristic.LPF2_ALL, data); - portObj.finished = () => { - return resolve(); - }; - }); - } + // /** + // * Rotate a motor by a given angle. + // * @method BoostMoveHub#setMotorAngle + // * @param {string} port + // * @param {number} angle How much the motor should be rotated (in degrees). + // * @param {number | Array.} [speed=100] For forward, a value between 1 - 100 should be set. For reverse, a value between -1 to -100. Stop is 0. If you are specifying port AB to control both motors, you can optionally supply a tuple of speeds. + // * @returns {Promise} Resolved upon successful completion of command (ie. once the motor is finished). + // */ + // public setMotorAngle (port: string, angle: number, speed: number | [number, number] = 100) { + // const portObj = this._portLookup(port); + // if (!( + // portObj.type === Consts.DeviceType.BOOST_TACHO_MOTOR || + // portObj.type === Consts.DeviceType.BOOST_MOVE_HUB_MOTOR || + // portObj.type === Consts.DeviceType.CONTROL_PLUS_LARGE_MOTOR || + // portObj.type === Consts.DeviceType.CONTROL_PLUS_XLARGE_MOTOR + // )) { + // throw new Error("Angle rotation is only available when using a Boost Tacho Motor, Boost Move Hub Motor, Control+ Medium Motor, or Control+ Large Motor"); + // } + // if (!this._virtualPorts[portObj.id] && speed instanceof Array) { + // throw new Error(`Port ${portObj.id} can only accept a single speed`); + // } + // portObj.cancelEventTimer(); + // return new Promise((resolve, reject) => { + // portObj.busy = true; + // let data = null; + // if (this._virtualPorts[portObj.id]) { + // data = Buffer.from([0x81, portObj.value, 0x11, 0x0c, 0x00, 0x00, 0x00, 0x00, this._mapSpeed(speed instanceof Array ? speed[0] : speed), this._mapSpeed(speed instanceof Array ? speed[1] : speed), 0x64, 0x7f, 0x03]); + // } else { + // // @ts-ignore: The type of speed is properly checked at the start + // data = Buffer.from([0x81, portObj.value, 0x11, 0x0b, 0x00, 0x00, 0x00, 0x00, this._mapSpeed(speed), 0x64, 0x7f, 0x03]); + // } + // data.writeUInt32LE(angle, 4); + // this.send(data, Consts.BLECharacteristic.LPF2_ALL); + // portObj.finished = () => { + // return resolve(); + // }; + // }); + // } - /** - * Tell motor to goto an absolute position - * @method BoostMoveHub#setAbsolutePosition - * @param {string} port - * @param {number} pos The position of the motor to go to - * @param {number | Array.} [speed=100] A value between 1 - 100 should be set (Direction does not apply when going to absolute position) - * @returns {Promise} Resolved upon successful completion of command (ie. once the motor is finished). - */ - public setAbsolutePosition (port: string, pos: number, speed: number = 100) { - const portObj = this._portLookup(port); - if (!( - portObj.type === Consts.DeviceType.CONTROL_PLUS_LARGE_MOTOR || - portObj.type === Consts.DeviceType.CONTROL_PLUS_XLARGE_MOTOR - )) { - throw new Error("Absolute positioning is only available when using a Control+ Medium Motor, or Control+ Large Motor"); - } - portObj.cancelEventTimer(); - return new Promise((resolve, reject) => { - portObj.busy = true; - let data = null; - if (this._virtualPorts[portObj.id]) { - data = Buffer.from([0x81, portObj.value, 0x11, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, this._mapSpeed(speed), 0x64, 0x7f, 0x03]); - data.writeInt32LE(pos, 4); - data.writeInt32LE(pos, 8); - } else { - // @ts-ignore: The type of speed is properly checked at the start - data = Buffer.from([0x81, portObj.value, 0x11, 0x0d, 0x00, 0x00, 0x00, 0x00, this._mapSpeed(speed), 0x64, 0x7f, 0x03]); - data.writeInt32LE(pos, 4); - } - this.send(Consts.BLECharacteristic.LPF2_ALL, data); - portObj.finished = () => { - return resolve(); - }; - }); - } + // /** + // * Tell motor to goto an absolute position + // * @method BoostMoveHub#setAbsolutePosition + // * @param {string} port + // * @param {number} pos The position of the motor to go to + // * @param {number | Array.} [speed=100] A value between 1 - 100 should be set (Direction does not apply when going to absolute position) + // * @returns {Promise} Resolved upon successful completion of command (ie. once the motor is finished). + // */ + // public setAbsolutePosition (port: string, pos: number, speed: number = 100) { + // const portObj = this._portLookup(port); + // if (!( + // portObj.type === Consts.DeviceType.CONTROL_PLUS_LARGE_MOTOR || + // portObj.type === Consts.DeviceType.CONTROL_PLUS_XLARGE_MOTOR + // )) { + // throw new Error("Absolute positioning is only available when using a Control+ Medium Motor, or Control+ Large Motor"); + // } + // portObj.cancelEventTimer(); + // return new Promise((resolve, reject) => { + // portObj.busy = true; + // let data = null; + // if (this._virtualPorts[portObj.id]) { + // data = Buffer.from([0x81, portObj.value, 0x11, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, this._mapSpeed(speed), 0x64, 0x7f, 0x03]); + // data.writeInt32LE(pos, 4); + // data.writeInt32LE(pos, 8); + // } else { + // // @ts-ignore: The type of speed is properly checked at the start + // data = Buffer.from([0x81, portObj.value, 0x11, 0x0d, 0x00, 0x00, 0x00, 0x00, this._mapSpeed(speed), 0x64, 0x7f, 0x03]); + // data.writeInt32LE(pos, 4); + // } + // this.send(data, Consts.BLECharacteristic.LPF2_ALL); + // portObj.finished = () => { + // return resolve(); + // }; + // }); + // } - /** - * Reset the current motor position as absolute position zero - * @method BoostMoveHub#resetAbsolutePosition - * @param {string} port - * @returns {Promise} Resolved upon successful completion of command (ie. once the motor is finished). - */ - public resetAbsolutePosition (port: string) { - const portObj = this._portLookup(port); - if (!( - portObj.type === Consts.DeviceType.CONTROL_PLUS_LARGE_MOTOR || - portObj.type === Consts.DeviceType.CONTROL_PLUS_XLARGE_MOTOR - )) { - throw new Error("Absolute positioning is only available when using a Control+ Medium Motor, or Control+ Large Motor"); - } - return new Promise((resolve) => { - const data = Buffer.from([0x81, portObj.value, 0x11, 0x51, 0x02, 0x00, 0x00, 0x00, 0x00]); - this.send(Consts.BLECharacteristic.LPF2_ALL, data); - return resolve(); - }); - } + // /** + // * Reset the current motor position as absolute position zero + // * @method BoostMoveHub#resetAbsolutePosition + // * @param {string} port + // * @returns {Promise} Resolved upon successful completion of command (ie. once the motor is finished). + // */ + // public resetAbsolutePosition (port: string) { + // const portObj = this._portLookup(port); + // if (!( + // portObj.type === Consts.DeviceType.CONTROL_PLUS_LARGE_MOTOR || + // portObj.type === Consts.DeviceType.CONTROL_PLUS_XLARGE_MOTOR + // )) { + // throw new Error("Absolute positioning is only available when using a Control+ Medium Motor, or Control+ Large Motor"); + // } + // return new Promise((resolve) => { + // const data = Buffer.from([0x81, portObj.value, 0x11, 0x51, 0x02, 0x00, 0x00, 0x00, 0x00]); + // this.send(data, Consts.BLECharacteristic.LPF2_ALL); + // return resolve(); + // }); + // } - /** - * Fully (hard) stop the motor on a given port. - * @method BoostMoveHub#brakeMotor - * @param {string} port - * @returns {Promise} Resolved upon successful completion of command. - */ - public brakeMotor (port: string) { - return this.setMotorSpeed(port, 127); - } + // /** + // * Fully (hard) stop the motor on a given port. + // * @method BoostMoveHub#brakeMotor + // * @param {string} port + // * @returns {Promise} Resolved upon successful completion of command. + // */ + // public brakeMotor (port: string) { + // return this.setMotorSpeed(port, 127); + // } - /** - * Set the light brightness on a given port. - * @method BoostMoveHub#setLightBrightness - * @param {string} port - * @param {number} brightness Brightness value between 0-100 (0 is off) - * @param {number} [time] How long to turn the light on (in milliseconds). Leave empty to turn the light on indefinitely. - * @returns {Promise} Resolved upon successful completion of command. If time is specified, this is once the light is turned off. - */ - public setLightBrightness (port: string, brightness: number, time?: number) { - const portObj = this._portLookup(port); - portObj.cancelEventTimer(); - return new Promise((resolve, reject) => { - const data = Buffer.from([0x81, portObj.value, 0x11, 0x51, 0x00, brightness]); - this.send(Consts.BLECharacteristic.LPF2_ALL, data); - if (time) { - const timeout = global.setTimeout(() => { - const data = Buffer.from([0x81, portObj.value, 0x11, 0x51, 0x00, 0x00]); - this.send(Consts.BLECharacteristic.LPF2_ALL, data); - return resolve(); - }, time); - portObj.setEventTimer(timeout); - } else { - return resolve(); - } - }); - } + // /** + // * Set the light brightness on a given port. + // * @method BoostMoveHub#setLightBrightness + // * @param {string} port + // * @param {number} brightness Brightness value between 0-100 (0 is off) + // * @param {number} [time] How long to turn the light on (in milliseconds). Leave empty to turn the light on indefinitely. + // * @returns {Promise} Resolved upon successful completion of command. If time is specified, this is once the light is turned off. + // */ + // public setLightBrightness (port: string, brightness: number, time?: number) { + // const portObj = this._portLookup(port); + // portObj.cancelEventTimer(); + // return new Promise((resolve, reject) => { + // const data = Buffer.from([0x81, portObj.value, 0x11, 0x51, 0x00, brightness]); + // this.send(data, Consts.BLECharacteristic.LPF2_ALL); + // if (time) { + // const timeout = global.setTimeout(() => { + // const data = Buffer.from([0x81, portObj.value, 0x11, 0x51, 0x00, 0x00]); + // this.send(data, Consts.BLECharacteristic.LPF2_ALL); + // return resolve(); + // }, time); + // portObj.setEventTimer(timeout); + // } else { + // return resolve(); + // } + // }); + // } protected _checkFirmware (version: string) { diff --git a/src/colordistancesensor.ts b/src/colordistancesensor.ts index 1eecb5c..306b892 100644 --- a/src/colordistancesensor.ts +++ b/src/colordistancesensor.ts @@ -7,6 +7,19 @@ export class ColorDistanceSensor extends Device { constructor (hub: Hub, portId: number) { super(hub, portId, Consts.DeviceType.COLOR_DISTANCE_SENSOR); + + this.on("newListener", (event) => { + switch (event) { + case "color": + this.subscribe(0x00); + break; + case "distance": + this.subscribe(0x01); + break; + } + + }); + } } diff --git a/src/controlplushub.ts b/src/controlplushub.ts index 4a0abf1..4d0a7b9 100644 --- a/src/controlplushub.ts +++ b/src/controlplushub.ts @@ -38,19 +38,19 @@ export class ControlPlusHub extends LPF2Hub { constructor (device: IBLEAbstraction, autoSubscribe: boolean = true) { super(device, autoSubscribe); - this.type = Consts.HubType.CONTROL_PLUS_HUB; - this._ports = { - "A": new Port("A", 0), - "B": new Port("B", 1), - "C": new Port("C", 2), - "D": new Port("D", 3), - "ACCEL": new Port("ACCEL", 97), - "GYRO": new Port("GYRO", 98), - "TILT": new Port("TILT", 99) + this._type = Consts.HubType.CONTROL_PLUS_HUB; + this._portNames = { + "A": 0, + "B": 1, + "C": 2, + "D": 3, + "ACCEL": 97, + "GYRO": 98, + "TILT": 99 }; - this.on("attach", (port, type) => { - this._combinePorts(port, type); - }); + // // this.on("attach", (port, type) => { + // // this._combinePorts(port, type); + // // }); debug("Discovered Control+ Hub"); } @@ -59,7 +59,7 @@ export class ControlPlusHub extends LPF2Hub { return new Promise(async (resolve, reject) => { debug("Connecting to Control+ Hub"); await super.connect(); - this.send(Consts.BLECharacteristic.LPF2_ALL, Buffer.from([0x41, 0x3d, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x01])); // Temperature + this.send(Buffer.from([0x41, 0x3d, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x01]), Consts.BLECharacteristic.LPF2_ALL); // Temperature debug("Connect completed"); return resolve(); }); @@ -75,200 +75,200 @@ export class ControlPlusHub extends LPF2Hub { * @returns {Promise} Resolved upon successful completion of command. If time is specified, this is once the motor is finished. */ public setMotorSpeed (port: string, speed: number | [number, number], time?: number | boolean) { - const portObj = this._portLookup(port); - if (!this._virtualPorts[portObj.id] && speed instanceof Array) { - throw new Error(`Port ${portObj.id} can only accept a single speed`); - } - let cancelEventTimer = true; - if (typeof time === "boolean") { - if (time === true) { - cancelEventTimer = false; - } - time = undefined; - } - if (cancelEventTimer) { - portObj.cancelEventTimer(); - } - return new Promise((resolve, reject) => { - if (time && typeof time === "number") { + // const portObj = this._portLookup(port); + // if (!this._virtualPorts[portObj.id] && speed instanceof Array) { + // throw new Error(`Port ${portObj.id} can only accept a single speed`); + // } + // let cancelEventTimer = true; + // if (typeof time === "boolean") { + // if (time === true) { + // cancelEventTimer = false; + // } + // time = undefined; + // } + // if (cancelEventTimer) { + // portObj.cancelEventTimer(); + // } + // return new Promise((resolve, reject) => { + // if (time && typeof time === "number") { - if ( - portObj.type === Consts.DeviceType.BOOST_TACHO_MOTOR || - portObj.type === Consts.DeviceType.BOOST_MOVE_HUB_MOTOR || - portObj.type === Consts.DeviceType.CONTROL_PLUS_LARGE_MOTOR || - portObj.type === Consts.DeviceType.CONTROL_PLUS_XLARGE_MOTOR - ) { - portObj.busy = true; - let data = null; - if (this._virtualPorts[portObj.id]) { - data = Buffer.from([0x81, portObj.value, 0x11, 0x0a, 0x00, 0x00, this._mapSpeed(speed instanceof Array ? speed[0] : speed), this._mapSpeed(speed instanceof Array ? speed[1] : speed), 0x64, 0x7f, 0x03]); - } else { - // @ts-ignore: The type of speed is properly checked at the start - data = Buffer.from([0x81, portObj.value, 0x11, 0x09, 0x00, 0x00, this._mapSpeed(speed), 0x64, 0x7f, 0x03]); - } - data.writeUInt16LE(time > 65535 ? 65535 : time, 4); - this.send(Consts.BLECharacteristic.LPF2_ALL, data); - portObj.finished = () => { - return resolve(); - }; - } else { - // @ts-ignore: The type of speed is properly checked at the start - const data = Buffer.from([0x81, portObj.value, 0x11, 0x51, 0x00, this._mapSpeed(speed)]); - this.send(Consts.BLECharacteristic.LPF2_ALL, data); - const timeout = global.setTimeout(() => { - const data = Buffer.from([0x81, portObj.value, 0x11, 0x51, 0x00, 0x00]); - this.send(Consts.BLECharacteristic.LPF2_ALL, data); - return resolve(); - // @ts-ignore: The type of time is properly checked at the start - }, time); - portObj.setEventTimer(timeout); - } + // if ( + // portObj.type === Consts.DeviceType.BOOST_TACHO_MOTOR || + // portObj.type === Consts.DeviceType.BOOST_MOVE_HUB_MOTOR || + // portObj.type === Consts.DeviceType.CONTROL_PLUS_LARGE_MOTOR || + // portObj.type === Consts.DeviceType.CONTROL_PLUS_XLARGE_MOTOR + // ) { + // portObj.busy = true; + // let data = null; + // if (this._virtualPorts[portObj.id]) { + // data = Buffer.from([0x81, portObj.value, 0x11, 0x0a, 0x00, 0x00, this._mapSpeed(speed instanceof Array ? speed[0] : speed), this._mapSpeed(speed instanceof Array ? speed[1] : speed), 0x64, 0x7f, 0x03]); + // } else { + // // @ts-ignore: The type of speed is properly checked at the start + // data = Buffer.from([0x81, portObj.value, 0x11, 0x09, 0x00, 0x00, this._mapSpeed(speed), 0x64, 0x7f, 0x03]); + // } + // data.writeUInt16LE(time > 65535 ? 65535 : time, 4); + // this.send(data, Consts.BLECharacteristic.LPF2_ALL); + // portObj.finished = () => { + // return resolve(); + // }; + // } else { + // // @ts-ignore: The type of speed is properly checked at the start + // const data = Buffer.from([0x81, portObj.value, 0x11, 0x51, 0x00, this._mapSpeed(speed)]); + // this.send(data, Consts.BLECharacteristic.LPF2_ALL); + // const timeout = global.setTimeout(() => { + // const data = Buffer.from([0x81, portObj.value, 0x11, 0x51, 0x00, 0x00]); + // this.send(data, Consts.BLECharacteristic.LPF2_ALL); + // return resolve(); + // // @ts-ignore: The type of time is properly checked at the start + // }, time); + // portObj.setEventTimer(timeout); + // } - } else { + // } else { - if (portObj.type === Consts.DeviceType.BOOST_TACHO_MOTOR || portObj.type === Consts.DeviceType.BOOST_MOVE_HUB_MOTOR) { - portObj.busy = true; - let data = null; - if (this._virtualPorts[portObj.id]) { - data = Buffer.from([0x81, portObj.value, 0x11, 0x02, this._mapSpeed(speed instanceof Array ? speed[0] : speed), this._mapSpeed(speed instanceof Array ? speed[1] : speed), 0x64, 0x7f, 0x03]); - } else { - // @ts-ignore: The type of speed is properly checked at the start - data = Buffer.from([0x81, portObj.value, 0x11, 0x01, this._mapSpeed(speed), 0x64, 0x7f, 0x03]); - } - this.send(Consts.BLECharacteristic.LPF2_ALL, data); - portObj.finished = () => { - return resolve(); - }; - } else { - // @ts-ignore: The type of speed is properly checked at the start - const data = Buffer.from([0x81, portObj.value, 0x11, 0x51, 0x00, this._mapSpeed(speed)]); - this.send(Consts.BLECharacteristic.LPF2_ALL, data); - } + // if (portObj.type === Consts.DeviceType.BOOST_TACHO_MOTOR || portObj.type === Consts.DeviceType.BOOST_MOVE_HUB_MOTOR) { + // portObj.busy = true; + // let data = null; + // if (this._virtualPorts[portObj.id]) { + // data = Buffer.from([0x81, portObj.value, 0x11, 0x02, this._mapSpeed(speed instanceof Array ? speed[0] : speed), this._mapSpeed(speed instanceof Array ? speed[1] : speed), 0x64, 0x7f, 0x03]); + // } else { + // // @ts-ignore: The type of speed is properly checked at the start + // data = Buffer.from([0x81, portObj.value, 0x11, 0x01, this._mapSpeed(speed), 0x64, 0x7f, 0x03]); + // } + // this.send(data, Consts.BLECharacteristic.LPF2_ALL); + // portObj.finished = () => { + // return resolve(); + // }; + // } else { + // // @ts-ignore: The type of speed is properly checked at the start + // const data = Buffer.from([0x81, portObj.value, 0x11, 0x51, 0x00, this._mapSpeed(speed)]); + // this.send(data, Consts.BLECharacteristic.LPF2_ALL); + // } - } - }); + // } + // }); } - /** - * Ramp the motor speed on a given port. - * @method ControlPlusHub#rampMotorSpeed - * @param {string} port - * @param {number} fromSpeed For forward, a value between 1 - 100 should be set. For reverse, a value between -1 to -100. Stop is 0. - * @param {number} toSpeed For forward, a value between 1 - 100 should be set. For reverse, a value between -1 to -100. Stop is 0. - * @param {number} time How long the ramp should last (in milliseconds). - * @returns {Promise} Resolved upon successful completion of command. - */ - public rampMotorSpeed (port: string, fromSpeed: number, toSpeed: number, time: number) { - const portObj = this._portLookup(port); - portObj.cancelEventTimer(); - return new Promise((resolve, reject) => { - this._calculateRamp(fromSpeed, toSpeed, time, portObj) - .on("changeSpeed", (speed) => { - this.setMotorSpeed(port, speed, true); - }) - .on("finished", resolve); - }); - } + // /** + // * Ramp the motor speed on a given port. + // * @method ControlPlusHub#rampMotorSpeed + // * @param {string} port + // * @param {number} fromSpeed For forward, a value between 1 - 100 should be set. For reverse, a value between -1 to -100. Stop is 0. + // * @param {number} toSpeed For forward, a value between 1 - 100 should be set. For reverse, a value between -1 to -100. Stop is 0. + // * @param {number} time How long the ramp should last (in milliseconds). + // * @returns {Promise} Resolved upon successful completion of command. + // */ + // public rampMotorSpeed (port: string, fromSpeed: number, toSpeed: number, time: number) { + // const portObj = this._portLookup(port); + // portObj.cancelEventTimer(); + // return new Promise((resolve, reject) => { + // this._calculateRamp(fromSpeed, toSpeed, time, portObj) + // .on("changeSpeed", (speed) => { + // this.setMotorSpeed(port, speed, true); + // }) + // .on("finished", resolve); + // }); + // } - /** - * Rotate a motor by a given angle. - * @method ControlPlusHub#setMotorAngle - * @param {string} port - * @param {number} angle How much the motor should be rotated (in degrees). - * @param {number | Array.} [speed=100] For forward, a value between 1 - 100 should be set. For reverse, a value between -1 to -100. Stop is 0. If you are specifying port AB to control both motors, you can optionally supply a tuple of speeds. - * @returns {Promise} Resolved upon successful completion of command (ie. once the motor is finished). - */ - public setMotorAngle (port: string, angle: number, speed: number | [number, number] = 100) { - const portObj = this._portLookup(port); - if (!( - portObj.type === Consts.DeviceType.BOOST_TACHO_MOTOR || - portObj.type === Consts.DeviceType.BOOST_MOVE_HUB_MOTOR || - portObj.type === Consts.DeviceType.CONTROL_PLUS_LARGE_MOTOR || - portObj.type === Consts.DeviceType.CONTROL_PLUS_XLARGE_MOTOR - )) { - throw new Error("Angle rotation is only available when using a Boost Tacho Motor, Boost Move Hub Motor, Control+ Medium Motor, or Control+ Large Motor"); - } - if (!this._virtualPorts[portObj.id] && speed instanceof Array) { - throw new Error(`Port ${portObj.id} can only accept a single speed`); - } - portObj.cancelEventTimer(); - return new Promise((resolve, reject) => { - portObj.busy = true; - let data = null; - if (this._virtualPorts[portObj.id]) { - data = Buffer.from([0x81, portObj.value, 0x11, 0x0c, 0x00, 0x00, 0x00, 0x00, this._mapSpeed(speed instanceof Array ? speed[0] : speed), this._mapSpeed(speed instanceof Array ? speed[1] : speed), 0x64, 0x7f, 0x03]); - } else { - // @ts-ignore: The type of speed is properly checked at the start - data = Buffer.from([0x81, portObj.value, 0x11, 0x0b, 0x00, 0x00, 0x00, 0x00, this._mapSpeed(speed), 0x64, 0x7f, 0x03]); - } - data.writeUInt32LE(angle, 4); - this.send(Consts.BLECharacteristic.LPF2_ALL, data); - portObj.finished = () => { - return resolve(); - }; - }); - } + // /** + // * Rotate a motor by a given angle. + // * @method ControlPlusHub#setMotorAngle + // * @param {string} port + // * @param {number} angle How much the motor should be rotated (in degrees). + // * @param {number | Array.} [speed=100] For forward, a value between 1 - 100 should be set. For reverse, a value between -1 to -100. Stop is 0. If you are specifying port AB to control both motors, you can optionally supply a tuple of speeds. + // * @returns {Promise} Resolved upon successful completion of command (ie. once the motor is finished). + // */ + // public setMotorAngle (port: string, angle: number, speed: number | [number, number] = 100) { + // const portObj = this._portLookup(port); + // if (!( + // portObj.type === Consts.DeviceType.BOOST_TACHO_MOTOR || + // portObj.type === Consts.DeviceType.BOOST_MOVE_HUB_MOTOR || + // portObj.type === Consts.DeviceType.CONTROL_PLUS_LARGE_MOTOR || + // portObj.type === Consts.DeviceType.CONTROL_PLUS_XLARGE_MOTOR + // )) { + // throw new Error("Angle rotation is only available when using a Boost Tacho Motor, Boost Move Hub Motor, Control+ Medium Motor, or Control+ Large Motor"); + // } + // if (!this._virtualPorts[portObj.id] && speed instanceof Array) { + // throw new Error(`Port ${portObj.id} can only accept a single speed`); + // } + // portObj.cancelEventTimer(); + // return new Promise((resolve, reject) => { + // portObj.busy = true; + // let data = null; + // if (this._virtualPorts[portObj.id]) { + // data = Buffer.from([0x81, portObj.value, 0x11, 0x0c, 0x00, 0x00, 0x00, 0x00, this._mapSpeed(speed instanceof Array ? speed[0] : speed), this._mapSpeed(speed instanceof Array ? speed[1] : speed), 0x64, 0x7f, 0x03]); + // } else { + // // @ts-ignore: The type of speed is properly checked at the start + // data = Buffer.from([0x81, portObj.value, 0x11, 0x0b, 0x00, 0x00, 0x00, 0x00, this._mapSpeed(speed), 0x64, 0x7f, 0x03]); + // } + // data.writeUInt32LE(angle, 4); + // this.send(data, Consts.BLECharacteristic.LPF2_ALL); + // portObj.finished = () => { + // return resolve(); + // }; + // }); + // } - /** - * Tell motor to goto an absolute position - * @method ControlPlusHub#setAbsolutePosition - * @param {string} port - * @param {number} pos The position of the motor to go to - * @param {number | Array.} [speed=100] A value between 1 - 100 should be set (Direction does not apply when going to absolute position) - * @returns {Promise} Resolved upon successful completion of command (ie. once the motor is finished). - */ - public setAbsolutePosition (port: string, pos: number, speed: number = 100) { - const portObj = this._portLookup(port); - if (!( - portObj.type === Consts.DeviceType.CONTROL_PLUS_LARGE_MOTOR || - portObj.type === Consts.DeviceType.CONTROL_PLUS_XLARGE_MOTOR - )) { - throw new Error("Absolute positioning is only available when using a Control+ Medium Motor, or Control+ Large Motor"); - } - portObj.cancelEventTimer(); - return new Promise((resolve, reject) => { - portObj.busy = true; - let data = null; - if (this._virtualPorts[portObj.id]) { - data = Buffer.from([0x81, portObj.value, 0x11, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, this._mapSpeed(speed), 0x64, 0x7f, 0x03]); - data.writeInt32LE(pos, 4); - data.writeInt32LE(pos, 8); - } else { - // @ts-ignore: The type of speed is properly checked at the start - data = Buffer.from([0x81, portObj.value, 0x11, 0x0d, 0x00, 0x00, 0x00, 0x00, this._mapSpeed(speed), 0x64, 0x7f, 0x03]); - data.writeInt32LE(pos, 4); - } - this.send(Consts.BLECharacteristic.LPF2_ALL, data); - portObj.finished = () => { - return resolve(); - }; - }); - } + // /** + // * Tell motor to goto an absolute position + // * @method ControlPlusHub#setAbsolutePosition + // * @param {string} port + // * @param {number} pos The position of the motor to go to + // * @param {number | Array.} [speed=100] A value between 1 - 100 should be set (Direction does not apply when going to absolute position) + // * @returns {Promise} Resolved upon successful completion of command (ie. once the motor is finished). + // */ + // public setAbsolutePosition (port: string, pos: number, speed: number = 100) { + // const portObj = this._portLookup(port); + // if (!( + // portObj.type === Consts.DeviceType.CONTROL_PLUS_LARGE_MOTOR || + // portObj.type === Consts.DeviceType.CONTROL_PLUS_XLARGE_MOTOR + // )) { + // throw new Error("Absolute positioning is only available when using a Control+ Medium Motor, or Control+ Large Motor"); + // } + // portObj.cancelEventTimer(); + // return new Promise((resolve, reject) => { + // portObj.busy = true; + // let data = null; + // if (this._virtualPorts[portObj.id]) { + // data = Buffer.from([0x81, portObj.value, 0x11, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, this._mapSpeed(speed), 0x64, 0x7f, 0x03]); + // data.writeInt32LE(pos, 4); + // data.writeInt32LE(pos, 8); + // } else { + // // @ts-ignore: The type of speed is properly checked at the start + // data = Buffer.from([0x81, portObj.value, 0x11, 0x0d, 0x00, 0x00, 0x00, 0x00, this._mapSpeed(speed), 0x64, 0x7f, 0x03]); + // data.writeInt32LE(pos, 4); + // } + // this.send(data, Consts.BLECharacteristic.LPF2_ALL); + // portObj.finished = () => { + // return resolve(); + // }; + // }); + // } - /** - * Reset the current motor position as absolute position zero - * @method ControlPlusHub#resetAbsolutePosition - * @param {string} port - * @returns {Promise} Resolved upon successful completion of command (ie. once the motor is finished). - */ - public resetAbsolutePosition (port: string) { - const portObj = this._portLookup(port); - if (!( - portObj.type === Consts.DeviceType.CONTROL_PLUS_LARGE_MOTOR || - portObj.type === Consts.DeviceType.CONTROL_PLUS_XLARGE_MOTOR - )) { - throw new Error("Absolute positioning is only available when using a Control+ Medium Motor, or Control+ Large Motor"); - } - return new Promise((resolve) => { - const data = Buffer.from([0x81, portObj.value, 0x11, 0x51, 0x02, 0x00, 0x00, 0x00, 0x00]); - this.send(Consts.BLECharacteristic.LPF2_ALL, data); - return resolve(); - }); - } + // /** + // * Reset the current motor position as absolute position zero + // * @method ControlPlusHub#resetAbsolutePosition + // * @param {string} port + // * @returns {Promise} Resolved upon successful completion of command (ie. once the motor is finished). + // */ + // public resetAbsolutePosition (port: string) { + // const portObj = this._portLookup(port); + // if (!( + // portObj.type === Consts.DeviceType.CONTROL_PLUS_LARGE_MOTOR || + // portObj.type === Consts.DeviceType.CONTROL_PLUS_XLARGE_MOTOR + // )) { + // throw new Error("Absolute positioning is only available when using a Control+ Medium Motor, or Control+ Large Motor"); + // } + // return new Promise((resolve) => { + // const data = Buffer.from([0x81, portObj.value, 0x11, 0x51, 0x02, 0x00, 0x00, 0x00, 0x00]); + // this.send(data, Consts.BLECharacteristic.LPF2_ALL); + // return resolve(); + // }); + // } /** @@ -282,32 +282,32 @@ export class ControlPlusHub extends LPF2Hub { } - /** - * Set the light brightness on a given port. - * @method ControlPlusHub#setLightBrightness - * @param {string} port - * @param {number} brightness Brightness value between 0-100 (0 is off) - * @param {number} [time] How long to turn the light on (in milliseconds). Leave empty to turn the light on indefinitely. - * @returns {Promise} Resolved upon successful completion of command. If time is specified, this is once the light is turned off. - */ - public setLightBrightness (port: string, brightness: number, time?: number) { - const portObj = this._portLookup(port); - portObj.cancelEventTimer(); - return new Promise((resolve, reject) => { - const data = Buffer.from([0x81, portObj.value, 0x11, 0x51, 0x00, brightness]); - this.send(Consts.BLECharacteristic.LPF2_ALL, data); - if (time) { - const timeout = global.setTimeout(() => { - const data = Buffer.from([0x81, portObj.value, 0x11, 0x51, 0x00, 0x00]); - this.send(Consts.BLECharacteristic.LPF2_ALL, data); - return resolve(); - }, time); - portObj.setEventTimer(timeout); - } else { - return resolve(); - } - }); - } + // /** + // * Set the light brightness on a given port. + // * @method ControlPlusHub#setLightBrightness + // * @param {string} port + // * @param {number} brightness Brightness value between 0-100 (0 is off) + // * @param {number} [time] How long to turn the light on (in milliseconds). Leave empty to turn the light on indefinitely. + // * @returns {Promise} Resolved upon successful completion of command. If time is specified, this is once the light is turned off. + // */ + // public setLightBrightness (port: string, brightness: number, time?: number) { + // const portObj = this._portLookup(port); + // portObj.cancelEventTimer(); + // return new Promise((resolve, reject) => { + // const data = Buffer.from([0x81, portObj.value, 0x11, 0x51, 0x00, brightness]); + // this.send(data, Consts.BLECharacteristic.LPF2_ALL); + // if (time) { + // const timeout = global.setTimeout(() => { + // const data = Buffer.from([0x81, portObj.value, 0x11, 0x51, 0x00, 0x00]); + // this.send(data, Consts.BLECharacteristic.LPF2_ALL); + // return resolve(); + // }, time); + // portObj.setEventTimer(timeout); + // } else { + // return resolve(); + // } + // }); + // } } diff --git a/src/device.ts b/src/device.ts index 73ddd4d..8fe202e 100644 --- a/src/device.ts +++ b/src/device.ts @@ -9,13 +9,20 @@ export class Device extends EventEmitter { private _portId: number; private _connected: boolean = true; private _type: number; + private _busy: boolean = false; + private _finished: (() => void) | null = null; constructor (hub: Hub, portId: number, type: number = Consts.DeviceType.UNKNOWN) { super(); this._hub = hub; this._portId = portId; this._type = type; - console.log(`New device on ${this._portId} - ${this._type}`); + this.hub.on("detach", (device) => { + if (device.portId === this.portId) { + this._connected = false; + this.emit("detach"); + } + }); } public get connected () { @@ -31,7 +38,7 @@ export class Device extends EventEmitter { } public get port () { - return "A"; // TODO NK: Look up the port name from the relevant hub + return this.hub.getPortNameForPortId(this.portId); } public get type () { @@ -39,7 +46,11 @@ export class Device extends EventEmitter { } public send (data: Buffer, characteristic: string = Consts.BLECharacteristic.LPF2_ALL, callback?: () => void) { - this.hub.send(characteristic, data, callback); + this.hub.send(data, characteristic, callback); + } + + public subscribe (mode: number) { + this.send(Buffer.from([0x41, this.portId, mode, 0x01, 0x00, 0x00, 0x00, 0x01])); } } diff --git a/src/duplotrainbase.ts b/src/duplotrainbase.ts index a343cdc..596cbd3 100644 --- a/src/duplotrainbase.ts +++ b/src/duplotrainbase.ts @@ -38,11 +38,11 @@ export class DuploTrainBase extends LPF2Hub { constructor (device: IBLEAbstraction, autoSubscribe: boolean = true) { super(device, autoSubscribe); - this.type = Consts.HubType.DUPLO_TRAIN_HUB; - this._ports = { - "MOTOR": new Port("MOTOR", 0), - "COLOR": new Port("COLOR", 18), - "SPEEDOMETER": new Port("SPEEDOMETER", 19) + this._type = Consts.HubType.DUPLO_TRAIN_HUB; + this._portNames = { + "MOTOR": 0, + "COLOR": 18, + "SPEEDOMETER": 19 }; debug("Discovered Duplo Train Base"); } @@ -57,77 +57,6 @@ export class DuploTrainBase extends LPF2Hub { }); } - /** - * Set the motor speed on a given port. - * @method DuploTrainBase#setMotorSpeed - * @param {string} port - * @param {number | Array.} speed For forward, a value between 1 - 100 should be set. For reverse, a value between -1 to -100. Stop is 0. If you are specifying port AB to control both motors, you can optionally supply a tuple of speeds. - * @param {number} [time] How long to activate the motor for (in milliseconds). Leave empty to turn the motor on indefinitely. - * @returns {Promise} Resolved upon successful completion of command. If time is specified, this is once the motor is finished. - */ - public setMotorSpeed (port: string, speed: number, time?: number | boolean) { - const portObj = this._portLookup(port); - let cancelEventTimer = true; - if (typeof time === "boolean") { - if (time === true) { - cancelEventTimer = false; - } - time = undefined; - } - if (cancelEventTimer) { - portObj.cancelEventTimer(); - } - return new Promise((resolve, reject) => { - if (time && typeof time === "number") { - const data = Buffer.from([0x81, portObj.value, 0x11, 0x51, 0x00, this._mapSpeed(speed)]); - this.send(Consts.BLECharacteristic.LPF2_ALL, data); - const timeout = global.setTimeout(() => { - const data = Buffer.from([0x81, portObj.value, 0x11, 0x51, 0x00, 0x00]); - this.send(Consts.BLECharacteristic.LPF2_ALL, data); - return resolve(); - }, time); - portObj.setEventTimer(timeout); - } else { - const data = Buffer.from([0x81, portObj.value, 0x11, 0x51, 0x00, this._mapSpeed(speed)]); - this.send(Consts.BLECharacteristic.LPF2_ALL, data); - return resolve(); - } - }); - } - - - /** - * Ramp the motor speed on a given port. - * @method DuploTrainBase#rampMotorSpeed - * @param {string} port - * @param {number} fromSpeed For forward, a value between 1 - 100 should be set. For reverse, a value between -1 to -100. Stop is 0. - * @param {number} toSpeed For forward, a value between 1 - 100 should be set. For reverse, a value between -1 to -100. Stop is 0. - * @param {number} time How long the ramp should last (in milliseconds). - * @returns {Promise} Resolved upon successful completion of command. - */ - public rampMotorSpeed (port: string, fromSpeed: number, toSpeed: number, time: number) { - const portObj = this._portLookup(port); - portObj.cancelEventTimer(); - return new Promise((resolve, reject) => { - this._calculateRamp(fromSpeed, toSpeed, time, portObj) - .on("changeSpeed", (speed) => { - this.setMotorSpeed(port, speed, true); - }) - .on("finished", resolve); - }); - } - - - /** - * Fully (hard) stop the motor on a given port. - * @method DuploTrainBase#brakeMotor - * @param {string} port - * @returns {Promise} Resolved upon successful completion of command. - */ - public brakeMotor (port: string) { - return this.setMotorSpeed(port, 127); - } - /** * Play a built-in train sound. @@ -138,7 +67,7 @@ export class DuploTrainBase extends LPF2Hub { public playSound (sound: number) { return new Promise((resolve, reject) => { const data = Buffer.from([0x81, 0x01, 0x11, 0x51, 0x01, sound]); - this.send(Consts.BLECharacteristic.LPF2_ALL, data); + this.send(data, Consts.BLECharacteristic.LPF2_ALL); return resolve(); }); } diff --git a/src/hub.ts b/src/hub.ts index f4fd976..323f53d 100644 --- a/src/hub.ts +++ b/src/hub.ts @@ -19,12 +19,12 @@ export class Hub extends EventEmitter { public autoSubscribe: boolean = true; public useSpeedMap: boolean = true; - public type: Consts.HubType = Consts.HubType.UNKNOWN; + protected _type: Consts.HubType = Consts.HubType.UNKNOWN; protected _attachedDevices: Device[] = []; - protected _ports: {[port: string]: Port} = {}; - protected _virtualPorts: {[port: string]: Port} = {}; + protected _portNames: {[port: string]: number} = {}; + // protected _virtualPorts: {[port: string]: Port} = {}; protected _name: string = ""; protected _firmwareVersion: string = "0.0.00.0000"; @@ -63,6 +63,15 @@ export class Hub extends EventEmitter { } + /** + * @readonly + * @property {string} type Hub type + */ + public get type () { + return this._type; + } + + /** * @readonly * @property {string} firmwareVersion Firmware version of the hub @@ -160,44 +169,55 @@ export class Hub extends EventEmitter { * @method Hub#disconnect * @returns {Promise} Resolved upon successful disconnect. */ - public async disconnect () { - this._bleDevice.disconnect(); + public disconnect () { + return this._bleDevice.disconnect(); } - /** - * Subscribe to sensor notifications on a given port. - * @method Hub#subscribe - * @param {string} port - * @param {number} [mode] The sensor mode to activate. If no mode is provided, the default for that sensor will be chosen. - * @returns {Promise} Resolved upon successful issuance of command. - */ - public subscribe (port: string, mode?: number) { - return new Promise((resolve, reject) => { - let newMode = this._getModeForDeviceType(this._portLookup(port).type); - if (mode !== undefined) { - newMode = mode; + public getPortNameForPortId (portId: number) { + for (const port of Object.keys(this._portNames)) { + console.log(port); + if (this._portNames[port] === portId) { + return port; } - this._activatePortDevice(this._portLookup(port).value, this._portLookup(port).type, newMode, 0x00, () => { - return resolve(); - }); - }); + } + return; } - /** - * Unsubscribe to sensor notifications on a given port. - * @method Hub#unsubscribe - * @param {string} port - * @returns {Promise} Resolved upon successful issuance of command. - */ - public unsubscribe (port: string) { - return new Promise((resolve, reject) => { - const mode = this._getModeForDeviceType(this._portLookup(port).type); - this._deactivatePortDevice(this._portLookup(port).value, this._portLookup(port).type, mode, 0x00, () => { - return resolve(); - }); - }); - } + + // /** + // * Subscribe to sensor notifications on a given port. + // * @method Hub#subscribe + // * @param {string} port + // * @param {number} [mode] The sensor mode to activate. If no mode is provided, the default for that sensor will be chosen. + // * @returns {Promise} Resolved upon successful issuance of command. + // */ + // public subscribe (port: string, mode?: number) { + // return new Promise((resolve, reject) => { + // let newMode = this._getModeForDeviceType(this._portLookup(port).type); + // if (mode !== undefined) { + // newMode = mode; + // } + // this._activatePortDevice(this._portLookup(port).value, this._portLookup(port).type, newMode, 0x00, () => { + // return resolve(); + // }); + // }); + // } + + // /** + // * Unsubscribe to sensor notifications on a given port. + // * @method Hub#unsubscribe + // * @param {string} port + // * @returns {Promise} Resolved upon successful issuance of command. + // */ + // public unsubscribe (port: string) { + // return new Promise((resolve, reject) => { + // const mode = this._getModeForDeviceType(this._portLookup(port).type); + // this._deactivatePortDevice(this._portLookup(port).value, this._portLookup(port).type, mode, 0x00, () => { + // return resolve(); + // }); + // }); + // } /** @@ -228,28 +248,18 @@ export class Hub extends EventEmitter { } - /** - * Get the hub type. - * @method Hub#getHubType - * @returns {HubType} - */ - public getHubType () { - return this.type; - } + // /** + // * Get the device type for a given port. + // * @method Hub#getPortDeviceType + // * @param {string} port + // * @returns {DeviceType} + // */ + // public getPortDeviceType (port: string) { + // return this._portLookup(port).type; + // } - /** - * Get the device type for a given port. - * @method Hub#getPortDeviceType - * @param {string} port - * @returns {DeviceType} - */ - public getPortDeviceType (port: string) { - return this._portLookup(port).type; - } - - - public send (uuid: string, message: Buffer, callback?: () => void) { + public send (message: Buffer, uuid: string, callback?: () => void) { if (callback) { callback(); } @@ -273,26 +283,12 @@ export class Hub extends EventEmitter { // } - protected _activatePortDevice (port: number, type: number, mode: number, format: number, callback?: () => void) { - if (callback) { - callback(); - } - } + protected _attachDevice (device: Device) { - - protected _deactivatePortDevice (port: number, type: number, mode: number, format: number, callback?: () => void) { - if (callback) { - callback(); - } - } - - - protected _registerDeviceAttachment (device: Device) { - - const exists = this._attachedDevices.find((attachedDevice) => attachedDevice.portId === device.portId); + const exists = this._getDeviceByPortId(device.portId); if (exists) { - // TODO NK: Remove existing zombie device + this._attachedDevices.splice(this._attachedDevices.findIndex((attachedDevice) => attachedDevice.portId === device.portId), 1); } else { this._attachedDevices.push(device); } @@ -333,110 +329,126 @@ export class Hub extends EventEmitter { } - protected _getPortForPortNumber (num: number) { - - for (const key of Object.keys(this._ports)) { - if (this._ports[key].value === num) { - return this._ports[key]; - } - } - - for (const key of Object.keys(this._virtualPorts)) { - if (this._virtualPorts[key].value === num) { - return this._virtualPorts[key]; - } - } - - return false; - + protected _detachDevice (device: Device) { + this._attachedDevices.splice(this._attachedDevices.findIndex((attachedDevice) => attachedDevice.portId === device.portId), 1); + /** + * Emits when a device is detached from the Hub. + * @event Hub#attach + * @param {Device} device + */ + this.emit("detach", device); } - protected _mapSpeed (speed: number) { // Speed range of -100 to 100 is supported unless speed mapping is turned off, in which case, you're on your own! - if (!this.useSpeedMap) { - return speed; - } - - if (speed === 127) { - return 127; // Hard stop - } - - if (speed > 100) { - speed = 100; - } else if (speed < -100) { - speed = -100; - } - - return speed; + protected _getDeviceByPortId (portId: number) { + return this._attachedDevices.find((attachedDevice) => attachedDevice.portId === portId); } - protected _calculateRamp (fromSpeed: number, toSpeed: number, time: number, port: Port) { - const emitter = new EventEmitter(); - const steps = Math.abs(toSpeed - fromSpeed); - let delay = time / steps; - let increment = 1; - if (delay < 50 && steps > 0) { - increment = 50 / delay; - delay = 50; - } - if (fromSpeed > toSpeed) { - increment = -increment; - } - let i = 0; - const interval = setInterval(() => { - let speed = Math.round(fromSpeed + (++i * increment)); - if (toSpeed > fromSpeed && speed > toSpeed) { - speed = toSpeed; - } else if (fromSpeed > toSpeed && speed < toSpeed) { - speed = toSpeed; - } - emitter.emit("changeSpeed", speed); - if (speed === toSpeed) { - clearInterval(interval); - emitter.emit("finished"); - } - }, delay); - port.setEventTimer(interval); - return emitter; - } + // protected _getPortForPortNumber (num: number) { + + // for (const key of Object.keys(this._ports)) { + // if (this._ports[key].value === num) { + // return this._ports[key]; + // } + // } + + // for (const key of Object.keys(this._virtualPorts)) { + // if (this._virtualPorts[key].value === num) { + // return this._virtualPorts[key]; + // } + // } + + // return false; + + // } - protected _portLookup (portName: string) { - const portNameUpper = portName.toUpperCase(); - const port = this._ports[portNameUpper] || this._virtualPorts[portNameUpper]; - if (!port) { - throw new Error(`Port ${portNameUpper} does not exist on this Hub type`); - } - return port; - } + // protected _mapSpeed (speed: number) { // Speed range of -100 to 100 is supported unless speed mapping is turned off, in which case, you're on your own! + // if (!this.useSpeedMap) { + // return speed; + // } - private _getModeForDeviceType (type: Consts.DeviceType) { - switch (type) { - case Consts.DeviceType.SIMPLE_MEDIUM_LINEAR_MOTOR: - return 0x02; - case Consts.DeviceType.TRAIN_MOTOR: - return 0x02; - case Consts.DeviceType.BOOST_TACHO_MOTOR: - return 0x02; - case Consts.DeviceType.BOOST_MOVE_HUB_MOTOR: - return 0x02; - case Consts.DeviceType.CONTROL_PLUS_LARGE_MOTOR: - return 0x02; - case Consts.DeviceType.CONTROL_PLUS_XLARGE_MOTOR: - return 0x02; - case Consts.DeviceType.CONTROL_PLUS_TILT: - return 0x00; - case Consts.DeviceType.CONTROL_PLUS_ACCELEROMETER: - return 0x00; - case Consts.DeviceType.COLOR_DISTANCE_SENSOR: - return (this.type === Consts.HubType.WEDO2_SMART_HUB ? 0x00 : 0x08); - case Consts.DeviceType.BOOST_TILT: - return 0x04; - default: - return 0x00; - } - } + // if (speed === 127) { + // return 127; // Hard stop + // } + + // if (speed > 100) { + // speed = 100; + // } else if (speed < -100) { + // speed = -100; + // } + + // return speed; + // } + + + // protected _calculateRamp (fromSpeed: number, toSpeed: number, time: number, port: Port) { + // const emitter = new EventEmitter(); + // const steps = Math.abs(toSpeed - fromSpeed); + // let delay = time / steps; + // let increment = 1; + // if (delay < 50 && steps > 0) { + // increment = 50 / delay; + // delay = 50; + // } + // if (fromSpeed > toSpeed) { + // increment = -increment; + // } + // let i = 0; + // const interval = setInterval(() => { + // let speed = Math.round(fromSpeed + (++i * increment)); + // if (toSpeed > fromSpeed && speed > toSpeed) { + // speed = toSpeed; + // } else if (fromSpeed > toSpeed && speed < toSpeed) { + // speed = toSpeed; + // } + // emitter.emit("changeSpeed", speed); + // if (speed === toSpeed) { + // clearInterval(interval); + // emitter.emit("finished"); + // } + // }, delay); + // port.setEventTimer(interval); + // return emitter; + // } + + + // protected _portLookup (portName: string) { + // const portNameUpper = portName.toUpperCase(); + // const port = this._ports[portNameUpper] || this._virtualPorts[portNameUpper]; + // if (!port) { + // throw new Error(`Port ${portNameUpper} does not exist on this Hub type`); + // } + // return port; + // } + + // private _getModeForDeviceType (type: Consts.DeviceType) { + // switch (type) { + // case Consts.DeviceType.SIMPLE_MEDIUM_LINEAR_MOTOR: + // return 0x02; + // case Consts.DeviceType.TRAIN_MOTOR: + // return 0x02; + // case Consts.DeviceType.BOOST_TACHO_MOTOR: + // return 0x02; + // case Consts.DeviceType.BOOST_MOVE_HUB_MOTOR: + // return 0x02; + // case Consts.DeviceType.CONTROL_PLUS_LARGE_MOTOR: + // return 0x02; + // case Consts.DeviceType.CONTROL_PLUS_XLARGE_MOTOR: + // return 0x02; + // case Consts.DeviceType.CONTROL_PLUS_TILT: + // return 0x00; + // case Consts.DeviceType.CONTROL_PLUS_ACCELEROMETER: + // return 0x00; + // case Consts.DeviceType.COLOR_DISTANCE_SENSOR: + // return (this.type === Consts.HubType.WEDO2_SMART_HUB ? 0x00 : 0x08); + // case Consts.DeviceType.BOOST_TILT: + // return 0x04; + // default: + // return 0x00; + // } + // } } diff --git a/src/lpf2hub.ts b/src/lpf2hub.ts index 9593682..7adcc86 100644 --- a/src/lpf2hub.ts +++ b/src/lpf2hub.ts @@ -48,21 +48,21 @@ export class LPF2Hub extends Hub { await this._bleDevice.discoverCharacteristicsForService(Consts.BLEService.LPF2_HUB); this._bleDevice.subscribeToCharacteristic(Consts.BLECharacteristic.LPF2_ALL, this._parseMessage.bind(this)); if (this._voltagePort !== undefined) { - this.send(Consts.BLECharacteristic.LPF2_ALL, Buffer.from([0x41, this._voltagePort, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01])); // Activate voltage reports + this.send(Buffer.from([0x41, this._voltagePort, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01]), Consts.BLECharacteristic.LPF2_ALL); // Activate voltage reports } if (this._currentPort !== undefined) { - this.send(Consts.BLECharacteristic.LPF2_ALL, Buffer.from([0x41, this._currentPort, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01])); // Activate current reports + this.send(Buffer.from([0x41, this._currentPort, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01]), Consts.BLECharacteristic.LPF2_ALL); // Activate current reports } - if (this.type === Consts.HubType.DUPLO_TRAIN_HUB) { - this.send(Consts.BLECharacteristic.LPF2_ALL, Buffer.from([0x41, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x01])); + if (this._type === Consts.HubType.DUPLO_TRAIN_HUB) { + this.send(Buffer.from([0x41, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x01]), Consts.BLECharacteristic.LPF2_ALL); } await this.sleep(100); - this.send(Consts.BLECharacteristic.LPF2_ALL, Buffer.from([0x01, 0x02, 0x02])); // Activate button reports - this.send(Consts.BLECharacteristic.LPF2_ALL, Buffer.from([0x01, 0x03, 0x05])); // Request firmware version - this.send(Consts.BLECharacteristic.LPF2_ALL, Buffer.from([0x01, 0x04, 0x05])); // Request hardware version - this.send(Consts.BLECharacteristic.LPF2_ALL, Buffer.from([0x01, 0x05, 0x02])); // Activate RSSI updates - this.send(Consts.BLECharacteristic.LPF2_ALL, Buffer.from([0x01, 0x06, 0x02])); // Activate battery level reports - this.send(Consts.BLECharacteristic.LPF2_ALL, Buffer.from([0x01, 0x0d, 0x05])); // Request primary MAC address + this.send(Buffer.from([0x01, 0x02, 0x02]), Consts.BLECharacteristic.LPF2_ALL); // Activate button reports + this.send(Buffer.from([0x01, 0x03, 0x05]), Consts.BLECharacteristic.LPF2_ALL); // Request firmware version + this.send(Buffer.from([0x01, 0x04, 0x05]), Consts.BLECharacteristic.LPF2_ALL); // Request hardware version + this.send(Buffer.from([0x01, 0x05, 0x02]), Consts.BLECharacteristic.LPF2_ALL); // Activate RSSI updates + this.send(Buffer.from([0x01, 0x06, 0x02]), Consts.BLECharacteristic.LPF2_ALL); // Activate battery level reports + this.send(Buffer.from([0x01, 0x0d, 0x05]), Consts.BLECharacteristic.LPF2_ALL); // Request primary MAC address this.emit("connect"); resolve(); }); @@ -76,7 +76,7 @@ export class LPF2Hub extends Hub { */ public shutdown () { return new Promise((resolve, reject) => { - this.send(Consts.BLECharacteristic.LPF2_ALL, Buffer.from([0x02, 0x01]), () => { + this.send(Buffer.from([0x02, 0x01]), Consts.BLECharacteristic.LPF2_ALL, () => { return resolve(); }); }); @@ -97,8 +97,8 @@ export class LPF2Hub extends Hub { let data = Buffer.from([0x01, 0x01, 0x01]); data = Buffer.concat([data, Buffer.from(name, "ascii")]); // Send this twice, as sometimes the first time doesn't take - this.send(Consts.BLECharacteristic.LPF2_ALL, data); - this.send(Consts.BLECharacteristic.LPF2_ALL, data); + this.send(data, Consts.BLECharacteristic.LPF2_ALL); + this.send(data, Consts.BLECharacteristic.LPF2_ALL); this._name = name; return resolve(); }); @@ -114,12 +114,12 @@ export class LPF2Hub extends Hub { public setLEDColor (color: number | boolean) { return new Promise((resolve, reject) => { let data = Buffer.from([0x41, this._ledPort, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00]); - this.send(Consts.BLECharacteristic.LPF2_ALL, data); + this.send(data, Consts.BLECharacteristic.LPF2_ALL); if (typeof color === "boolean") { color = 0; } data = Buffer.from([0x81, this._ledPort, 0x11, 0x51, 0x00, color]); - this.send(Consts.BLECharacteristic.LPF2_ALL, data); + this.send(data, Consts.BLECharacteristic.LPF2_ALL); return resolve(); }); } @@ -136,9 +136,9 @@ export class LPF2Hub extends Hub { public setLEDRGB (red: number, green: number, blue: number) { return new Promise((resolve, reject) => { let data = Buffer.from([0x41, this._ledPort, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00]); - this.send(Consts.BLECharacteristic.LPF2_ALL, data); + this.send(data, Consts.BLECharacteristic.LPF2_ALL); data = Buffer.from([0x81, this._ledPort, 0x11, 0x51, 0x01, red, green, blue]); - this.send(Consts.BLECharacteristic.LPF2_ALL, data); + this.send(data, Consts.BLECharacteristic.LPF2_ALL); return resolve(); }); } @@ -146,14 +146,14 @@ export class LPF2Hub extends Hub { public sendRaw (message: Buffer) { return new Promise((resolve, reject) => { - this.send(Consts.BLECharacteristic.LPF2_ALL, message, () => { + this.send(message, Consts.BLECharacteristic.LPF2_ALL, () => { return resolve(); }); }); } - public send (uuid: string, message: Buffer, callback?: () => void) { + public send (message: Buffer, uuid: string, callback?: () => void) { message = Buffer.concat([Buffer.alloc(2), message]); message[0] = message.length; debug("Sent Message (LPF2_ALL)", message); @@ -161,30 +161,30 @@ export class LPF2Hub extends Hub { } - protected _activatePortDevice (port: number, type: number, mode: number, format: number, callback?: () => void) { - this.send(Consts.BLECharacteristic.LPF2_ALL, Buffer.from([0x41, port, mode, 0x01, 0x00, 0x00, 0x00, 0x01]), callback); - } + // protected _activatePortDevice (port: number, type: number, mode: number, format: number, callback?: () => void) { + // this.send(Buffer.from([0x41, port, mode, 0x01, 0x00, 0x00, 0x00, 0x01]), Consts.BLECharacteristic.LPF2_ALL, callback); + // } - protected _deactivatePortDevice (port: number, type: number, mode: number, format: number, callback?: () => void) { - this.send(Consts.BLECharacteristic.LPF2_ALL, Buffer.from([0x41, port, mode, 0x01, 0x00, 0x00, 0x00, 0x00]), callback); - } + // protected _deactivatePortDevice (port: number, type: number, mode: number, format: number, callback?: () => void) { + // this.send(Buffer.from([0x41, port, mode, 0x01, 0x00, 0x00, 0x00, 0x00]), Consts.BLECharacteristic.LPF2_ALL, callback); + // } - protected _combinePorts (port: string, type: number) { - if (!this._ports[port]) { - return; - } - const portObj = this._portLookup(port); - if (portObj) { - Object.keys(this._ports).forEach((id) => { - if (this._ports[id].type === type && this._ports[id].value !== portObj.value && !this._virtualPorts[`${portObj.value < this._ports[id].value ? portObj.id : this._ports[id].id}${portObj.value > this._ports[id].value ? portObj.id : this._ports[id].id}`]) { - debug("Combining ports", portObj.value < this._ports[id].value ? portObj.id : id, portObj.value > this._ports[id].value ? portObj.id : id); - this.send(Consts.BLECharacteristic.LPF2_ALL, Buffer.from([0x61, 0x01, portObj.value < this._ports[id].value ? portObj.value : this._ports[id].value, portObj.value > this._ports[id].value ? portObj.value : this._ports[id].value])); - } - }); - } - } + // protected _combinePorts (port: string, type: number) { + // if (!this._ports[port]) { + // return; + // } + // const portObj = this._portLookup(port); + // if (portObj) { + // Object.keys(this._ports).forEach((id) => { + // if (this._ports[id].type === type && this._ports[id].value !== portObj.value && !this._virtualPorts[`${portObj.value < this._ports[id].value ? portObj.id : this._ports[id].id}${portObj.value > this._ports[id].value ? portObj.id : this._ports[id].id}`]) { + // debug("Combining ports", portObj.value < this._ports[id].value ? portObj.id : id, portObj.value > this._ports[id].value ? portObj.id : id); + // this.send(Buffer.from([0x61, 0x01, portObj.value < this._ports[id].value ? portObj.value : this._ports[id].value, portObj.value > this._ports[id].value ? portObj.value : this._ports[id].value]), Consts.BLECharacteristic.LPF2_ALL); + // } + // }); + // } + // } protected _checkFirmware (version: string) { @@ -297,6 +297,7 @@ export class LPF2Hub extends Hub { const event = data[4]; const deviceType = event ? data.readUInt16LE(5) : 0; + // Handle device attachments if (event === 0x01) { let device; @@ -313,8 +314,14 @@ export class LPF2Hub extends Hub { break; } - this._registerDeviceAttachment(device); + this._attachDevice(device); + // Handle device detachments + } else if (event === 0x00) { + const device = this._getDeviceByPortId(portId); + if (device) { + this._detachDevice(device); + } } @@ -358,8 +365,8 @@ export class LPF2Hub extends Hub { private _sendPortInformationRequest (port: number) { - this.send(Consts.BLECharacteristic.LPF2_ALL, Buffer.from([0x21, port, 0x01])); - this.send(Consts.BLECharacteristic.LPF2_ALL, Buffer.from([0x21, port, 0x02])); // Mode combinations + this.send(Buffer.from([0x21, port, 0x01]), Consts.BLECharacteristic.LPF2_ALL); + this.send(Buffer.from([0x21, port, 0x02]), Consts.BLECharacteristic.LPF2_ALL); // Mode combinations } @@ -390,7 +397,7 @@ export class LPF2Hub extends Hub { private _sendModeInformationRequest (port: number, mode: number, type: number) { - this.send(Consts.BLECharacteristic.LPF2_ALL, Buffer.from([0x22, port, mode, type])); + this.send(Buffer.from([0x22, port, mode, type]), Consts.BLECharacteristic.LPF2_ALL); } @@ -426,233 +433,233 @@ export class LPF2Hub extends Hub { private _parsePortAction (data: Buffer) { - const port = this._getPortForPortNumber(data[3]); + // const port = this._getPortForPortNumber(data[3]); - if (!port) { - return; - } + // if (!port) { + // return; + // } - if (data[4] === 0x0a) { - port.busy = false; - if (port.finished) { - port.finished(); - port.finished = null; - } - } + // if (data[4] === 0x0a) { + // port.busy = false; + // if (port.finished) { + // port.finished(); + // port.finished = null; + // } + // } } private _parseSensorMessage (data: Buffer) { - if (data[3] === this._voltagePort) { - const voltageRaw = data.readUInt16LE(4); - this._voltage = voltageRaw * this._voltageMaxV / this._voltageMaxRaw; - return; - } else if (data[3] === this._currentPort) { - const currentRaw = data.readUInt16LE(4); - this._current = this._currentMaxMA * currentRaw / this._currentMaxRaw; - return; - } + // if (data[3] === this._voltagePort) { + // const voltageRaw = data.readUInt16LE(4); + // this._voltage = voltageRaw * this._voltageMaxV / this._voltageMaxRaw; + // return; + // } else if (data[3] === this._currentPort) { + // const currentRaw = data.readUInt16LE(4); + // this._current = this._currentMaxMA * currentRaw / this._currentMaxRaw; + // return; + // } - if ((data[3] === 0x3d && this.type === Consts.HubType.CONTROL_PLUS_HUB)) { // Control+ CPU Temperature - /** - * Emits when a change is detected on a temperature sensor. Measured in degrees centigrade. - * @event LPF2Hub#temp - * @param {string} port For Control+ Hubs, port will be "CPU" as the sensor reports CPU temperature. - * @param {number} temp - */ - this.emit("temp", "CPU", ((data.readInt16LE(4) / 900) * 90).toFixed(2)); - return; - } + // if ((data[3] === 0x3d && this.type === Consts.HubType.CONTROL_PLUS_HUB)) { // Control+ CPU Temperature + // /** + // * Emits when a change is detected on a temperature sensor. Measured in degrees centigrade. + // * @event LPF2Hub#temp + // * @param {string} port For Control+ Hubs, port will be "CPU" as the sensor reports CPU temperature. + // * @param {number} temp + // */ + // this.emit("temp", "CPU", ((data.readInt16LE(4) / 900) * 90).toFixed(2)); + // return; + // } - const port = this._getPortForPortNumber(data[3]); + // const port = this._getPortForPortNumber(data[3]); - if (!port) { - return; - } + // if (!port) { + // return; + // } - if (port && port.connected) { - switch (port.type) { - case Consts.DeviceType.WEDO2_DISTANCE: { - let distance = data[4]; - if (data[5] === 1) { - distance = data[4] + 255; - } - /** - * Emits when a distance sensor is activated. - * @event LPF2Hub#distance - * @param {string} port - * @param {number} distance Distance, in millimeters. - */ - this.emit("distance", port.id, distance * 10); - break; - } - case Consts.DeviceType.COLOR_DISTANCE_SENSOR: { + // if (port && port.connected) { + // switch (port.type) { + // case Consts.DeviceType.WEDO2_DISTANCE: { + // let distance = data[4]; + // if (data[5] === 1) { + // distance = data[4] + 255; + // } + // /** + // * Emits when a distance sensor is activated. + // * @event LPF2Hub#distance + // * @param {string} port + // * @param {number} distance Distance, in millimeters. + // */ + // this.emit("distance", port.id, distance * 10); + // break; + // } + // case Consts.DeviceType.COLOR_DISTANCE_SENSOR: { - /** - * Emits when a color sensor is activated. - * @event LPF2Hub#color - * @param {string} port - * @param {Color} color - */ - if (data[4] <= 10) { - this.emit("color", port.id, data[4]); - } + // /** + // * Emits when a color sensor is activated. + // * @event LPF2Hub#color + // * @param {string} port + // * @param {Color} color + // */ + // if (data[4] <= 10) { + // this.emit("color", port.id, data[4]); + // } - let distance = data[5]; - const partial = data[7]; + // let distance = data[5]; + // const partial = data[7]; - if (partial > 0) { - distance += 1.0 / partial; - } + // if (partial > 0) { + // distance += 1.0 / partial; + // } - distance = Math.floor(distance * 25.4) - 20; + // distance = Math.floor(distance * 25.4) - 20; - this.emit("distance", port.id, distance); + // this.emit("distance", port.id, distance); - /** - * A combined color and distance event, emits when the sensor is activated. - * @event LPF2Hub#colorAndDistance - * @param {string} port - * @param {Color} color - * @param {number} distance Distance, in millimeters. - */ - if (data[4] <= 10) { - this.emit("colorAndDistance", port.id, data[4], distance); - } - break; - } - case Consts.DeviceType.WEDO2_TILT: { - const tiltX = data.readInt8(4); - const tiltY = data.readInt8(5); - this._lastTiltX = tiltX; - this._lastTiltY = tiltY; - /** - * Emits when a tilt sensor is activated. - * @event LPF2Hub#tilt - * @param {string} port If the event is fired from the Move Hub or Control+ Hub's in-built tilt sensor, the special port "TILT" is used. - * @param {number} x - * @param {number} y - * @param {number} z (Only available when using a Control+ Hub) - */ - this.emit("tilt", port.id, this._lastTiltX, this._lastTiltY, this._lastTiltZ); - break; - } - case Consts.DeviceType.BOOST_TACHO_MOTOR: { - const rotation = data.readInt32LE(4); - /** - * Emits when a rotation sensor is activated. - * @event LPF2Hub#rotate - * @param {string} port - * @param {number} rotation - */ - this.emit("rotate", port.id, rotation); - break; - } - case Consts.DeviceType.BOOST_MOVE_HUB_MOTOR: { - const rotation = data.readInt32LE(4); - this.emit("rotate", port.id, rotation); - break; - } - case Consts.DeviceType.CONTROL_PLUS_LARGE_MOTOR: { - const rotation = data.readInt32LE(4); - this.emit("rotate", port.id, rotation); - break; - } - case Consts.DeviceType.CONTROL_PLUS_XLARGE_MOTOR: { - const rotation = data.readInt32LE(4); - this.emit("rotate", port.id, rotation); - break; - } - case Consts.DeviceType.CONTROL_PLUS_TILT: { - const tiltZ = data.readInt16LE(4); - const tiltY = data.readInt16LE(6); - const tiltX = data.readInt16LE(8); - this._lastTiltX = tiltX; - this._lastTiltY = tiltY; - this._lastTiltZ = tiltZ; - this.emit("tilt", "TILT", this._lastTiltX, this._lastTiltY, this._lastTiltZ); - break; - } - case Consts.DeviceType.CONTROL_PLUS_GYRO: { - const gyroX = Math.round(data.readInt16LE(4) * 7 / 400); - const gyroY = Math.round(data.readInt16LE(6) * 7 / 400); - const gyroZ = Math.round(data.readInt16LE(8) * 7 / 400); - /** - * Emits when gyroscope detects movement. Measured in DPS - degrees per second. - * @event LPF2Hub#gyro - * @param {string} port - * @param {number} x - * @param {number} y - * @param {number} z - */ - this.emit("gyro", "GYRO", gyroX, gyroY, gyroZ); - break; - } - case Consts.DeviceType.CONTROL_PLUS_ACCELEROMETER: { - const accelX = Math.round(data.readInt16LE(4) / 4.096); - const accelY = Math.round(data.readInt16LE(6) / 4.096); - const accelZ = Math.round(data.readInt16LE(8) / 4.096); - /** - * Emits when accelerometer detects movement. Measured in mG. - * @event LPF2Hub#accel - * @param {string} port - * @param {number} x - * @param {number} y - * @param {number} z - */ - this.emit("accel", "ACCEL", accelX, accelY, accelZ); - break; - } - case Consts.DeviceType.BOOST_TILT: { - const tiltX = data.readInt8(4); - const tiltY = data.readInt8(5); - this._lastTiltX = tiltX; - this._lastTiltY = tiltY; - this.emit("tilt", port.id, this._lastTiltX, this._lastTiltY, this._lastTiltZ); - break; - } - case Consts.DeviceType.POWERED_UP_REMOTE_BUTTON: { - switch (data[4]) { - case 0x01: { - this.emit("button", port.id, Consts.ButtonState.UP); - break; - } - case 0xff: { - this.emit("button", port.id, Consts.ButtonState.DOWN); - break; - } - case 0x7f: { - this.emit("button", port.id, Consts.ButtonState.STOP); - break; - } - case 0x00: { - this.emit("button", port.id, Consts.ButtonState.RELEASED); - break; - } - } - break; - } - case Consts.DeviceType.DUPLO_TRAIN_BASE_COLOR: { - if (data[4] <= 10) { - this.emit("color", port.id, data[4]); - } - break; - } - case Consts.DeviceType.DUPLO_TRAIN_BASE_SPEEDOMETER: { - /** - * Emits on a speed change. - * @event LPF2Hub#speed - * @param {string} port - * @param {number} speed - */ - const speed = data.readInt16LE(4); - this.emit("speed", port.id, speed); - break; - } - } - } + // /** + // * A combined color and distance event, emits when the sensor is activated. + // * @event LPF2Hub#colorAndDistance + // * @param {string} port + // * @param {Color} color + // * @param {number} distance Distance, in millimeters. + // */ + // if (data[4] <= 10) { + // this.emit("colorAndDistance", port.id, data[4], distance); + // } + // break; + // } + // case Consts.DeviceType.WEDO2_TILT: { + // const tiltX = data.readInt8(4); + // const tiltY = data.readInt8(5); + // this._lastTiltX = tiltX; + // this._lastTiltY = tiltY; + // /** + // * Emits when a tilt sensor is activated. + // * @event LPF2Hub#tilt + // * @param {string} port If the event is fired from the Move Hub or Control+ Hub's in-built tilt sensor, the special port "TILT" is used. + // * @param {number} x + // * @param {number} y + // * @param {number} z (Only available when using a Control+ Hub) + // */ + // this.emit("tilt", port.id, this._lastTiltX, this._lastTiltY, this._lastTiltZ); + // break; + // } + // case Consts.DeviceType.BOOST_TACHO_MOTOR: { + // const rotation = data.readInt32LE(4); + // /** + // * Emits when a rotation sensor is activated. + // * @event LPF2Hub#rotate + // * @param {string} port + // * @param {number} rotation + // */ + // this.emit("rotate", port.id, rotation); + // break; + // } + // case Consts.DeviceType.BOOST_MOVE_HUB_MOTOR: { + // const rotation = data.readInt32LE(4); + // this.emit("rotate", port.id, rotation); + // break; + // } + // case Consts.DeviceType.CONTROL_PLUS_LARGE_MOTOR: { + // const rotation = data.readInt32LE(4); + // this.emit("rotate", port.id, rotation); + // break; + // } + // case Consts.DeviceType.CONTROL_PLUS_XLARGE_MOTOR: { + // const rotation = data.readInt32LE(4); + // this.emit("rotate", port.id, rotation); + // break; + // } + // case Consts.DeviceType.CONTROL_PLUS_TILT: { + // const tiltZ = data.readInt16LE(4); + // const tiltY = data.readInt16LE(6); + // const tiltX = data.readInt16LE(8); + // this._lastTiltX = tiltX; + // this._lastTiltY = tiltY; + // this._lastTiltZ = tiltZ; + // this.emit("tilt", "TILT", this._lastTiltX, this._lastTiltY, this._lastTiltZ); + // break; + // } + // case Consts.DeviceType.CONTROL_PLUS_GYRO: { + // const gyroX = Math.round(data.readInt16LE(4) * 7 / 400); + // const gyroY = Math.round(data.readInt16LE(6) * 7 / 400); + // const gyroZ = Math.round(data.readInt16LE(8) * 7 / 400); + // /** + // * Emits when gyroscope detects movement. Measured in DPS - degrees per second. + // * @event LPF2Hub#gyro + // * @param {string} port + // * @param {number} x + // * @param {number} y + // * @param {number} z + // */ + // this.emit("gyro", "GYRO", gyroX, gyroY, gyroZ); + // break; + // } + // case Consts.DeviceType.CONTROL_PLUS_ACCELEROMETER: { + // const accelX = Math.round(data.readInt16LE(4) / 4.096); + // const accelY = Math.round(data.readInt16LE(6) / 4.096); + // const accelZ = Math.round(data.readInt16LE(8) / 4.096); + // /** + // * Emits when accelerometer detects movement. Measured in mG. + // * @event LPF2Hub#accel + // * @param {string} port + // * @param {number} x + // * @param {number} y + // * @param {number} z + // */ + // this.emit("accel", "ACCEL", accelX, accelY, accelZ); + // break; + // } + // case Consts.DeviceType.BOOST_TILT: { + // const tiltX = data.readInt8(4); + // const tiltY = data.readInt8(5); + // this._lastTiltX = tiltX; + // this._lastTiltY = tiltY; + // this.emit("tilt", port.id, this._lastTiltX, this._lastTiltY, this._lastTiltZ); + // break; + // } + // case Consts.DeviceType.POWERED_UP_REMOTE_BUTTON: { + // switch (data[4]) { + // case 0x01: { + // this.emit("button", port.id, Consts.ButtonState.UP); + // break; + // } + // case 0xff: { + // this.emit("button", port.id, Consts.ButtonState.DOWN); + // break; + // } + // case 0x7f: { + // this.emit("button", port.id, Consts.ButtonState.STOP); + // break; + // } + // case 0x00: { + // this.emit("button", port.id, Consts.ButtonState.RELEASED); + // break; + // } + // } + // break; + // } + // case Consts.DeviceType.DUPLO_TRAIN_BASE_COLOR: { + // if (data[4] <= 10) { + // this.emit("color", port.id, data[4]); + // } + // break; + // } + // case Consts.DeviceType.DUPLO_TRAIN_BASE_SPEEDOMETER: { + // /** + // * Emits on a speed change. + // * @event LPF2Hub#speed + // * @param {string} port + // * @param {number} speed + // */ + // const speed = data.readInt16LE(4); + // this.emit("speed", port.id, speed); + // break; + // } + // } + // } } diff --git a/src/puphub.ts b/src/puphub.ts index f718d5e..8902bf9 100644 --- a/src/puphub.ts +++ b/src/puphub.ts @@ -36,14 +36,11 @@ export class PUPHub extends LPF2Hub { constructor (device: IBLEAbstraction, autoSubscribe: boolean = true) { super(device, autoSubscribe); - this.type = Consts.HubType.POWERED_UP_HUB; - this._ports = { - "A": new Port("A", 0), - "B": new Port("B", 1) + this._type = Consts.HubType.POWERED_UP_HUB; + this._portNames = { + "A": 0, + "B": 1 }; - this.on("attach", (port, type) => { - this._combinePorts(port, type); - }); debug("Discovered Powered UP Hub"); } @@ -58,239 +55,6 @@ export class PUPHub extends LPF2Hub { } - /** - * Set the motor speed on a given port. - * @method PUPHub#setMotorSpeed - * @param {string} port - * @param {number | Array.} speed For forward, a value between 1 - 100 should be set. For reverse, a value between -1 to -100. Stop is 0. If you are specifying port AB to control both motors, you can optionally supply a tuple of speeds. - * @param {number} [time] How long to activate the motor for (in milliseconds). Leave empty to turn the motor on indefinitely. - * @returns {Promise} Resolved upon successful completion of command. If time is specified, this is once the motor is finished. - */ - public setMotorSpeed (port: string, speed: number | [number, number], time?: number | boolean) { - const portObj = this._portLookup(port); - if (!this._virtualPorts[portObj.id] && speed instanceof Array) { - throw new Error(`Port ${portObj.id} can only accept a single speed`); - } - let cancelEventTimer = true; - if (typeof time === "boolean") { - if (time === true) { - cancelEventTimer = false; - } - time = undefined; - } - if (cancelEventTimer) { - portObj.cancelEventTimer(); - } - return new Promise((resolve, reject) => { - if (time && typeof time === "number") { - - if ( - portObj.type === Consts.DeviceType.BOOST_TACHO_MOTOR || - portObj.type === Consts.DeviceType.BOOST_MOVE_HUB_MOTOR || - portObj.type === Consts.DeviceType.CONTROL_PLUS_LARGE_MOTOR || - portObj.type === Consts.DeviceType.CONTROL_PLUS_XLARGE_MOTOR - ) { - portObj.busy = true; - let data = null; - if (this._virtualPorts[portObj.id]) { - data = Buffer.from([0x81, portObj.value, 0x11, 0x0a, 0x00, 0x00, this._mapSpeed(speed instanceof Array ? speed[0] : speed), this._mapSpeed(speed instanceof Array ? speed[1] : speed), 0x64, 0x7f, 0x03]); - } else { - // @ts-ignore: The type of speed is properly checked at the start - data = Buffer.from([0x81, portObj.value, 0x11, 0x09, 0x00, 0x00, this._mapSpeed(speed), 0x64, 0x7f, 0x03]); - } - data.writeUInt16LE(time > 65535 ? 65535 : time, 4); - this.send(Consts.BLECharacteristic.LPF2_ALL, data); - portObj.finished = () => { - return resolve(); - }; - } else { - // @ts-ignore: The type of speed is properly checked at the start - const data = Buffer.from([0x81, portObj.value, 0x11, 0x51, 0x00, this._mapSpeed(speed)]); - this.send(Consts.BLECharacteristic.LPF2_ALL, data); - const timeout = global.setTimeout(() => { - const data = Buffer.from([0x81, portObj.value, 0x11, 0x51, 0x00, 0x00]); - this.send(Consts.BLECharacteristic.LPF2_ALL, data); - return resolve(); - // @ts-ignore: The type of time is properly checked at the start - }, time); - portObj.setEventTimer(timeout); - } - - } else { - - if (portObj.type === Consts.DeviceType.BOOST_TACHO_MOTOR || portObj.type === Consts.DeviceType.BOOST_MOVE_HUB_MOTOR) { - portObj.busy = true; - let data = null; - if (this._virtualPorts[portObj.id]) { - data = Buffer.from([0x81, portObj.value, 0x11, 0x02, this._mapSpeed(speed instanceof Array ? speed[0] : speed), this._mapSpeed(speed instanceof Array ? speed[1] : speed), 0x64, 0x7f, 0x03]); - } else { - // @ts-ignore: The type of speed is properly checked at the start - data = Buffer.from([0x81, portObj.value, 0x11, 0x01, this._mapSpeed(speed), 0x64, 0x7f, 0x03]); - } - this.send(Consts.BLECharacteristic.LPF2_ALL, data); - portObj.finished = () => { - return resolve(); - }; - } else { - // @ts-ignore: The type of speed is properly checked at the start - const data = Buffer.from([0x81, portObj.value, 0x11, 0x51, 0x00, this._mapSpeed(speed)]); - this.send(Consts.BLECharacteristic.LPF2_ALL, data); - } - - } - }); - } - - - /** - * Ramp the motor speed on a given port. - * @method PUPHub#rampMotorSpeed - * @param {string} port - * @param {number} fromSpeed For forward, a value between 1 - 100 should be set. For reverse, a value between -1 to -100. Stop is 0. - * @param {number} toSpeed For forward, a value between 1 - 100 should be set. For reverse, a value between -1 to -100. Stop is 0. - * @param {number} time How long the ramp should last (in milliseconds). - * @returns {Promise} Resolved upon successful completion of command. - */ - public rampMotorSpeed (port: string, fromSpeed: number, toSpeed: number, time: number) { - const portObj = this._portLookup(port); - portObj.cancelEventTimer(); - return new Promise((resolve, reject) => { - this._calculateRamp(fromSpeed, toSpeed, time, portObj) - .on("changeSpeed", (speed) => { - this.setMotorSpeed(port, speed, true); - }) - .on("finished", resolve); - }); - } - - - /** - * Rotate a motor by a given angle. - * @method PUPHub#setMotorAngle - * @param {string} port - * @param {number} angle How much the motor should be rotated (in degrees). - * @param {number | Array.} [speed=100] For forward, a value between 1 - 100 should be set. For reverse, a value between -1 to -100. Stop is 0. If you are specifying port AB to control both motors, you can optionally supply a tuple of speeds. - * @returns {Promise} Resolved upon successful completion of command (ie. once the motor is finished). - */ - public setMotorAngle (port: string, angle: number, speed: number | [number, number] = 100) { - const portObj = this._portLookup(port); - if (!( - portObj.type === Consts.DeviceType.BOOST_TACHO_MOTOR || - portObj.type === Consts.DeviceType.BOOST_MOVE_HUB_MOTOR || - portObj.type === Consts.DeviceType.CONTROL_PLUS_LARGE_MOTOR || - portObj.type === Consts.DeviceType.CONTROL_PLUS_XLARGE_MOTOR - )) { - throw new Error("Angle rotation is only available when using a Boost Tacho Motor, Boost Move Hub Motor, Control+ Medium Motor, or Control+ Large Motor"); - } - if (!this._virtualPorts[portObj.id] && speed instanceof Array) { - throw new Error(`Port ${portObj.id} can only accept a single speed`); - } - portObj.cancelEventTimer(); - return new Promise((resolve, reject) => { - portObj.busy = true; - let data = null; - if (this._virtualPorts[portObj.id]) { - data = Buffer.from([0x81, portObj.value, 0x11, 0x0c, 0x00, 0x00, 0x00, 0x00, this._mapSpeed(speed instanceof Array ? speed[0] : speed), this._mapSpeed(speed instanceof Array ? speed[1] : speed), 0x64, 0x7f, 0x03]); - } else { - // @ts-ignore: The type of speed is properly checked at the start - data = Buffer.from([0x81, portObj.value, 0x11, 0x0b, 0x00, 0x00, 0x00, 0x00, this._mapSpeed(speed), 0x64, 0x7f, 0x03]); - } - data.writeUInt32LE(angle, 4); - this.send(Consts.BLECharacteristic.LPF2_ALL, data); - portObj.finished = () => { - return resolve(); - }; - }); - } - - - /** - * Tell motor to goto an absolute position - * @method PUPHub#setAbsolutePosition - * @param {string} port - * @param {number} pos The position of the motor to go to - * @param {number | Array.} [speed=100] A value between 1 - 100 should be set (Direction does not apply when going to absolute position) - * @returns {Promise} Resolved upon successful completion of command (ie. once the motor is finished). - */ - public setAbsolutePosition (port: string, pos: number, speed: number = 100) { - const portObj = this._portLookup(port); - if (!( - portObj.type === Consts.DeviceType.CONTROL_PLUS_LARGE_MOTOR || - portObj.type === Consts.DeviceType.CONTROL_PLUS_XLARGE_MOTOR - )) { - throw new Error("Absolute positioning is only available when using a Control+ Medium Motor, or Control+ Large Motor"); - } - portObj.cancelEventTimer(); - return new Promise((resolve, reject) => { - portObj.busy = true; - let data = null; - if (this._virtualPorts[portObj.id]) { - data = Buffer.from([0x81, portObj.value, 0x11, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, this._mapSpeed(speed), 0x64, 0x7f, 0x03]); - data.writeInt32LE(pos, 4); - data.writeInt32LE(pos, 8); - } else { - // @ts-ignore: The type of speed is properly checked at the start - data = Buffer.from([0x81, portObj.value, 0x11, 0x0d, 0x00, 0x00, 0x00, 0x00, this._mapSpeed(speed), 0x64, 0x7f, 0x03]); - data.writeInt32LE(pos, 4); - } - this.send(Consts.BLECharacteristic.LPF2_ALL, data); - portObj.finished = () => { - return resolve(); - }; - }); - } - - - /** - * Reset the current motor position as absolute position zero - * @method PUPHub#resetAbsolutePosition - * @param {string} port - * @returns {Promise} Resolved upon successful completion of command (ie. once the motor is finished). - */ - public resetAbsolutePosition (port: string) { - const portObj = this._portLookup(port); - if (!( - portObj.type === Consts.DeviceType.CONTROL_PLUS_LARGE_MOTOR || - portObj.type === Consts.DeviceType.CONTROL_PLUS_XLARGE_MOTOR - )) { - throw new Error("Absolute positioning is only available when using a Control+ Medium Motor, or Control+ Large Motor"); - } - return new Promise((resolve) => { - const data = Buffer.from([0x81, portObj.value, 0x11, 0x51, 0x02, 0x00, 0x00, 0x00, 0x00]); - this.send(Consts.BLECharacteristic.LPF2_ALL, data); - return resolve(); - }); - } - - - /** - * Set the light brightness on a given port. - * @method PUPHub#setLightBrightness - * @param {string} port - * @param {number} brightness Brightness value between 0-100 (0 is off) - * @param {number} [time] How long to turn the light on (in milliseconds). Leave empty to turn the light on indefinitely. - * @returns {Promise} Resolved upon successful completion of command. If time is specified, this is once the light is turned off. - */ - public setLightBrightness (port: string, brightness: number, time?: number) { - const portObj = this._portLookup(port); - portObj.cancelEventTimer(); - return new Promise((resolve, reject) => { - const data = Buffer.from([0x81, portObj.value, 0x11, 0x51, 0x00, brightness]); - this.send(Consts.BLECharacteristic.LPF2_ALL, data); - if (time) { - const timeout = global.setTimeout(() => { - const data = Buffer.from([0x81, portObj.value, 0x11, 0x51, 0x00, 0x00]); - this.send(Consts.BLECharacteristic.LPF2_ALL, data); - return resolve(); - }, time); - portObj.setEventTimer(timeout); - } else { - return resolve(); - } - }); - } - - protected _checkFirmware (version: string) { if (compareVersion("1.1.00.0004", version) === 1) { throw new Error(`Your Powered Up Hub's (${this.name}) firmware is out of date and unsupported by this library. Please update it via the official Powered Up app.`); diff --git a/src/pupremote.ts b/src/pupremote.ts index c6fcecf..2a25bb1 100644 --- a/src/pupremote.ts +++ b/src/pupremote.ts @@ -39,10 +39,10 @@ export class PUPRemote extends LPF2Hub { constructor (device: IBLEAbstraction, autoSubscribe: boolean = true) { super(device, autoSubscribe); - this.type = Consts.HubType.POWERED_UP_REMOTE; - this._ports = { - "LEFT": new Port("LEFT", 0), - "RIGHT": new Port("RIGHT", 1) + this._type = Consts.HubType.POWERED_UP_REMOTE; + this._portNames = { + "LEFT": 0, + "RIGHT": 1 }; debug("Discovered Powered UP Remote"); } diff --git a/src/utils.ts b/src/utils.ts index 83650aa..e463214 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,9 +1,22 @@ // @ts-ignore export const isWebBluetooth = (typeof navigator !== "undefined" && navigator && navigator.bluetooth); -export function toHex (value: number, length: number = 2) { +export const toHex = (value: number, length: number = 2) => { return value.toString(16).padStart(length, "0"); -} -export function toBin (value: number, length: number = 8) { +}; + +export const toBin = (value: number, length: number = 8) => { return value.toString(2).padStart(length, "0"); -} +}; + +export const mapSpeed = (speed: number) => { + if (speed === 127) { + return 127; + } + if (speed > 100) { + speed = 100; + } else if (speed < -100) { + speed = -100; + } + return speed; +}; diff --git a/src/wedo2smarthub.ts b/src/wedo2smarthub.ts index e08a99a..4142e36 100644 --- a/src/wedo2smarthub.ts +++ b/src/wedo2smarthub.ts @@ -38,10 +38,10 @@ export class WeDo2SmartHub extends Hub { constructor (device: IBLEAbstraction, autoSubscribe: boolean = true) { super(device, autoSubscribe); - this.type = Consts.HubType.WEDO2_SMART_HUB; - this._ports = { - "A": new Port("A", 1), - "B": new Port("B", 2) + this._type = Consts.HubType.WEDO2_SMART_HUB; + this._portNames = { + "A": 1, + "B": 2 }; debug("Discovered WeDo 2.0 Smart Hub"); } @@ -115,8 +115,8 @@ export class WeDo2SmartHub extends Hub { return new Promise((resolve, reject) => { const data = Buffer.from(name, "ascii"); // Send this twice, as sometimes the first time doesn't take - this.send(Consts.BLECharacteristic.WEDO2_NAME_ID, data); - this.send(Consts.BLECharacteristic.WEDO2_NAME_ID, data); + this.send(data, Consts.BLECharacteristic.WEDO2_NAME_ID); + this.send(data, Consts.BLECharacteristic.WEDO2_NAME_ID); this._name = name; return resolve(); }); @@ -132,12 +132,12 @@ export class WeDo2SmartHub extends Hub { public setLEDColor (color: number | boolean) { return new Promise((resolve, reject) => { let data = Buffer.from([0x06, 0x17, 0x01, 0x01]); - this.send(Consts.BLECharacteristic.WEDO2_PORT_TYPE_WRITE, data); + this.send(data, Consts.BLECharacteristic.WEDO2_PORT_TYPE_WRITE); if (typeof color === "boolean") { color = 0; } data = Buffer.from([0x06, 0x04, 0x01, color]); - this.send(Consts.BLECharacteristic.WEDO2_MOTOR_VALUE_WRITE, data); + this.send(data, Consts.BLECharacteristic.WEDO2_MOTOR_VALUE_WRITE); return resolve(); }); } @@ -150,7 +150,7 @@ export class WeDo2SmartHub extends Hub { */ public shutdown () { return new Promise((resolve, reject) => { - this.send(Consts.BLECharacteristic.WEDO2_DISCONNECT, Buffer.from([0x00]), () => { + this.send(Buffer.from([0x00]), Consts.BLECharacteristic.WEDO2_DISCONNECT, () => { return resolve(); }); }); @@ -168,80 +168,80 @@ export class WeDo2SmartHub extends Hub { public setLEDRGB (red: number, green: number, blue: number) { return new Promise((resolve, reject) => { let data = Buffer.from([0x06, 0x17, 0x01, 0x02]); - this.send(Consts.BLECharacteristic.WEDO2_PORT_TYPE_WRITE, data); + this.send(data, Consts.BLECharacteristic.WEDO2_PORT_TYPE_WRITE); data = Buffer.from([0x06, 0x04, 0x03, red, green, blue]); - this.send(Consts.BLECharacteristic.WEDO2_MOTOR_VALUE_WRITE, data); + this.send(data, Consts.BLECharacteristic.WEDO2_MOTOR_VALUE_WRITE); return resolve(); }); } - /** - * Set the motor speed on a given port. - * @method WeDo2SmartHub#setMotorSpeed - * @param {string} port - * @param {number} speed For forward, a value between 1 - 100 should be set. For reverse, a value between -1 to -100. Stop is 0. - * @param {number} [time] How long to activate the motor for (in milliseconds). Leave empty to turn the motor on indefinitely. - * @returns {Promise} Resolved upon successful completion of command. If time is specified, this is once the motor is finished. - */ - public setMotorSpeed (port: string, speed: number, time?: number | boolean) { - const portObj = this._portLookup(port); - let cancelEventTimer = true; - if (typeof time === "boolean") { - if (time === true) { - cancelEventTimer = false; - } - time = undefined; - } - if (cancelEventTimer) { - portObj.cancelEventTimer(); - } - return new Promise((resolve, reject) => { - this.send(Consts.BLECharacteristic.WEDO2_MOTOR_VALUE_WRITE, Buffer.from([portObj.value, 0x01, 0x02, this._mapSpeed(speed)])); - if (time && typeof time === "number") { - const timeout = global.setTimeout(() => { - this.send(Consts.BLECharacteristic.WEDO2_MOTOR_VALUE_WRITE, Buffer.from([portObj.value, 0x01, 0x02, 0x00])); - return resolve(); - }, time); - portObj.setEventTimer(timeout); - } else { - return resolve(); - } - }); - } + // /** + // * Set the motor speed on a given port. + // * @method WeDo2SmartHub#setMotorSpeed + // * @param {string} port + // * @param {number} speed For forward, a value between 1 - 100 should be set. For reverse, a value between -1 to -100. Stop is 0. + // * @param {number} [time] How long to activate the motor for (in milliseconds). Leave empty to turn the motor on indefinitely. + // * @returns {Promise} Resolved upon successful completion of command. If time is specified, this is once the motor is finished. + // */ + // public setMotorSpeed (port: string, speed: number, time?: number | boolean) { + // const portObj = this._portLookup(port); + // let cancelEventTimer = true; + // if (typeof time === "boolean") { + // if (time === true) { + // cancelEventTimer = false; + // } + // time = undefined; + // } + // if (cancelEventTimer) { + // portObj.cancelEventTimer(); + // } + // return new Promise((resolve, reject) => { + // this.send(Buffer.from([portObj.value, 0x01, 0x02, this._mapSpeed(speed)]), Consts.BLECharacteristic.WEDO2_MOTOR_VALUE_WRITE); + // if (time && typeof time === "number") { + // const timeout = global.setTimeout(() => { + // this.send(Buffer.from([portObj.value, 0x01, 0x02, 0x00]), Consts.BLECharacteristic.WEDO2_MOTOR_VALUE_WRITE); + // return resolve(); + // }, time); + // portObj.setEventTimer(timeout); + // } else { + // return resolve(); + // } + // }); + // } - /** - * Ramp the motor speed on a given port. - * @method WeDo2SmartHub#rampMotorSpeed - * @param {string} port - * @param {number} fromSpeed For forward, a value between 1 - 100 should be set. For reverse, a value between -1 to -100. Stop is 0. - * @param {number} toSpeed For forward, a value between 1 - 100 should be set. For reverse, a value between -1 to -100. Stop is 0. - * @param {number} time How long the ramp should last (in milliseconds). - * @returns {Promise} Resolved upon successful completion of command. - */ - public rampMotorSpeed (port: string, fromSpeed: number, toSpeed: number, time: number) { - const portObj = this._portLookup(port); - portObj.cancelEventTimer(); - return new Promise((resolve, reject) => { - this._calculateRamp(fromSpeed, toSpeed, time, portObj) - .on("changeSpeed", (speed) => { - this.setMotorSpeed(port, speed, true); - }) - .on("finished", resolve); - }); - } + // /** + // * Ramp the motor speed on a given port. + // * @method WeDo2SmartHub#rampMotorSpeed + // * @param {string} port + // * @param {number} fromSpeed For forward, a value between 1 - 100 should be set. For reverse, a value between -1 to -100. Stop is 0. + // * @param {number} toSpeed For forward, a value between 1 - 100 should be set. For reverse, a value between -1 to -100. Stop is 0. + // * @param {number} time How long the ramp should last (in milliseconds). + // * @returns {Promise} Resolved upon successful completion of command. + // */ + // public rampMotorSpeed (port: string, fromSpeed: number, toSpeed: number, time: number) { + // const portObj = this._portLookup(port); + // portObj.cancelEventTimer(); + // return new Promise((resolve, reject) => { + // this._calculateRamp(fromSpeed, toSpeed, time, portObj) + // .on("changeSpeed", (speed) => { + // this.setMotorSpeed(port, speed, true); + // }) + // .on("finished", resolve); + // }); + // } - /** - * Fully (hard) stop the motor on a given port. - * @method WeDo2SmartHub#brakeMotor - * @param {string} port - * @returns {Promise} Resolved upon successful completion of command. - */ - public brakeMotor (port: string) { - return this.setMotorSpeed(port, 127); - } + // /** + // * Fully (hard) stop the motor on a given port. + // * @method WeDo2SmartHub#brakeMotor + // * @param {string} port + // * @returns {Promise} Resolved upon successful completion of command. + // */ + // public brakeMotor (port: string) { + // return this.setMotorSpeed(port, 127); + // } /** @@ -256,41 +256,41 @@ export class WeDo2SmartHub extends Hub { const data = Buffer.from([0x05, 0x02, 0x04, 0x00, 0x00, 0x00, 0x00]); data.writeUInt16LE(frequency, 3); data.writeUInt16LE(time, 5); - this.send(Consts.BLECharacteristic.WEDO2_MOTOR_VALUE_WRITE, data); + this.send(data, Consts.BLECharacteristic.WEDO2_MOTOR_VALUE_WRITE); global.setTimeout(resolve, time); }); } - /** - * Set the light brightness on a given port. - * @method WeDo2SmartHub#setLightBrightness - * @param {string} port - * @param {number} brightness Brightness value between 0-100 (0 is off) - * @param {number} [time] How long to turn the light on (in milliseconds). Leave empty to turn the light on indefinitely. - * @returns {Promise} Resolved upon successful completion of command. If time is specified, this is once the light is turned off. - */ - public setLightBrightness (port: string, brightness: number, time?: number) { - const portObj = this._portLookup(port); - portObj.cancelEventTimer(); - return new Promise((resolve, reject) => { - const data = Buffer.from([portObj.value, 0x01, 0x02, brightness]); - this.send(Consts.BLECharacteristic.WEDO2_MOTOR_VALUE_WRITE, data); - if (time) { - const timeout = global.setTimeout(() => { - const data = Buffer.from([portObj.value, 0x01, 0x02, 0x00]); - this.send(Consts.BLECharacteristic.WEDO2_MOTOR_VALUE_WRITE, data); - return resolve(); - }, time); - portObj.setEventTimer(timeout); - } else { - return resolve(); - } - }); - } + // /** + // * Set the light brightness on a given port. + // * @method WeDo2SmartHub#setLightBrightness + // * @param {string} port + // * @param {number} brightness Brightness value between 0-100 (0 is off) + // * @param {number} [time] How long to turn the light on (in milliseconds). Leave empty to turn the light on indefinitely. + // * @returns {Promise} Resolved upon successful completion of command. If time is specified, this is once the light is turned off. + // */ + // public setLightBrightness (port: string, brightness: number, time?: number) { + // const portObj = this._portLookup(port); + // portObj.cancelEventTimer(); + // return new Promise((resolve, reject) => { + // const data = Buffer.from([portObj.value, 0x01, 0x02, brightness]); + // this.send(data, Consts.BLECharacteristic.WEDO2_MOTOR_VALUE_WRITE); + // if (time) { + // const timeout = global.setTimeout(() => { + // const data = Buffer.from([portObj.value, 0x01, 0x02, 0x00]); + // this.send(data, Consts.BLECharacteristic.WEDO2_MOTOR_VALUE_WRITE); + // return resolve(); + // }, time); + // portObj.setEventTimer(timeout); + // } else { + // return resolve(); + // } + // }); + // } - public send (uuid: string, message: Buffer, callback?: () => void) { + public send (message: Buffer, uuid: string, callback?: () => void) { if (debug.enabled) { debug(`Sent Message (${this._getCharacteristicNameFromUUID(uuid)})`, message); } @@ -299,12 +299,12 @@ export class WeDo2SmartHub extends Hub { protected _activatePortDevice (port: number, type: number, mode: number, format: number, callback?: () => void) { - this.send(Consts.BLECharacteristic.WEDO2_PORT_TYPE_WRITE, Buffer.from([0x01, 0x02, port, type, mode, 0x01, 0x00, 0x00, 0x00, format, 0x01]), callback); + this.send(Buffer.from([0x01, 0x02, port, type, mode, 0x01, 0x00, 0x00, 0x00, format, 0x01]), Consts.BLECharacteristic.WEDO2_PORT_TYPE_WRITE, callback); } protected _deactivatePortDevice (port: number, type: number, mode: number, format: number, callback?: () => void) { - this.send(Consts.BLECharacteristic.WEDO2_PORT_TYPE_WRITE, Buffer.from([0x01, 0x02, port, type, mode, 0x01, 0x00, 0x00, 0x00, format, 0x00]), callback); + this.send(Buffer.from([0x01, 0x02, port, type, mode, 0x01, 0x00, 0x00, 0x00, format, 0x00]), Consts.BLECharacteristic.WEDO2_PORT_TYPE_WRITE, callback); } @@ -361,7 +361,7 @@ export class WeDo2SmartHub extends Hub { break; } - this._registerDeviceAttachment(device); + this._attachDevice(device); } @@ -379,101 +379,101 @@ export class WeDo2SmartHub extends Hub { private _parseSensorMessage (data: Buffer) { - debug("Received Message (WEDO2_SENSOR_VALUE)", data); + // debug("Received Message (WEDO2_SENSOR_VALUE)", data); - if (data[0] === 0x01) { - /** - * Emits when a button is pressed. - * @event WeDo2SmartHub#button - * @param {string} button - * @param {ButtonState} state - */ - this.emit("button", "GREEN", Consts.ButtonState.PRESSED); - return; - } else if (data[0] === 0x00) { - this.emit("button", "GREEN", Consts.ButtonState.RELEASED); - return; - } + // if (data[0] === 0x01) { + // /** + // * Emits when a button is pressed. + // * @event WeDo2SmartHub#button + // * @param {string} button + // * @param {ButtonState} state + // */ + // this.emit("button", "GREEN", Consts.ButtonState.PRESSED); + // return; + // } else if (data[0] === 0x00) { + // this.emit("button", "GREEN", Consts.ButtonState.RELEASED); + // return; + // } - // Voltage - if (data[1] === 0x03) { - const voltage = data.readInt16LE(2); - this._voltage = voltage / 40; - // Current - } else if (data[1] === 0x04) { - const current = data.readInt16LE(2); - this._current = current / 1000; - } + // // Voltage + // if (data[1] === 0x03) { + // const voltage = data.readInt16LE(2); + // this._voltage = voltage / 40; + // // Current + // } else if (data[1] === 0x04) { + // const current = data.readInt16LE(2); + // this._current = current / 1000; + // } - const port = this._getPortForPortNumber(data[1]); + // const port = this._getPortForPortNumber(data[1]); - if (!port) { - return; - } + // if (!port) { + // return; + // } - if (port && port.connected) { - switch (port.type) { - case Consts.DeviceType.WEDO2_DISTANCE: { - let distance = data[2]; - if (data[3] === 1) { - distance = data[2] + 255; - } - /** - * Emits when a distance sensor is activated. - * @event WeDo2SmartHub#distance - * @param {string} port - * @param {number} distance Distance, in millimeters. - */ - this.emit("distance", port.id, distance * 10); - break; - } - case Consts.DeviceType.COLOR_DISTANCE_SENSOR: { - const distance = data[2]; - /** - * Emits when a color sensor is activated. - * @event WeDo2SmartHub#color - * @param {string} port - * @param {Color} color - */ - this.emit("color", port.id, distance); - break; - } - case Consts.DeviceType.WEDO2_TILT: { - this._lastTiltX = data.readInt8(2); - this._lastTiltY = data.readInt8(3); - /** - * Emits when a tilt sensor is activated. - * @event WeDo2SmartHub#tilt - * @param {string} port - * @param {number} x - * @param {number} y - */ - this.emit("tilt", port.id, this._lastTiltX, this._lastTiltY); - break; - } - case Consts.DeviceType.BOOST_TACHO_MOTOR: { - const rotation = data.readInt32LE(2); - /** - * Emits when a rotation sensor is activated. - * @event WeDo2SmartHub#rotate - * @param {string} port - * @param {number} rotation - */ - this.emit("rotate", port.id, rotation); - break; - } - case Consts.DeviceType.CONTROL_PLUS_LARGE_MOTOR: { - const rotation = data.readInt32LE(2); - this.emit("rotate", port.id, rotation); - break; - } - case Consts.DeviceType.CONTROL_PLUS_XLARGE_MOTOR: { - const rotation = data.readInt32LE(2); - this.emit("rotate", port.id, rotation); - break; - } - } - } + // if (port && port.connected) { + // switch (port.type) { + // case Consts.DeviceType.WEDO2_DISTANCE: { + // let distance = data[2]; + // if (data[3] === 1) { + // distance = data[2] + 255; + // } + // /** + // * Emits when a distance sensor is activated. + // * @event WeDo2SmartHub#distance + // * @param {string} port + // * @param {number} distance Distance, in millimeters. + // */ + // this.emit("distance", port.id, distance * 10); + // break; + // } + // case Consts.DeviceType.COLOR_DISTANCE_SENSOR: { + // const distance = data[2]; + // /** + // * Emits when a color sensor is activated. + // * @event WeDo2SmartHub#color + // * @param {string} port + // * @param {Color} color + // */ + // this.emit("color", port.id, distance); + // break; + // } + // case Consts.DeviceType.WEDO2_TILT: { + // this._lastTiltX = data.readInt8(2); + // this._lastTiltY = data.readInt8(3); + // /** + // * Emits when a tilt sensor is activated. + // * @event WeDo2SmartHub#tilt + // * @param {string} port + // * @param {number} x + // * @param {number} y + // */ + // this.emit("tilt", port.id, this._lastTiltX, this._lastTiltY); + // break; + // } + // case Consts.DeviceType.BOOST_TACHO_MOTOR: { + // const rotation = data.readInt32LE(2); + // /** + // * Emits when a rotation sensor is activated. + // * @event WeDo2SmartHub#rotate + // * @param {string} port + // * @param {number} rotation + // */ + // this.emit("rotate", port.id, rotation); + // break; + // } + // case Consts.DeviceType.CONTROL_PLUS_LARGE_MOTOR: { + // const rotation = data.readInt32LE(2); + // this.emit("rotate", port.id, rotation); + // break; + // } + // case Consts.DeviceType.CONTROL_PLUS_XLARGE_MOTOR: { + // const rotation = data.readInt32LE(2); + // this.emit("rotate", port.id, rotation); + // break; + // } + // } + // } }