First pass at TypeScript version

This commit is contained in:
Nathan Kunicki 2018-06-20 13:51:37 +01:00
parent f840adb5f0
commit 40a14a602f
13 changed files with 1565 additions and 329 deletions

2
.gitignore vendored
View File

@ -1 +1,3 @@
node_modules/ node_modules/
dist/

View File

@ -1,18 +1,33 @@
const debug = require("debug")("lpf2"), import { Peripheral } from "noble";
EventEmitter = require("events").EventEmitter;
const Hub = require("./hub.js"), import { Hub } from "./hub.js";
Port = require("./port.js"), import { Port } from "./port.js";
Consts = require("./consts.js");
import Debug = require("debug");
const debug = Debug("lpf2");
import { Consts } from "./consts.js";
class BoostHub extends Hub { /**
* @class BoostHub
* @extends Hub
*/
export class BoostHub extends Hub {
constructor (peripheral, autoSubscribe) { public static IsBoostHub (peripheral: Peripheral) {
return (peripheral.advertisement.localName === Consts.BLE.Name.BOOST_MOVE_HUB_NAME && peripheral.advertisement.serviceUuids.indexOf(Consts.BLE.Services.BOOST_MOVE_HUB) >= 0);
}
private _lastTiltX: number = 0;
private _lastTiltY: number = 0;
constructor (peripheral: Peripheral, autoSubscribe: boolean = true) {
super(peripheral, autoSubscribe); super(peripheral, autoSubscribe);
this.type = Consts.Hubs.BOOST_MOVE_HUB; this.type = Consts.Hubs.BOOST_MOVE_HUB;
this.ports = { this._ports = {
"A": new Port("A", 55), "A": new Port("A", 55),
"B": new Port("B", 56), "B": new Port("B", 56),
"AB": new Port("AB", 57), "AB": new Port("AB", 57),
@ -20,18 +35,11 @@ class BoostHub extends Hub {
"C": new Port("C", 1), "C": new Port("C", 1),
"D": new Port("D", 2) "D": new Port("D", 2)
}; };
this._lastTiltX = 0;
this._lastTiltY = 0;
debug("Discovered Boost Move Hub"); debug("Discovered Boost Move Hub");
} }
static isBoostHub (peripheral) { public connect (callback: () => void) {
return (peripheral.advertisement.localName === Consts.BLE.Name.BOOST_MOVE_HUB_NAME && peripheral.advertisement.serviceUuids.indexOf(Consts.BLE.Services.BOOST_MOVE_HUB) >= 0);
}
connect (callback) {
debug("Connecting to Boost Move Hub"); debug("Connecting to Boost Move Hub");
super.connect(() => { super.connect(() => {
const characteristic = this._characteristics[Consts.BLE.Characteristics.Boost.ALL]; const characteristic = this._characteristics[Consts.BLE.Characteristics.Boost.ALL];
@ -41,11 +49,16 @@ class BoostHub extends Hub {
if (callback) { if (callback) {
callback(); callback();
} }
}) });
} }
setLEDColor (color) { /**
* Set the color of the LED on the Hub via a color value.
* @method BoostHub#setLEDColor
* @param {number} color - A number representing one of the LED color consts.
*/
public setLEDColor (color: number | boolean) {
const characteristic = this._characteristics[Consts.BLE.Characteristics.Boost.ALL]; const characteristic = this._characteristics[Consts.BLE.Characteristics.Boost.ALL];
if (characteristic) { if (characteristic) {
let data = Buffer.from([0x05, 0x00, 0x01, 0x02, 0x02]); let data = Buffer.from([0x05, 0x00, 0x01, 0x02, 0x02]);
@ -70,25 +83,39 @@ class BoostHub extends Hub {
// } // }
setMotorSpeed (port, speed, time) { /**
* Set the motor speed on a given port.
* @method BoostHub#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.
*/
public setMotorSpeed (port: string, speed: number, time: number) {
const characteristic = this._characteristics[Consts.BLE.Characteristics.Boost.ALL]; const characteristic = this._characteristics[Consts.BLE.Characteristics.Boost.ALL];
if (characteristic) { if (characteristic) {
if (time) { if (time) {
const data = Buffer.from([0x0c, 0x00, 0x81, this.ports[port].value, 0x11, 0x09, 0x00, 0x00, speed, 0x64, 0x7f, 0x03]); const data = Buffer.from([0x0c, 0x00, 0x81, this._ports[port].value, 0x11, 0x09, 0x00, 0x00, speed, 0x64, 0x7f, 0x03]);
data.writeUInt16LE(time > 65535 ? 65535 : time, 6); data.writeUInt16LE(time > 65535 ? 65535 : time, 6);
characteristic.write(data); characteristic.write(data);
} else { } else {
const data = Buffer.from([0x0a, 0x00, 0x81, this.ports[port].value, 0x11, 0x01, speed, 0x64, 0x7f, 0x03]); const data = Buffer.from([0x0a, 0x00, 0x81, this._ports[port].value, 0x11, 0x01, speed, 0x64, 0x7f, 0x03]);
characteristic.write(data); characteristic.write(data);
} }
} }
} }
setMotorAngle (port, angle, speed = 100) { /**
* Rotate a motor by a given angle.
* @method BoostHub#setMotorAngle
* @param {string} port
* @param {number} angle - How much the motor should be rotated (in degrees).
* @param {number} [speed=100] - How fast the motor should be rotated.
*/
public setMotorAngle (port: string, angle: number, speed: number = 100) {
const characteristic = this._characteristics[Consts.BLE.Characteristics.Boost.ALL]; const characteristic = this._characteristics[Consts.BLE.Characteristics.Boost.ALL];
if (characteristic) { if (characteristic) {
const data = Buffer.from([0x0e, 0x00, 0x81, this.ports[port].value, 0x11, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x7f, 0x03]);; const data = Buffer.from([0x0e, 0x00, 0x81, this._ports[port].value, 0x11, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x7f, 0x03]);
data.writeUInt32LE(angle, 6); data.writeUInt32LE(angle, 6);
data.writeInt8(speed, 10); data.writeInt8(speed, 10);
characteristic.write(data); characteristic.write(data);
@ -96,22 +123,38 @@ class BoostHub extends Hub {
} }
_getPortForPortNumber (num) { protected _activatePortDevice (port: number, type: number, mode: number, format: number, callback: () => void) {
const characteristic = this._characteristics[Consts.BLE.Characteristics.Boost.ALL];
if (characteristic) {
characteristic.write(Buffer.from([0x0a, 0x00, 0x41, port, mode, 0x01, 0x00, 0x00, 0x00, 0x01]), callback);
}
}
protected _deactivatePortDevice (port: number, type: number, mode: number, format: number, callback: () => void) {
const characteristic = this._characteristics[Consts.BLE.Characteristics.Boost.ALL];
if (characteristic) {
characteristic.write(Buffer.from([0x0a, 0x00, 0x41, port, mode, 0x01, 0x00, 0x00, 0x00, 0x00]), callback);
}
}
private _getPortForPortNumber (num: number) {
let port = null; let port = null;
if (num === 1) { if (num === 1) {
port = this.ports["C"]; port = this._ports["C"];
} else if (num === 2) { } else if (num === 2) {
port = this.ports["D"]; port = this._ports["D"];
} else if (num === 55) { } else if (num === 55) {
port = this.ports["A"]; port = this._ports["A"];
} else if (num === 56) { } else if (num === 56) {
port = this.ports["B"]; port = this._ports["B"];
} else if (num === 57) { } else if (num === 57) {
port = this.ports["AB"]; port = this._ports["AB"];
} else if (num === 58) { } else if (num === 58) {
port = this.ports["TILT"]; port = this._ports["TILT"];
} else { } else {
return false; return false;
} }
@ -121,7 +164,7 @@ class BoostHub extends Hub {
} }
_parseMessage (data) { private _parseMessage (data: Buffer) {
switch (data[2]) { switch (data[2]) {
case 0x01: case 0x01:
@ -147,10 +190,15 @@ class BoostHub extends Hub {
} }
_parseDeviceInfo (data) { private _parseDeviceInfo (data: Buffer) {
if (data[3] === 2) { if (data[3] === 2) {
if (data[5] === 1) { if (data[5] === 1) {
/**
* Emits when a button is pressed.
* @event BoostHub#button
* @param {number} state - A number representing one of the button state consts.
*/
this.emit("button", Consts.Button.PRESSED); this.emit("button", Consts.Button.PRESSED);
return; return;
} else if (data[5] === 0) { } else if (data[5] === 0) {
@ -162,9 +210,9 @@ class BoostHub extends Hub {
} }
_parsePortMessage (data) { private _parsePortMessage (data: Buffer) {
let port = this._getPortForPortNumber(data[3]); const port = this._getPortForPortNumber(data[3]);
if (!port) { if (!port) {
return; return;
@ -176,9 +224,9 @@ class BoostHub extends Hub {
} }
_parsePortAction (data) { private _parsePortAction (data: Buffer) {
let port = this._getPortForPortNumber(data[3]); const port = this._getPortForPortNumber(data[3]);
if (!port) { if (!port) {
return; return;
@ -189,25 +237,9 @@ class BoostHub extends Hub {
} }
_activatePortDevice (port, type, mode, format, callback) { private _parseSensorMessage (data: Buffer) {
const characteristic = this._characteristics[Consts.BLE.Characteristics.Boost.ALL];
if (characteristic) {
characteristic.write(Buffer.from([0x0a, 0x00, 0x41, port, mode, 0x01, 0x00, 0x00, 0x00, 0x01]), callback);
}
}
const port = this._getPortForPortNumber(data[3]);
_deactivatePortDevice (port, type, mode, format, callback) {
const characteristic = this._characteristics[Consts.BLE.Characteristics.Boost.ALL];
if (characteristic) {
characteristic.write(Buffer.from([0x0a, 0x00, 0x41, port, mode, 0x01, 0x00, 0x00, 0x00, 0x00]), callback);
}
}
_parseSensorMessage (data) {
let port = this._getPortForPortNumber(data[3]);
if (!port) { if (!port) {
return; return;
@ -221,16 +253,28 @@ class BoostHub extends Hub {
if (data[5] === 1) { if (data[5] === 1) {
distance = data[4] + 255; distance = data[4] + 255;
} }
/**
* Emits when a distance sensor is activated.
* @event BoostHub#distance
* @param {string} port
* @param {number} distance - Distance, in millimeters.
*/
this.emit("distance", port.id, distance * 10); this.emit("distance", port.id, distance * 10);
break; break;
} }
case Consts.Devices.BOOST_DISTANCE: case Consts.Devices.BOOST_DISTANCE:
{ {
/**
* Emits when a color sensor is activated.
* @event BoostHub#color
* @param {string} port
* @param {number} color - A number representing one of the LED color consts.
*/
this.emit("color", port.id, data[4]); this.emit("color", port.id, data[4]);
let distance = data[5], let distance = data[5];
partial = data[7]; const partial = data[7];
if (partial > 0) { if (partial > 0) {
distance += 1 / partial; distance += 1 / partial;
@ -241,23 +285,36 @@ class BoostHub extends Hub {
} }
case Consts.Devices.WEDO2_TILT: case Consts.Devices.WEDO2_TILT:
{ {
let tiltX = data[4] > 160 ? data[4] - 255 : data[4] - (data[4] * 2); const tiltX = data[4] > 160 ? data[4] - 255 : data[4] - (data[4] * 2);
let tiltY = data[5] > 160 ? 255 - data[5] : data[5] - (data[5] * 2); const tiltY = data[5] > 160 ? 255 - data[5] : data[5] - (data[5] * 2);
this._lastTiltX = tiltX; this._lastTiltX = tiltX;
this._lastTiltY = tiltY; this._lastTiltY = tiltY;
/**
* Emits when a tilt sensor is activated.
* @event BoostHub#tilt
* @param {string} port
* @param {number} x
* @param {number} y
*/
this.emit("tilt", port.id, this._lastTiltX, this._lastTiltY); this.emit("tilt", port.id, this._lastTiltX, this._lastTiltY);
break; break;
} }
case Consts.Devices.BOOST_INTERACTIVE_MOTOR: case Consts.Devices.BOOST_INTERACTIVE_MOTOR:
{ {
const rotation = data.readInt32LE(2); const rotation = data.readInt32LE(2);
/**
* Emits when a rotation sensor is activated.
* @event BoostHub#rotate
* @param {string} port
* @param {number} rotation
*/
this.emit("rotate", port.id, rotation); this.emit("rotate", port.id, rotation);
break; break;
} }
case Consts.Devices.BOOST_TILT: case Consts.Devices.BOOST_TILT:
{ {
let tiltX = data[4] > 160 ? data[4] - 255 : data[4]; const tiltX = data[4] > 160 ? data[4] - 255 : data[4];
let tiltY = data[5] > 160 ? 255 - data[5] : data[5] - (data[5] * 2); const tiltY = data[5] > 160 ? 255 - data[5] : data[5] - (data[5] * 2);
this.emit("tilt", port.id, tiltX, tiltY); this.emit("tilt", port.id, tiltX, tiltY);
break; break;
} }
@ -268,6 +325,3 @@ class BoostHub extends Hub {
} }
module.exports = BoostHub;

View File

@ -1,4 +1,6 @@
const Consts = { /* tslint:disable */
export const Consts = {
Hubs: { Hubs: {
UNKNOWN: 0, UNKNOWN: 0,
WEDO2_SMART_HUB: 1, WEDO2_SMART_HUB: 1,
@ -64,5 +66,3 @@ const Consts = {
} }
} }
} }
module.exports = Consts;

152
hub.js
View File

@ -1,152 +0,0 @@
const debug = require("debug")("lpf2"),
EventEmitter = require("events").EventEmitter;
const Port = require("./port.js"),
Consts = require("./consts.js");
class Hub extends EventEmitter {
constructor (peripheral, autoSubscribe) {
super();
this.autoSubscribe = !!autoSubscribe;
this._peripheral = peripheral;
this._characteristics = {};
this._batteryLevel = 100;
this._rssi = -100; // Initialize as -100 - no signal
this._ports = {};
this.type = Consts.Hubs.UNKNOWN;
this.uuid = peripheral.uuid;
}
connect (callback) {
const self = this;
this._peripheral.connect((err) => {
this._rssi = this._peripheral.rssi;
let rssiUpdateInterval = setInterval(() => {
this._peripheral.updateRssi((err, rssi) => {
if (!err) {
if (this._rssi != rssi) {
this._rssi = rssi;
debug(`RSSI change ${rssi}`)
self.emit("rssiChange", rssi);
}
}
});
}, 2000);
self._peripheral.on("disconnect", () => {
clearInterval(rssiUpdateInterval);
});
self._peripheral.discoverServices([], (err, services) => {
if (err) {
this.emit("error", err);
return;
}
debug("Service/characteristic discovery started");
const servicePromises = [];
services.forEach((service) => {
servicePromises.push(new Promise((resolve, reject) => {
service.discoverCharacteristics([], (err, characteristics) => {
characteristics.forEach((characteristic) => {
this._characteristics[characteristic.uuid] = characteristic;
});
return resolve();
});
}));
});
Promise.all(servicePromises).then(() => {
debug("Service/characteristic discovery finished");
if (callback) {
callback();
}
})
});
});
}
subscribe (port, mode = false) {
if (!mode) {
mode = this._getModeForDeviceType(this.ports[port].type);
}
this._activatePortDevice(this.ports[port].value, this.ports[port].type, mode, 0x00);
}
unsubscribe (port, mode = false) {
if (!mode) {
mode = this._getModeForDeviceType(this.ports[port].type);
}
this._deactivatePortDevice(this.ports[port].value, this.ports[port].type, mode, 0x00);
}
_getModeForDeviceType (type) {
switch (type) {
case Consts.Devices.BASIC_MOTOR:
return 0x02;
case Consts.Devices.BOOST_INTERACTIVE_MOTOR:
return 0x02;
case Consts.Devices.BOOST_MOVE_HUB_MOTOR:
return 0x02;
case Consts.Devices.BOOST_DISTANCE:
return (this.type == Consts.Hubs.WEDO2_SMART_HUB ? 0x00 : 0x08);
case Consts.Devices.BOOST_TILT:
return 0x04;
default:
return 0x00;
}
}
_subscribeToCharacteristic (characteristic, callback) {
characteristic.on("data", (data, isNotification) => {
return callback(data);
});
characteristic.subscribe((err) => {
if (err) {
this.emit("error", err);
}
});
}
_registerDeviceAttachment (port, type) {
if (port.connected) {
port.type = type;
if (this.autoSubscribe) {
this._activatePortDevice(port.value, type, this._getModeForDeviceType(type), 0x00);
}
} else {
port.type = null;
debug(`Port ${port.id} disconnected`);
}
}
}
module.exports = Hub;

192
hub.ts Normal file
View File

@ -0,0 +1,192 @@
import { EventEmitter } from "events";
import { Characteristic, Peripheral, Service } from "noble";
import { Port } from "./port";
import Debug = require("debug");
const debug = Debug("lpf2");
import { Consts } from "./consts.js";
/**
* @class Hub
* @extends EventEmitter
*/
export class Hub extends EventEmitter {
public autoSubscribe: boolean;
public type: number = Consts.Hubs.UNKNOWN;
public uuid: string;
protected _ports: any = {};
protected _characteristics: any = {};
private _peripheral: Peripheral;
private _rssi: number = -100;
private _batteryLevel: number = 100;
constructor (peripheral: Peripheral, autoSubscribe: boolean = true) {
super();
this.autoSubscribe = !!autoSubscribe;
this._peripheral = peripheral;
this.uuid = peripheral.uuid;
}
/**
* Connect to the Hub.
* @method Hub#connect
* @param {function} [callback]
*/
public connect (callback: () => void) {
const self = this;
this._peripheral.connect((err: string) => {
this._rssi = this._peripheral.rssi;
const rssiUpdateInterval = setInterval(() => {
this._peripheral.updateRssi((err: string, rssi: number) => {
if (!err) {
if (this._rssi !== rssi) {
this._rssi = rssi;
debug(`RSSI change ${rssi}`);
self.emit("rssiChange", rssi);
}
}
});
}, 2000);
self._peripheral.on("disconnect", () => {
clearInterval(rssiUpdateInterval);
this.emit("disconnect");
});
self._peripheral.discoverServices([], (err: string, services: Service[]) => {
if (err) {
this.emit("error", err);
return;
}
debug("Service/characteristic discovery started");
const servicePromises: Array<Promise<null>> = [];
services.forEach((service) => {
servicePromises.push(new Promise((resolve, reject) => {
service.discoverCharacteristics([], (err, characteristics) => {
characteristics.forEach((characteristic) => {
this._characteristics[characteristic.uuid] = characteristic;
});
return resolve();
});
}));
});
Promise.all(servicePromises).then(() => {
debug("Service/characteristic discovery finished");
this.emit("connect");
if (callback) {
callback();
}
});
});
});
}
/**
* Subscribe to sensor notifications on a given port.
* @method Hub#subscribe
* @param {string} port
* @param {number|boolean} [mode=false] - The sensor mode to activate. If no mode is provided, the default for that sensor will be chosen.
*/
public subscribe (port: string, mode: number | boolean = false, callback?: () => void) {
let newMode = 0x00;
if (!mode) {
newMode = this._getModeForDeviceType(this._ports[port].type);
}
this._activatePortDevice(this._ports[port].value, this._ports[port].type, newMode, 0x00, callback);
}
/**
* Unsubscribe to sensor notifications on a given port.
* @method Hub#unsubscribe
* @param {string} port
*/
public unsubscribe (port: string, callback?: () => void) {
const mode = this._getModeForDeviceType(this._ports[port].type);
this._deactivatePortDevice(this._ports[port].value, this._ports[port].type, mode, 0x00, callback);
}
protected _subscribeToCharacteristic (characteristic: Characteristic, callback: (data: Buffer) => void) {
characteristic.on("data", (data: Buffer) => {
return callback(data);
});
characteristic.subscribe((err) => {
if (err) {
this.emit("error", err);
}
});
}
protected _activatePortDevice (port: number, type: number, mode: number, format: number, callback?: () => void) {
if (callback) {
callback();
}
}
protected _deactivatePortDevice (port: number, type: number, mode: number, format: number, callback?: () => void) {
if (callback) {
callback();
}
}
protected _registerDeviceAttachment (port: Port, type: number) {
if (port.connected) {
port.type = type;
if (this.autoSubscribe) {
this._activatePortDevice(port.value, type, this._getModeForDeviceType(type), 0x00);
}
} else {
port.type = null;
debug(`Port ${port.id} disconnected`);
}
}
private _getModeForDeviceType (type: number) {
switch (type) {
case Consts.Devices.BASIC_MOTOR:
return 0x02;
case Consts.Devices.BOOST_INTERACTIVE_MOTOR:
return 0x02;
case Consts.Devices.BOOST_MOVE_HUB_MOTOR:
return 0x02;
case Consts.Devices.BOOST_DISTANCE:
return (this.type === Consts.Hubs.WEDO2_SMART_HUB ? 0x00 : 0x08);
case Consts.Devices.BOOST_TILT:
return 0x04;
default:
return 0x00;
}
}
}

View File

@ -1,15 +1,19 @@
const noble = require("noble"), import { Peripheral } from "noble";
debug = require("debug")("lpf2"),
EventEmitter = require("events").EventEmitter;
const WeDo2Hub = require("./wedo2hub.js"), import { BoostHub } from "./boosthub";
BoostHub = require("./boosthub.js"), import { Hub } from "./hub";
Consts = require("./consts.js"); import { WeDo2Hub } from "./wedo2hub";
let ready = false, import { EventEmitter} from "events";
wantScan = false;
noble.on("stateChange", (state) => { import Debug = require("debug");
const debug = Debug("lpf2");
import noble = require("noble");
let ready = false;
let wantScan = false;
noble.on("stateChange", (state: string) => {
ready = (state === "poweredOn"); ready = (state === "poweredOn");
if (ready) { if (ready) {
if (wantScan) { if (wantScan) {
@ -21,26 +25,38 @@ noble.on("stateChange", (state) => {
} }
}); });
class LPF2 extends EventEmitter { /**
* @class LPF2
* @extends EventEmitter
*/
export class LPF2 extends EventEmitter {
public autoSubscribe: boolean = true;
private _connectedDevices: any = {};
constructor () { constructor () {
super(); super();
this.autoSubscribe = true;
this._connectedDevices = {};
} }
scan () { /**
* Begin scanning for LPF2 Hub devices.
* @method LPF2#scan
*/
public scan () {
wantScan = true; wantScan = true;
noble.on("discover", (peripheral) => { noble.on("discover", (peripheral: Peripheral) => {
let hub = null; let hub: Hub;
if (WeDo2Hub.isWeDo2Hub(peripheral)) { if (WeDo2Hub.IsWeDo2Hub(peripheral)) {
hub = new WeDo2Hub(peripheral, this.autoSubscribe); hub = new WeDo2Hub(peripheral, this.autoSubscribe);
} else if (BoostHub.isBoostHub(peripheral)) { } else if (BoostHub.IsBoostHub(peripheral)) {
hub = new BoostHub(peripheral, this.autoSubscribe); hub = new BoostHub(peripheral, this.autoSubscribe);
} else { } else {
return; return;
@ -50,12 +66,12 @@ class LPF2 extends EventEmitter {
noble.stopScanning(); noble.stopScanning();
noble.startScanning(); noble.startScanning();
hub._peripheral.on("connect", () => { hub.on("connect", () => {
debug(`Hub ${hub.uuid} connected`); debug(`Hub ${hub.uuid} connected`);
this._connectedDevices[hub.uuid] = hub; this._connectedDevices[hub.uuid] = hub;
}); });
hub._peripheral.on("disconnect", () => { hub.on("disconnect", () => {
debug(`Hub ${hub.uuid} disconnected`); debug(`Hub ${hub.uuid} disconnected`);
delete this._connectedDevices[hub.uuid]; delete this._connectedDevices[hub.uuid];
@ -67,6 +83,11 @@ class LPF2 extends EventEmitter {
}); });
debug(`Hub ${hub.uuid} discovered`); debug(`Hub ${hub.uuid} discovered`);
/**
* Emits when a LPF2 Hub device is found.
* @event LPF2#discover
* @param {Hub} hub
*/
this.emit("discover", hub); this.emit("discover", hub);
}); });
@ -79,6 +100,3 @@ class LPF2 extends EventEmitter {
} }
module.exports = LPF2;

998
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -2,13 +2,22 @@
"name": "node-lpf2", "name": "node-lpf2",
"version": "1.0.0", "version": "1.0.0",
"description": "A Node.js module to interface with Lego Power Functions 2.0 components.", "description": "A Node.js module to interface with Lego Power Functions 2.0 components.",
"main": "lpf2.js", "main": "dist/lpf2.js",
"types": "dist/lpf2.d.ts",
"scripts": { "scripts": {
"test": "echo \"Error: no test specified\" && exit 1" "build": "tslint -c tslint.json \"*.ts\" && tsc"
}, },
"author": "Nathan Kunicki <me@nathankunicki.com>", "author": "Nathan Kunicki <me@nathankunicki.com>",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"noble": "^1.9.1" "@types/debug": "0.0.30",
"@types/noble": "0.0.37",
"@types/node": "^10.3.4",
"noble": "^1.9.1",
"typescript": "^2.9.2"
},
"devDependencies": {
"jsdoc-to-markdown": "^4.0.1",
"tslint": "^5.10.0"
} }
} }

12
port.js
View File

@ -1,12 +0,0 @@
class Port {
constructor (id, value) {
this.id = id;
this.value = value;
this.connected = false;
this.type = null;
}
}
module.exports = Port;

17
port.ts Normal file
View File

@ -0,0 +1,17 @@
export class Port {
public id: string;
public value: number;
public connected: boolean;
public type: number | null;
constructor (id: string, value: number) {
this.id = id;
this.value = value;
this.connected = false;
this.type = null;
}
}

59
tsconfig.json Normal file
View File

@ -0,0 +1,59 @@
{
"compilerOptions": {
/* Basic Options */
"target": "es2017", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
// "lib": [], /* Specify library files to be included in the compilation. */
// "allowJs": true, /* Allow javascript files to be compiled. */
// "checkJs": true, /* Report errors in .js files. */
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
"declaration": true, /* Generates corresponding '.d.ts' file. */
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
// "sourceMap": true, /* Generates corresponding '.map' file. */
// "outFile": "./", /* Concatenate and emit output to single file. */
"outDir": "./dist", /* Redirect output structure to the directory. */
// "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
// "composite": true, /* Enable project compilation */
// "removeComments": true, /* Do not emit comments to output. */
// "noEmit": true, /* Do not emit outputs. */
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
/* Strict Type-Checking Options */
"strict": true, /* Enable all strict type-checking options. */
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* Enable strict null checks. */
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
/* Additional Checks */
// "noUnusedLocals": true, /* Report errors on unused locals. */
// "noUnusedParameters": true, /* Report errors on unused parameters. */
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
/* Module Resolution Options */
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
// "typeRoots": [], /* List of folders to include type definitions from. */
// "types": [], /* Type declaration files to be included in compilation. */
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
/* Source Map Options */
// "sourceRoot": "./", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
// "mapRoot": "./", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
/* Experimental Options */
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
}
}

23
tslint.json Normal file
View File

@ -0,0 +1,23 @@
{
"defaultSeverity": "error",
"extends": [
"tslint:recommended"
],
"jsRules": {},
"rules": {
"no-consecutive-blank-lines": false,
"space-before-function-paren": false,
"no-bitwise": false,
"trailing-comma": false,
"max-line-length": false,
"prefer-for-of": false,
"typedef": true,
"no-console": false,
"variable-name": [true, "ban-keywords", "check-format", "allow-leading-underscore"],
"object-literal-key-quotes": "consistent",
"object-literal-sort-keys": false,
"no-string-literal": false,
"no-shadowed-variable": [false]
},
"rulesDirectory": []
}

View File

@ -1,33 +1,41 @@
const debug = require("debug")("lpf2"), import { Peripheral } from "noble";
EventEmitter = require("events").EventEmitter;
const Hub = require("./hub.js"), import { Hub } from "./hub.js";
Port = require("./port.js"), import { Port } from "./port.js";
Consts = require("./consts.js");
import Debug = require("debug");
const debug = Debug("lpf2");
import { Consts } from "./consts.js";
class WeDo2Hub extends Hub { /**
* @class WeDo2Hub
* @extends Hub
*/
export class WeDo2Hub extends Hub {
constructor (peripheral, autoSubscribe) { public static IsWeDo2Hub (peripheral: Peripheral) {
super(peripheral, autoSubscribe);
this.type = Consts.Hubs.WEDO2_SMART_HUB;
this.ports = {
"A": new Port("A", 1),
"B": new Port("B", 2)
};
this._lastTiltX = 0;
this._lastTiltY = 0;
debug("Discovered WeDo 2.0 Smart Hub");
}
static isWeDo2Hub (peripheral) {
return (peripheral.advertisement.localName === Consts.BLE.Name.WEDO2_SMART_HUB_NAME && peripheral.advertisement.serviceUuids.indexOf(Consts.BLE.Services.WEDO2_SMART_HUB) >= 0); return (peripheral.advertisement.localName === Consts.BLE.Name.WEDO2_SMART_HUB_NAME && peripheral.advertisement.serviceUuids.indexOf(Consts.BLE.Services.WEDO2_SMART_HUB) >= 0);
} }
connect (callback) { private _lastTiltX: number = 0;
private _lastTiltY: number = 0;
constructor (peripheral: Peripheral, autoSubscribe: boolean = true) {
super(peripheral, autoSubscribe);
this.type = Consts.Hubs.WEDO2_SMART_HUB;
this._ports = {
"A": new Port("A", 1),
"B": new Port("B", 2)
};
debug("Discovered WeDo 2.0 Smart Hub");
}
public connect (callback: () => void) {
debug("Connecting to WeDo 2.0 Smart Hub"); debug("Connecting to WeDo 2.0 Smart Hub");
super.connect(() => { super.connect(() => {
this._subscribeToCharacteristic(this._characteristics[Consts.BLE.Characteristics.WeDo2.PORT_TYPE], this._parsePortMessage.bind(this)); this._subscribeToCharacteristic(this._characteristics[Consts.BLE.Characteristics.WeDo2.PORT_TYPE], this._parsePortMessage.bind(this));
@ -37,11 +45,16 @@ class WeDo2Hub extends Hub {
if (callback) { if (callback) {
callback(); callback();
} }
}) });
} }
setLEDColor (color) { /**
* Set the color of the LED on the Hub via a color value.
* @method WeDo2Hub#setLEDColor
* @param {number} color - A number representing one of the LED color consts.
*/
public setLEDColor (color: number | boolean) {
const motorCharacteristic = this._characteristics[Consts.BLE.Characteristics.WeDo2.MOTOR_VALUE_WRITE]; const motorCharacteristic = this._characteristics[Consts.BLE.Characteristics.WeDo2.MOTOR_VALUE_WRITE];
const portCharacteristic = this._characteristics[Consts.BLE.Characteristics.WeDo2.PORT_TYPE_WRITE]; const portCharacteristic = this._characteristics[Consts.BLE.Characteristics.WeDo2.PORT_TYPE_WRITE];
if (motorCharacteristic && portCharacteristic) { if (motorCharacteristic && portCharacteristic) {
@ -56,35 +69,63 @@ class WeDo2Hub extends Hub {
} }
setLEDRGB (red, green, blue) { /**
* Set the color of the LED on the Hub via RGB values.
* @method WeDo2Hub#setLEDRGB
* @param {number} red
* @param {number} green
* @param {number} blue
*/
public setLEDRGB (red: number, green: number, blue: number) {
const motorCharacteristic = this._characteristics[Consts.BLE.Characteristics.WeDo2.MOTOR_VALUE_WRITE]; const motorCharacteristic = this._characteristics[Consts.BLE.Characteristics.WeDo2.MOTOR_VALUE_WRITE];
const portCharacteristic = this._characteristics[Consts.BLE.Characteristics.WeDo2.PORT_TYPE_WRITE]; const portCharacteristic = this._characteristics[Consts.BLE.Characteristics.WeDo2.PORT_TYPE_WRITE];
if (motorCharacteristic && portCharacteristic) { if (motorCharacteristic && portCharacteristic) {
let data1 = Buffer.from([0x01, 0x02, 0x06, 0x17, 0x01, 0x02]); const data1 = Buffer.from([0x01, 0x02, 0x06, 0x17, 0x01, 0x02]);
portCharacteristic.write(data1); portCharacteristic.write(data1);
let data2 = Buffer.from([0x06, 0x04, 0x03, red, green, blue]); const data2 = Buffer.from([0x06, 0x04, 0x03, red, green, blue]);
console.log(data2);
motorCharacteristic.write(data2); motorCharacteristic.write(data2);
} }
} }
setMotorSpeed (port, speed) { /**
* Set the motor speed on a given port.
* @method WeDo2Hub#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.
*/
public setMotorSpeed (port: string, speed: number) {
const characteristic = this._characteristics[Consts.BLE.Characteristics.WeDo2.MOTOR_VALUE_WRITE]; const characteristic = this._characteristics[Consts.BLE.Characteristics.WeDo2.MOTOR_VALUE_WRITE];
if (characteristic) { if (characteristic) {
characteristic.write(Buffer.from([this.ports[port].value, 0x01, 0x02, speed])); characteristic.write(Buffer.from([this._ports[port].value, 0x01, 0x02, speed]));
} }
} }
_getPortForPortNumber (num) { protected _activatePortDevice (port: number, type: number, mode: number, format: number, callback: () => void) {
const characteristic = this._characteristics[Consts.BLE.Characteristics.WeDo2.PORT_TYPE_WRITE];
if (characteristic) {
characteristic.write(Buffer.from([0x01, 0x02, port, type, mode, 0x01, 0x00, 0x00, 0x00, format, 0x01]), callback);
}
}
protected _deactivatePortDevice (port: number, type: number, mode: number, format: number, callback: () => void) {
const characteristic = this._characteristics[Consts.BLE.Characteristics.WeDo2.PORT_TYPE_WRITE];
if (characteristic) {
characteristic.write(Buffer.from([0x01, 0x02, port, type, mode, 0x01, 0x00, 0x00, 0x00, format, 0x00]), callback);
}
}
private _getPortForPortNumber (num: number) {
let port = null; let port = null;
if (num === 1) { if (num === 1) {
port = this.ports["A"]; port = this._ports["A"];
} else if (num === 2) { } else if (num === 2) {
port = this.ports["B"]; port = this._ports["B"];
} else { } else {
return; return;
} }
@ -94,9 +135,9 @@ class WeDo2Hub extends Hub {
} }
_parsePortMessage (data) { private _parsePortMessage (data: Buffer) {
let port = this._getPortForPortNumber(data[0]); const port = this._getPortForPortNumber(data[0]);
if (!port) { if (!port) {
return; return;
@ -108,26 +149,15 @@ class WeDo2Hub extends Hub {
} }
_activatePortDevice (port, type, mode, format, callback) { private _parseSensorMessage (data: Buffer) {
const characteristic = this._characteristics[Consts.BLE.Characteristics.WeDo2.PORT_TYPE_WRITE];
if (characteristic) {
characteristic.write(Buffer.from([0x01, 0x02, port, type, mode, 0x01, 0x00, 0x00, 0x00, format, 0x01]), callback);
}
}
_deactivatePortDevice (port, type, mode, format, callback) {
const characteristic = this._characteristics[Consts.BLE.Characteristics.WeDo2.PORT_TYPE_WRITE];
if (characteristic) {
characteristic.write(Buffer.from([0x01, 0x02, port, type, mode, 0x01, 0x00, 0x00, 0x00, format, 0x00]), callback);
}
}
_parseSensorMessage (data) {
if (data[0] === 1) { if (data[0] === 1) {
/**
* Emits when a button is pressed.
* @event WeDo2Hub#button
* @param {number} state - A number representing one of the button state consts.
*/
this.emit("button", Consts.Button.PRESSED); this.emit("button", Consts.Button.PRESSED);
return; return;
} else if (data[0] === 0) { } else if (data[0] === 0) {
@ -135,7 +165,7 @@ class WeDo2Hub extends Hub {
return; return;
} }
let port = this._getPortForPortNumber(data[1]); const port = this._getPortForPortNumber(data[1]);
if (!port) { if (!port) {
return; return;
@ -149,12 +179,24 @@ class WeDo2Hub extends Hub {
if (data[3] === 1) { if (data[3] === 1) {
distance = data[2] + 255; distance = data[2] + 255;
} }
/**
* Emits when a distance sensor is activated.
* @event WeDo2Hub#distance
* @param {string} port
* @param {number} distance - Distance, in millimeters.
*/
this.emit("distance", port.id, distance * 10); this.emit("distance", port.id, distance * 10);
break; break;
} }
case Consts.Devices.BOOST_DISTANCE: case Consts.Devices.BOOST_DISTANCE:
{ {
let distance = data[2]; const distance = data[2];
/**
* Emits when a color sensor is activated.
* @event WeDo2Hub#color
* @param {string} port
* @param {number} color - A number representing one of the LED color consts.
*/
this.emit("color", port.id, distance); this.emit("color", port.id, distance);
break; break;
} }
@ -168,12 +210,25 @@ class WeDo2Hub extends Hub {
if (this._lastTiltY > 100) { if (this._lastTiltY > 100) {
this._lastTiltY = -(255 - this._lastTiltY); this._lastTiltY = -(255 - this._lastTiltY);
} }
/**
* Emits when a tilt sensor is activated.
* @event WeDo2Hub#tilt
* @param {string} port
* @param {number} x
* @param {number} y
*/
this.emit("tilt", port.id, this._lastTiltX, this._lastTiltY); this.emit("tilt", port.id, this._lastTiltX, this._lastTiltY);
break; break;
} }
case Consts.Devices.BOOST_INTERACTIVE_MOTOR: case Consts.Devices.BOOST_INTERACTIVE_MOTOR:
{ {
const rotation = data.readInt32LE(2); const rotation = data.readInt32LE(2);
/**
* Emits when a rotation sensor is activated.
* @event WeDo2Hub#rotate
* @param {string} port
* @param {number} rotation
*/
this.emit("rotate", port.id, rotation); this.emit("rotate", port.id, rotation);
} }
} }
@ -183,6 +238,3 @@ class WeDo2Hub extends Hub {
} }
module.exports = WeDo2Hub;