Implemented basic lights, device busy-ness

This commit is contained in:
Nathan Kellenicki 2019-12-08 22:57:44 -08:00
parent d382c3d9f2
commit a02249f696
13 changed files with 189 additions and 60 deletions

View File

@ -16,22 +16,36 @@ poweredUP.on("discover", async (hub) => { // Wait to discover hubs
await hub.connect(); // Connect to hub await hub.connect(); // Connect to hub
console.log(`Connected to ${hub.name}!`); console.log(`Connected to ${hub.name}!`);
hub.on("attach", (device) => { hub.on("attach", async (device) => {
console.log(`Attached device ${device.type} to ${device.port}`)
if (device instanceof PoweredUP.ControlPlusLargeMotor) { if (device instanceof PoweredUP.ControlPlusLargeMotor) {
const motor = device; const motor = device;
motor.setSpeed(30);
motor.on("rotate", (angle) => {
console.log(`Rotate ${angle}`);
});
await motor.rotateByAngle(9000, 50);
await motor.rotateByAngle(9000, -50);
await motor.rotateByAngle(9000, 50);
await motor.rotateByAngle(9000, -50);
motor.power(100);
} }
if (device instanceof PoweredUP.ColorDistanceSensor) { if (device instanceof PoweredUP.ColorDistanceSensor) {
const sensor = device; const sensor = device;
sensor.on("distance", (distance) => { // Adding an event handler for distance automatically subscribes to distance notifications sensor.on("distance", (distance) => { // Adding an event handler for distance automatically subscribes to distance notifications
console.log(`Distance ${distance}`); console.log(`Distance ${distance}`);
}) });
sensor.on("color", (color) => {
console.log(`Color ${color}`);
});
} }
device.on("detach", () => { device.on("detach", () => {
console.log(device.connected); console.log(`Detached device ${device.type} from ${device.port}`)
}) })
}); });

View File

@ -0,0 +1,60 @@
/*
*
* This runs the train under our Christmas tree. It uses the 10254 Winter Holiday Train retrofitted with a Powered UP hub and train motor.
* It also uses a WeDo 2.0 hub with Powered UP distance sensor to detect the train approaching the station and slow it down.
*
* Note that if you want to use this yourself you don't need to use a WeDo 2.0 hub, you can use any hub that can accept a distance or color/distance sensor.
*
* The maximum speed of the train is set to a constant 50. A further improvement can be made by scaling the speed up according to the current battery voltage,
* so the speed doesn't slow as the battery starts dying.
*
*/
const PoweredUP = require("..");
const poweredUP = new PoweredUP.PoweredUP();
poweredUP.scan(); // Start scanning for hubs
console.log("Looking for Hubs...");
let train = null;
let sensor = null;
let ramping = false;
poweredUP.on("discover", async (hub) => { // Wait to discover hubs
if (hub.name === "NK_Winter_Train") {
await hub.connect(); // Connect to hub
console.log(`Connected to train!`);
train = hub;
} else if (hub.name === "NK_Winter_Sensor") {
await hub.connect(); // Connect to hub
console.log(`Connected to sensor!`);
sensor = hub;
sensor.on("distance", (_, distance) => {
if (distance < 5 && !ramping) {
await stopTrain();
}
});
}
if (train && sensor) {
console.log("Train and sensor connected, starting!");
await startTrain();
}
});
const startTrain = async () => {
ramping = true;
await train.rampMotorSpeed("A", 0, 50, 2000);
ramping = false;
}
const stopTrain = async () => {
ramping = true;
await train.rampMotorSpeed("A", 50, 0, 2000);
}

View File

