From 5fde49c0c20f31fbaae16f01be68cd0adc31c8d6 Mon Sep 17 00:00:00 2001 From: Nathan Kellenicki Date: Mon, 13 Jan 2020 10:58:57 -0800 Subject: [PATCH] Adding power and brightness ramping back in --- src/devices/absolutemotor.ts | 4 ++++ src/devices/basicmotor.ts | 29 +++++++++++++++++++++++++++-- src/devices/device.ts | 12 ++++++++++++ src/devices/light.ts | 28 ++++++++++++++++++++++++++-- src/devices/tachomotor.ts | 2 ++ src/utils.ts | 33 +++++++++++++++++++++++++++++++++ 6 files changed, 104 insertions(+), 4 deletions(-) diff --git a/src/devices/absolutemotor.ts b/src/devices/absolutemotor.ts index 54e2f53..817e7f5 100644 --- a/src/devices/absolutemotor.ts +++ b/src/devices/absolutemotor.ts @@ -44,6 +44,7 @@ export class AbsoluteMotor extends TachoMotor { if (this.isWeDo2SmartHub) { throw new Error("Absolute positioning is not available on the WeDo 2.0 Smart Hub"); } + this.cancelEventTimer(); return new Promise((resolve) => { this._busy = true; if (speed === undefined || speed === null) { @@ -78,6 +79,7 @@ export class AbsoluteMotor extends TachoMotor { if (this.isWeDo2SmartHub) { throw new Error("Absolute positioning is not available on the WeDo 2.0 Smart Hub"); } + this.cancelEventTimer(); return new Promise((resolve) => { this._busy = true; let message; @@ -97,6 +99,7 @@ export class AbsoluteMotor extends TachoMotor { } public async calibrateServo () { + this.cancelEventTimer(); const oldMode = this.mode; let currentAngle = 0; const listener = ({ angle }: { angle: number }) => { @@ -142,6 +145,7 @@ export class AbsoluteMotor extends TachoMotor { } public async resetServo (angle: number) { + this.cancelEventTimer(); const oldMode = this.mode; let currentAngle = 0; const listener = ({ angle }: { angle: number }) => { diff --git a/src/devices/basicmotor.ts b/src/devices/basicmotor.ts index c72151d..234d472 100644 --- a/src/devices/basicmotor.ts +++ b/src/devices/basicmotor.ts @@ -4,7 +4,7 @@ import { IDeviceInterface } from "../interfaces"; import * as Consts from "../consts"; -import { mapSpeed } from "../utils"; +import { calculateRamp, mapSpeed } from "../utils"; export class BasicMotor extends Device { @@ -20,7 +20,10 @@ export class BasicMotor extends Device { * @param {number} power For forward, a value between 1 - 100 should be set. For reverse, a value between -1 to -100. Stop is 0. * @returns {Promise} Resolved upon successful completion of command. */ - public setPower (power: number) { + public setPower (power: number, interrupt: boolean = true) { + if (interrupt) { + this.cancelEventTimer(); + } return new Promise((resolve) => { this.writeDirect(0x00, Buffer.from([mapSpeed(power)])); return resolve(); @@ -28,12 +31,33 @@ export class BasicMotor extends Device { } + /** + * Ramp the motor power. + * @method BasicMotor#rampPower + * @param {number} fromPower For forward, a value between 1 - 100 should be set. For reverse, a value between -1 to -100. Stop is 0. + * @param {number} toPower 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 rampPower (fromPower: number, toPower: number, time: number) { + this.cancelEventTimer(); + return new Promise((resolve) => { + calculateRamp(this, fromPower, toPower, time) + .on("changePower", (power) => { + this.setPower(power, false); + }) + .on("finished", resolve); + }); + } + + /** * Stop the motor. * @method BasicMotor#stop * @returns {Promise} Resolved upon successful completion of command. */ public stop () { + this.cancelEventTimer(); return this.setPower(0); } @@ -44,6 +68,7 @@ export class BasicMotor extends Device { * @returns {Promise} Resolved upon successful completion of command. */ public brake () { + this.cancelEventTimer(); return this.setPower(127); } diff --git a/src/devices/device.ts b/src/devices/device.ts index fc34d28..36d659c 100644 --- a/src/devices/device.ts +++ b/src/devices/device.ts @@ -20,6 +20,7 @@ export class Device extends EventEmitter { private _isWeDo2SmartHub: boolean; private _isVirtualPort: boolean = false; + private _eventTimer: NodeJS.Timer | null = null; constructor (hub: IDeviceInterface, portId: number, modeMap: {[event: string]: number} = {}, type: Consts.DeviceType = Consts.DeviceType.UNKNOWN) { super(); @@ -136,6 +137,17 @@ export class Device extends EventEmitter { } } + public setEventTimer (timer: NodeJS.Timer) { + this._eventTimer = timer; + } + + public cancelEventTimer () { + if (this._eventTimer) { + clearTimeout(this._eventTimer); + this._eventTimer = null; + } + } + private _ensureConnected () { if (!this.connected) { throw new Error("Device is not connected"); diff --git a/src/devices/light.ts b/src/devices/light.ts index 8d2ae6b..4f2079b 100644 --- a/src/devices/light.ts +++ b/src/devices/light.ts @@ -3,6 +3,7 @@ import { Device } from "./device"; import { IDeviceInterface } from "../interfaces"; import * as Consts from "../consts"; +import { calculateRamp } from "../utils"; export class Light extends Device { @@ -14,11 +15,14 @@ export class Light extends Device { /** * Set the light brightness. - * @method Light#brightness + * @method Light#setBrightness * @param {number} brightness Brightness value between 0-100 (0 is off) * @returns {Promise} Resolved upon successful completion of command. */ - public setBrightness (brightness: number) { + public setBrightness (brightness: number, interrupt: boolean = true) { + if (interrupt) { + this.cancelEventTimer(); + } return new Promise((resolve) => { this.writeDirect(0x00, Buffer.from([brightness])); return resolve(); @@ -26,4 +30,24 @@ export class Light extends Device { } + /** + * Ramp the light brightness. + * @method Light#rampBrightness + * @param {number} fromBrightness Brightness value between 0-100 (0 is off) + * @param {number} toBrightness Brightness value between 0-100 (0 is off) + * @param {number} time How long the ramp should last (in milliseconds). + * @returns {Promise} Resolved upon successful completion of command. + */ + public rampBrightness (fromBrightness: number, toBrightness: number, time: number) { + this.cancelEventTimer(); + return new Promise((resolve) => { + calculateRamp(this, fromBrightness, toBrightness, time) + .on("changePower", (power) => { + this.setBrightness(power, false); + }) + .on("finished", resolve); + }); + } + + } diff --git a/src/devices/tachomotor.ts b/src/devices/tachomotor.ts index f7b192d..9e2f9f4 100644 --- a/src/devices/tachomotor.ts +++ b/src/devices/tachomotor.ts @@ -40,6 +40,7 @@ export class TachoMotor extends BasicMotor { if (this.isWeDo2SmartHub) { throw new Error("Motor speed is not available on the WeDo 2.0 Smart Hub"); } + this.cancelEventTimer(); return new Promise((resolve) => { this._busy = true; if (speed === undefined || speed === null) { @@ -81,6 +82,7 @@ export class TachoMotor extends BasicMotor { if (this.isWeDo2SmartHub) { throw new Error("Rotation is not available on the WeDo 2.0 Smart Hub"); } + this.cancelEventTimer(); return new Promise((resolve) => { this._busy = true; if (speed === undefined || speed === null) { diff --git a/src/utils.ts b/src/utils.ts index 590d998..36710de 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,3 +1,6 @@ +import { EventEmitter } from "events"; +import { Device } from "./devices/device"; + // @ts-ignore export const isWebBluetooth = (typeof navigator !== "undefined" && navigator && navigator.bluetooth); @@ -55,3 +58,33 @@ export const roundAngleToNearest90 = (angle: number) => { } return -180; }; + +export const calculateRamp = (device: Device, fromPower: number, toPower: number, time: number) => { + const emitter = new EventEmitter(); + const steps = Math.abs(toPower - fromPower); + let delay = time / steps; + let increment = 1; + if (delay < 50 && steps > 0) { + increment = 50 / delay; + delay = 50; + } + if (fromPower > toPower) { + increment = -increment; + } + let i = 0; + const interval = setInterval(() => { + let power = Math.round(fromPower + (++i * increment)); + if (toPower > fromPower && power > toPower) { + power = toPower; + } else if (fromPower > toPower && power < toPower) { + power = toPower; + } + emitter.emit("changePower", power); + if (power === toPower) { + clearInterval(interval); + emitter.emit("finished"); + } + }, delay); + device.setEventTimer(interval); + return emitter; +};