Added autosubscribe when adding eventlisteners

This commit is contained in:
Nathan Kellenicki 2019-12-06 17:31:12 -08:00
parent d453fe52fb
commit 4af1d3d69b
13 changed files with 1194 additions and 1403 deletions

View File

@ -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");
})
});

View File

@ -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();
});

View File

@ -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.<number>} 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.<number>} 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.<number>} [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.<number>} [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.<number>} [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.<number>} [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) {

View File

@ -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;
}
});
}
}

View File

@ -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.<number>} [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.<number>} [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.<number>} [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.<number>} [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();
// }
// });
// }
}

View File

@ -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]));
}
}

View File

@ -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.<number>} 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();
});
}

View File

@ -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;
// }
// }
}

View File

@ -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;
// }
// }
// }
}

View File

@ -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.<number>} 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.<number>} [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.<number>} [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.`);

View File

@ -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");
}

View File

@ -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;
};

View File

@ -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;
// }
// }
// }
}