@ -15,13 +15,13 @@ export class BasicMotor extends Device {
/** /**
* Set the motor speed. * Set the motor speed.
* @method BasicMotor#setSpeed * @method BasicMotor#power
* @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} power For forward, a value between 1 - 100 should be set. For reverse, a value between -1 to -100. Stop is 0.
* @returns {Promise} Resolved upon successful completion of command. * @returns {Promise} Resolved upon successful completion of command.
*/ */
public setSpeed (speed: number) { public power (power: number) {
return new Promise((resolve) => { return new Promise((resolve) => {
const data = Buffer.from([0x81, this.portId, 0x11, 0x51, 0x00, mapSpeed(speed)]); const data = Buffer.from([0x81, this.portId, 0x11, 0x51, 0x00, mapSpeed(power)]);
this.send(data); this.send(data);
return resolve(); return resolve();
}); });
@ -34,7 +34,7 @@ export class BasicMotor extends Device {
* @returns {Promise} Resolved upon successful completion of command. * @returns {Promise} Resolved upon successful completion of command.
*/ */
public brake () { public brake () {
return this.setSpeed(127); return this.power(127);
} }

View File

@ -1,13 +1,13 @@
import { Peripheral } from "@abandonware/noble"; import { Peripheral } from "@abandonware/noble";
import compareVersion from "compare-versions"; import compareVersion from "compare-versions";
import { IBLEAbstraction } from "./interfaces";
import { LPF2Hub } from "./lpf2hub"; import { LPF2Hub } from "./lpf2hub";
import { Port } from "./port";
import * as Consts from "./consts"; import * as Consts from "./consts";
import Debug = require("debug"); import Debug = require("debug");
import { IBLEAbstraction } from "./interfaces";
const debug = Debug("boostmovehub"); const debug = Debug("boostmovehub");

View File

@ -1,12 +1,12 @@
import { Peripheral } from "@abandonware/noble"; import { Peripheral } from "@abandonware/noble";
import { IBLEAbstraction } from "./interfaces";
import { LPF2Hub } from "./lpf2hub"; import { LPF2Hub } from "./lpf2hub";
import { Port } from "./port";
import * as Consts from "./consts"; import * as Consts from "./consts";
import Debug = require("debug"); import Debug = require("debug");
import { IBLEAbstraction } from "./interfaces";
const debug = Debug("ControlPlusHub"); const debug = Debug("ControlPlusHub");

View File

@ -8,13 +8,13 @@ export class Device extends EventEmitter {
public autoSubscribe: boolean = true; public autoSubscribe: boolean = true;
protected _mode: number = 0x00; protected _mode: number = 0x00;
protected _busy: boolean = false;
protected _finished: (() => void) | undefined;
private _hub: Hub; private _hub: Hub;
private _portId: number; private _portId: number;
private _connected: boolean = true; private _connected: boolean = true;
private _type: number; private _type: number;
private _busy: boolean = false;
private _finished: (() => void) | null = null;
constructor (hub: Hub, portId: number, type: number = Consts.DeviceType.UNKNOWN) { constructor (hub: Hub, portId: number, type: number = Consts.DeviceType.UNKNOWN) {
super(); super();
@ -69,4 +69,12 @@ export class Device extends EventEmitter {
this.emit("receive", message); this.emit("receive", message);
} }
public finish () {
this._busy = false;
if (this._finished) {
this._finished();
this._finished = undefined;
}
}
} }

29
src/lights.ts Normal file
View File

@ -0,0 +1,29 @@
import { Device } from "./device";
import { Hub } from "./hub";
import * as Consts from "./consts";
export class Lights extends Device {
constructor (hub: Hub, portId: number) {
super(hub, portId, Consts.DeviceType.LED_LIGHTS);
}
/**
* Set the light brightness.
* @method Light#brightness
* @param {number} brightness Brightness value between 0-100 (0 is off)
* @returns {Promise} Resolved upon successful completion of command.
*/
public brightness (brightness: number) {
return new Promise((resolve) => {
const data = Buffer.from([0x81, this.portId, 0x11, 0x51, 0x00, brightness]);
this.send(data);
return resolve();
});
}
}

View File

@ -1,12 +1,13 @@
import { Device } from "./device"; import { Device } from "./device";
import { Hub } from "./hub"; import { Hub } from "./hub";
import { Port } from "./port";
import { ColorDistanceSensor } from "./colordistancesensor"; import { ColorDistanceSensor } from "./colordistancesensor";
import { ControlPlusLargeMotor } from "./controlpluslargemotor"; import { ControlPlusLargeMotor } from "./controlpluslargemotor";
import { Lights } from "./lights";
import * as Consts from "./consts"; import * as Consts from "./consts";
import { toBin, toHex } from "./utils";
import { decodeMACAddress, decodeVersion, toBin, toHex } from "./utils";
import Debug = require("debug"); import Debug = require("debug");
const debug = Debug("lpf2hub"); const debug = Debug("lpf2hub");
@ -18,14 +19,6 @@ const modeInfoDebug = Debug("lpf2hubmodeinfo");
* @extends Hub * @extends Hub
*/ */
export class LPF2Hub extends Hub { export class LPF2Hub extends Hub {
private static decodeVersion(v: number) {
const t = v.toString(16).padStart(8, "0");
return [t[0], t[1], t.substring(2, 4), t.substring(4)].join(".");
}
private static decodeMACAddress(v: Uint8Array) {
return Array.from(v).map((n) => toHex(n, 2)).join(":");
}
protected _ledPort: number = 0x32; protected _ledPort: number = 0x32;
protected _voltagePort: number | undefined; protected _voltagePort: number | undefined;
@ -265,12 +258,12 @@ export class LPF2Hub extends Hub {
// Firmware version // Firmware version
} else if (data[3] === 0x03) { } else if (data[3] === 0x03) {
this._firmwareVersion = LPF2Hub.decodeVersion(data.readInt32LE(5)); this._firmwareVersion = decodeVersion(data.readInt32LE(5));
this._checkFirmware(this._firmwareVersion); this._checkFirmware(this._firmwareVersion);
// Hardware version // Hardware version
} else if (data[3] === 0x04) { } else if (data[3] === 0x04) {
this._hardwareVersion = LPF2Hub.decodeVersion(data.readInt32LE(5)); this._hardwareVersion = decodeVersion(data.readInt32LE(5));
// RSSI update // RSSI update
} else if (data[3] === 0x05) { } else if (data[3] === 0x05) {
@ -282,7 +275,7 @@ export class LPF2Hub extends Hub {
// primary MAC Address // primary MAC Address
} else if (data[3] === 0x0d) { } else if (data[3] === 0x0d) {
this._primaryMACAddress = LPF2Hub.decodeMACAddress(data.slice(5)); this._primaryMACAddress = decodeMACAddress(data.slice(5));
// Battery level reports // Battery level reports
} else if (data[3] === 0x06) { } else if (data[3] === 0x06) {
@ -291,11 +284,11 @@ export class LPF2Hub extends Hub {
} }
private _parsePortMessage (data: Buffer) { private _parsePortMessage (message: Buffer) {
const portId = data[3]; const portId = message[3];
const event = data[4]; const event = message[4];
const deviceType = event ? data.readUInt16LE(5) : 0; const deviceType = event ? message.readUInt16LE(5) : 0;
// Handle device attachments // Handle device attachments
if (event === 0x01) { if (event === 0x01) {
@ -303,6 +296,9 @@ export class LPF2Hub extends Hub {
let device; let device;
switch (deviceType) { switch (deviceType) {
case Consts.DeviceType.LED_LIGHTS:
device = new Lights(this, portId);
break;
case Consts.DeviceType.CONTROL_PLUS_LARGE_MOTOR: case Consts.DeviceType.CONTROL_PLUS_LARGE_MOTOR:
device = new ControlPlusLargeMotor(this, portId); device = new ControlPlusLargeMotor(this, portId);
break; break;
@ -314,6 +310,15 @@ export class LPF2Hub extends Hub {
break; break;
} }
if (modeInfoDebug.enabled) {
const deviceTypeName = Consts.DeviceTypeNames[message[5]] || "Unknown";
modeInfoDebug(`Port ${toHex(portId)}, type ${toHex(deviceType, 4)} (${deviceTypeName})`);
const hwVersion = decodeVersion(message.readInt32LE(7));
const swVersion = decodeVersion(message.readInt32LE(11));
modeInfoDebug(`Port ${toHex(portId)}, hardware version ${hwVersion}, software version ${swVersion}`);
this._sendPortInformationRequest(portId);
}
this._attachDevice(device); this._attachDevice(device);
// Handle device detachments // Handle device detachments
@ -324,19 +329,8 @@ export class LPF2Hub extends Hub {
} }
} }
// let port = this._getPortForPortNumber(data[3]); // let port = this._getPortForPortNumber(data[3]);
// if (data[4] === 0x01 && modeInfoDebug.enabled) {
// const typeName = Consts.DeviceTypeNames[data[5]] || "unknown";
// modeInfoDebug(`Port ${toHex(data[3])}, type ${toHex(deviceType, 4)} (${typeName})`);
// const hwVersion = LPF2Hub.decodeVersion(data.readInt32LE(7));
// const swVersion = LPF2Hub.decodeVersion(data.readInt32LE(11));
// modeInfoDebug(`Port ${toHex(data[3])}, hardware version ${hwVersion}, software version ${swVersion}`);
// this._sendPortInformationRequest(data[3]);
// }
// if (!port) { // if (!port) {
// if (data[4] === 0x02) { // if (data[4] === 0x02) {
// const portA = this._getPortForPortNumber(data[7]); // const portA = this._getPortForPortNumber(data[7]);
@ -356,9 +350,6 @@ export class LPF2Hub extends Hub {
// } else { // } else {
// return; // return;
// } // }
// } else {
// port.connected = (data[4] === 0x01 || data[4] === 0x02) ? true : false;
// this._registerDeviceAttachment(port, deviceType);
// } // }
} }
@ -431,7 +422,17 @@ export class LPF2Hub extends Hub {
} }
private _parsePortAction (data: Buffer) { private _parsePortAction (message: Buffer) {
const portId = message[3];
const device = this._getDeviceByPortId(portId);
if (device) {
const finished = (message[4] === 0x0a);
if (finished) {
device.finish();
}
}
// const port = this._getPortForPortNumber(data[3]); // const port = this._getPortForPortNumber(data[3]);

View File

@ -1,13 +1,13 @@
import { Peripheral } from "@abandonware/noble"; import { Peripheral } from "@abandonware/noble";
import compareVersion from "compare-versions"; import compareVersion from "compare-versions";
import { IBLEAbstraction } from "./interfaces";
import { LPF2Hub } from "./lpf2hub"; import { LPF2Hub } from "./lpf2hub";
import { Port } from "./port";
import * as Consts from "./consts"; import * as Consts from "./consts";
import Debug = require("debug"); import Debug = require("debug");
import { IBLEAbstraction } from "./interfaces";
const debug = Debug("puphub"); const debug = Debug("puphub");

View File

@ -1,12 +1,12 @@
import { Peripheral } from "@abandonware/noble"; import { Peripheral } from "@abandonware/noble";
import { IBLEAbstraction } from "./interfaces";
import { LPF2Hub } from "./lpf2hub"; import { LPF2Hub } from "./lpf2hub";
import { Port } from "./port";
import * as Consts from "./consts"; import * as Consts from "./consts";
import Debug = require("debug"); import Debug = require("debug");
import { IBLEAbstraction } from "./interfaces";
const debug = Debug("pupremote"); const debug = Debug("pupremote");

View File

@ -40,15 +40,18 @@ export class TachoMotor extends BasicMotor {
* Rotate a motor by a given angle. * Rotate a motor by a given angle.
* @method TachoMotor#setMotorAngle * @method TachoMotor#setMotorAngle
* @param {number} angle How much the motor should be rotated (in degrees). * @param {number} angle How much the motor should be rotated (in degrees).
* @param {number} [speed=100] For forward, a value between 1 - 100 should be set. For reverse, a value between -1 to -100. * @param {number} [power=100] For forward, a value between 1 - 100 should be set. For reverse, a value between -1 to -100.
* @returns {Promise} Resolved upon successful completion of command (ie. once the motor is finished). * @returns {Promise} Resolved upon successful completion of command (ie. once the motor is finished).
*/ */
public rotateByAngle (port: string, angle: number, speed: number = 100) { public rotateByAngle (angle: number, power: number = 100) {
return new Promise((resolve) => { return new Promise((resolve) => {
const message = Buffer.from([0x81, this.portId, 0x11, 0x0b, 0x00, 0x00, 0x00, 0x00, mapSpeed(speed), 0x64, 0x7f, 0x03]); this._busy = true;
const message = Buffer.from([0x81, this.portId, 0x11, 0x0b, 0x00, 0x00, 0x00, 0x00, mapSpeed(power), 0x64, 0x7f, 0x03]);
message.writeUInt32LE(angle, 4); message.writeUInt32LE(angle, 4);
this.send(message); this.send(message);
return resolve(); this._finished = () => {
return resolve();
};
}); });
} }

View File

@ -20,3 +20,12 @@ export const mapSpeed = (speed: number) => {
} }
return speed; return speed;
}; };
export const decodeVersion = (version: number) => {
const parts = version.toString(16).padStart(8, "0");
return [parts[0], parts[1], parts.substring(2, 4), parts.substring(4)].join(".");
};
export const decodeMACAddress = (address: Uint8Array) => {
return Array.from(address).map((part) => toHex(part, 2)).join(":");
};

View File

@ -1,17 +1,19 @@
import { Peripheral } from "@abandonware/noble"; import { Peripheral } from "@abandonware/noble";
import { IBLEAbstraction } from "./interfaces";
import { Device } from "./device"; import { Device } from "./device";
import { Hub } from "./hub"; import { Hub } from "./hub";
import { Port } from "./port";
import { ColorDistanceSensor } from "./colordistancesensor"; import { ColorDistanceSensor } from "./colordistancesensor";
import { ControlPlusLargeMotor } from "./controlpluslargemotor"; import { ControlPlusLargeMotor } from "./controlpluslargemotor";
import { Lights } from "./lights";
import * as Consts from "./consts"; import * as Consts from "./consts";
import Debug = require("debug");
import { IBLEAbstraction } from "./interfaces";
import { isWebBluetooth } from "./utils"; import { isWebBluetooth } from "./utils";
import Debug = require("debug");
const debug = Debug("wedo2smarthub"); const debug = Debug("wedo2smarthub");
@ -350,6 +352,9 @@ export class WeDo2SmartHub extends Hub {
let device; let device;
switch (deviceType) { switch (deviceType) {
case Consts.DeviceType.LED_LIGHTS:
device = new Lights(this, portId);
break;
case Consts.DeviceType.CONTROL_PLUS_LARGE_MOTOR: case Consts.DeviceType.CONTROL_PLUS_LARGE_MOTOR:
device = new ControlPlusLargeMotor(this, portId); device = new ControlPlusLargeMotor(this, portId);
break; break;
@ -357,9 +362,9 @@ export class WeDo2SmartHub extends Hub {
device = new ColorDistanceSensor(this, portId); device = new ColorDistanceSensor(this, portId);
break; break;
default: default:
device = new Device(this, portId); device = new Device(this, portId, deviceType);
break; break;
} }
this._attachDevice(device); this._attachDevice(device);