From ea6b23b0fa86ebb56e267afc9181a066bb26b630 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Bonnargent?= Date: Mon, 22 Jun 2020 22:40:08 +0200 Subject: [PATCH 01/14] Autoparsing POC --- src/consts.ts | 19 +++++++ src/devices/absolutemotor.ts | 4 ++ src/devices/colordistancesensor.ts | 4 ++ src/devices/device.ts | 57 ++++++++++++++++++- src/devices/duplotrainbasecolorsensor.ts | 4 ++ src/devices/duplotrainbasespeedometer.ts | 4 ++ src/devices/motionsensor.ts | 4 ++ src/devices/movehubtiltsensor.ts | 4 ++ src/devices/remotecontrolbutton.ts | 4 ++ src/devices/tachomotor.ts | 4 ++ src/devices/techniccolorsensor.ts | 4 ++ src/devices/technicdistancesensor.ts | 4 ++ src/devices/technicforcesensor.ts | 4 ++ .../technicmediumhubaccelerometersensor.ts | 4 ++ src/devices/technicmediumhubgyrosensor.ts | 4 ++ src/devices/technicmediumhubtiltsensor.ts | 4 ++ src/devices/tiltsensor.ts | 4 ++ src/hubs/basehub.ts | 47 +++++++++------ src/hubs/duplotrainbase.ts | 4 +- src/hubs/hub.ts | 4 +- src/hubs/lpf2hub.ts | 49 +++++++++++++--- src/hubs/movehub.ts | 4 +- src/hubs/remotecontrol.ts | 4 +- src/hubs/technicmediumhub.ts | 4 +- src/hubs/wedo2smarthub.ts | 4 +- src/interfaces.ts | 23 ++++++++ src/poweredup-browser.ts | 16 +++--- src/poweredup-node.ts | 16 +++--- 28 files changed, 259 insertions(+), 52 deletions(-) diff --git a/src/consts.ts b/src/consts.ts index d5395d44..29474c14 100644 --- a/src/consts.ts +++ b/src/consts.ts @@ -634,4 +634,23 @@ export enum PortInputFormatSetupSubCommand { } +/** + * @typedef ValueType + * @param {number} Int8 0x01 + * @param {number} Int16 0x02 + * @param {number} Int32 0x03 + * @param {number} Float 0x04 + */ +export enum ValueType { + Int8 = 0x00, + Int16 = 0x01, + Int32 = 0x02, + Float = 0x03, +} +export const ValueTypeSize = { + [ValueType.Int8]: 1, + [ValueType.Int16]: 2, + [ValueType.Int32]: 4, + [ValueType.Float]: 4, +} diff --git a/src/devices/absolutemotor.ts b/src/devices/absolutemotor.ts index 8ab47703..05501005 100644 --- a/src/devices/absolutemotor.ts +++ b/src/devices/absolutemotor.ts @@ -16,6 +16,10 @@ export class AbsoluteMotor extends TachoMotor { } public receive (message: Buffer) { + if (this.hub.autoParse) { + return super.receive(message); + } + const mode = this._mode; switch (mode) { diff --git a/src/devices/colordistancesensor.ts b/src/devices/colordistancesensor.ts index f7560be3..37d3c6b3 100644 --- a/src/devices/colordistancesensor.ts +++ b/src/devices/colordistancesensor.ts @@ -15,6 +15,10 @@ export class ColorDistanceSensor extends Device { } public receive (message: Buffer) { + if (this.hub.autoParse) { + return super.receive(message); + } + const mode = this._mode; switch (mode) { diff --git a/src/devices/device.ts b/src/devices/device.ts index dfd86475..53f70ba3 100644 --- a/src/devices/device.ts +++ b/src/devices/device.ts @@ -1,6 +1,6 @@ import { EventEmitter } from "events"; -import { IDeviceInterface } from "../interfaces"; +import { IDeviceInterface, IMode } from "../interfaces"; import * as Consts from "../consts"; @@ -13,10 +13,12 @@ export class Device extends EventEmitter { public autoSubscribe: boolean = true; public values: {[event: string]: any} = {}; + protected _modes: IMode[] = []; protected _mode: number | undefined; protected _busy: boolean = false; protected _finished: (() => void) | undefined; + private _ready: boolean = false; private _hub: IDeviceInterface; private _portId: number; private _connected: boolean = true; @@ -37,7 +39,7 @@ export class Device extends EventEmitter { this._isVirtualPort = this.hub.isPortVirtual(portId); const eventAttachListener = (event: string) => { - if (event === "detach") { + if (event === "detach" || !this._ready) { return; } if (this.autoSubscribe) { @@ -64,6 +66,11 @@ export class Device extends EventEmitter { this.hub.on("newListener", eventAttachListener); this.on("newListener", eventAttachListener); this.hub.on("detach", deviceDetachListener); + + if (!this.hub.autoParse) { + this._ready = true; + this.emit('ready'); + } } /** @@ -126,6 +133,14 @@ export class Device extends EventEmitter { return this._isVirtualPort; } + /** + * @readonly + * @property {string[]} events List of availlable events. + */ + public get events () { + return Object.keys(this._modeMap); + } + public writeDirect (mode: number, data: Buffer) { if (this.isWeDo2SmartHub) { return this.send(Buffer.concat([Buffer.from([this.portId, 0x01, 0x02]), data]), Consts.BLECharacteristic.WEDO2_MOTOR_VALUE_WRITE); @@ -153,6 +168,33 @@ export class Device extends EventEmitter { public receive (message: Buffer) { this.notify("receive", { message }); + + const mode = this._mode; + if (mode === undefined) { + return; + } + const { name, values } = this._modes[mode]; + const valueSize = Consts.ValueTypeSize[values.type]; + const data = []; + + for(let v = 0; v < values.count; v++) { + const offset = 4 + v * valueSize; + switch(values.type) { + case Consts.ValueType.Int8: + data.push(message.readInt8(offset)); + break; + case Consts.ValueType.Int16: + data.push(message.readInt16LE(offset)); + break; + case Consts.ValueType.Int32: + data.push(message.readInt32LE(offset)); + break; + case Consts.ValueType.Float: + data.push(message.readFloatLE(offset)); + break; + } + } + this.notify(name, data); } public notify (event: string, values: any) { @@ -192,4 +234,15 @@ export class Device extends EventEmitter { } } + public setModes(modes: IMode[]) { + this._modes = modes; + + this._modeMap = modes.reduce((map: {[name: string]: number}, mode, index) => { + map[mode.name] = index; + return map; + }, {}); + + this._ready = true; + this.emit('ready'); + } } diff --git a/src/devices/duplotrainbasecolorsensor.ts b/src/devices/duplotrainbasecolorsensor.ts index 40183657..1eed822b 100644 --- a/src/devices/duplotrainbasecolorsensor.ts +++ b/src/devices/duplotrainbasecolorsensor.ts @@ -15,6 +15,10 @@ export class DuploTrainBaseColorSensor extends Device { } public receive (message: Buffer) { + if (this.hub.autoParse) { + return super.receive(message); + } + const mode = this._mode; switch (mode) { diff --git a/src/devices/duplotrainbasespeedometer.ts b/src/devices/duplotrainbasespeedometer.ts index d45b9d78..e58bd393 100644 --- a/src/devices/duplotrainbasespeedometer.ts +++ b/src/devices/duplotrainbasespeedometer.ts @@ -15,6 +15,10 @@ export class DuploTrainBaseSpeedometer extends Device { } public receive (message: Buffer) { + if (this.hub.autoParse) { + return super.receive(message); + } + const mode = this._mode; switch (mode) { diff --git a/src/devices/motionsensor.ts b/src/devices/motionsensor.ts index a46e3a76..6866527d 100644 --- a/src/devices/motionsensor.ts +++ b/src/devices/motionsensor.ts @@ -15,6 +15,10 @@ export class MotionSensor extends Device { } public receive (message: Buffer) { + if (this.hub.autoParse) { + return super.receive(message); + } + const mode = this._mode; switch (mode) { diff --git a/src/devices/movehubtiltsensor.ts b/src/devices/movehubtiltsensor.ts index cdc002a9..8830abee 100644 --- a/src/devices/movehubtiltsensor.ts +++ b/src/devices/movehubtiltsensor.ts @@ -15,6 +15,10 @@ export class MoveHubTiltSensor extends Device { } public receive (message: Buffer) { + if (this.hub.autoParse) { + return super.receive(message); + } + const mode = this._mode; switch (mode) { diff --git a/src/devices/remotecontrolbutton.ts b/src/devices/remotecontrolbutton.ts index c2cd644b..32b95b16 100644 --- a/src/devices/remotecontrolbutton.ts +++ b/src/devices/remotecontrolbutton.ts @@ -15,6 +15,10 @@ export class RemoteControlButton extends Device { } public receive (message: Buffer) { + if (this.hub.autoParse) { + return super.receive(message); + } + const mode = this._mode; switch (mode) { diff --git a/src/devices/tachomotor.ts b/src/devices/tachomotor.ts index 6650d064..c5e79027 100644 --- a/src/devices/tachomotor.ts +++ b/src/devices/tachomotor.ts @@ -21,6 +21,10 @@ export class TachoMotor extends BasicMotor { } public receive (message: Buffer) { + if (this.hub.autoParse) { + return super.receive(message); + } + const mode = this._mode; switch (mode) { diff --git a/src/devices/techniccolorsensor.ts b/src/devices/techniccolorsensor.ts index 3ecee93c..a77b47c8 100644 --- a/src/devices/techniccolorsensor.ts +++ b/src/devices/techniccolorsensor.ts @@ -15,6 +15,10 @@ export class TechnicColorSensor extends Device { } public receive (message: Buffer) { + if (this.hub.autoParse) { + return super.receive(message); + } + const mode = this._mode; switch (mode) { diff --git a/src/devices/technicdistancesensor.ts b/src/devices/technicdistancesensor.ts index 45500693..862d0ee1 100644 --- a/src/devices/technicdistancesensor.ts +++ b/src/devices/technicdistancesensor.ts @@ -15,6 +15,10 @@ export class TechnicDistanceSensor extends Device { } public receive (message: Buffer) { + if (this.hub.autoParse) { + return super.receive(message); + } + const mode = this._mode; switch (mode) { diff --git a/src/devices/technicforcesensor.ts b/src/devices/technicforcesensor.ts index d2f01aa1..e555fcc1 100644 --- a/src/devices/technicforcesensor.ts +++ b/src/devices/technicforcesensor.ts @@ -15,6 +15,10 @@ export class TechnicForceSensor extends Device { } public receive (message: Buffer) { + if (this.hub.autoParse) { + return super.receive(message); + } + const mode = this._mode; switch (mode) { diff --git a/src/devices/technicmediumhubaccelerometersensor.ts b/src/devices/technicmediumhubaccelerometersensor.ts index 092a63f4..ef46ce22 100644 --- a/src/devices/technicmediumhubaccelerometersensor.ts +++ b/src/devices/technicmediumhubaccelerometersensor.ts @@ -15,6 +15,10 @@ export class TechnicMediumHubAccelerometerSensor extends Device { } public receive (message: Buffer) { + if (this.hub.autoParse) { + return super.receive(message); + } + const mode = this._mode; switch (mode) { diff --git a/src/devices/technicmediumhubgyrosensor.ts b/src/devices/technicmediumhubgyrosensor.ts index ac994f07..961275e7 100644 --- a/src/devices/technicmediumhubgyrosensor.ts +++ b/src/devices/technicmediumhubgyrosensor.ts @@ -15,6 +15,10 @@ export class TechnicMediumHubGyroSensor extends Device { } public receive (message: Buffer) { + if (this.hub.autoParse) { + return super.receive(message); + } + const mode = this._mode; switch (mode) { diff --git a/src/devices/technicmediumhubtiltsensor.ts b/src/devices/technicmediumhubtiltsensor.ts index b421a724..1fefcb1c 100644 --- a/src/devices/technicmediumhubtiltsensor.ts +++ b/src/devices/technicmediumhubtiltsensor.ts @@ -15,6 +15,10 @@ export class TechnicMediumHubTiltSensor extends Device { } public receive (message: Buffer) { + if (this.hub.autoParse) { + return super.receive(message); + } + const mode = this._mode; switch (mode) { diff --git a/src/devices/tiltsensor.ts b/src/devices/tiltsensor.ts index 0c38b559..bd779388 100644 --- a/src/devices/tiltsensor.ts +++ b/src/devices/tiltsensor.ts @@ -15,6 +15,10 @@ export class TiltSensor extends Device { } public receive (message: Buffer) { + if (this.hub.autoParse) { + return super.receive(message); + } + const mode = this._mode; switch (mode) { diff --git a/src/hubs/basehub.ts b/src/hubs/basehub.ts index 025b6dc2..280ecf9f 100644 --- a/src/hubs/basehub.ts +++ b/src/hubs/basehub.ts @@ -1,6 +1,6 @@ import { EventEmitter } from "events"; -import { IBLEAbstraction } from "../interfaces"; +import { IBLEAbstraction, IMode } from "../interfaces"; import { ColorDistanceSensor } from "../devices/colordistancesensor"; import { CurrentSensor } from "../devices/currentsensor"; @@ -47,6 +47,8 @@ const debug = Debug("basehub"); export class BaseHub extends EventEmitter { protected _attachedDevices: {[portId: number]: Device} = {}; + protected _autoParse: boolean = false; + protected _devicesModes: {[portId: number]: IMode[]} = {}; protected _name: string = ""; protected _firmwareVersion: string = "0.0.00.0000"; @@ -62,12 +64,13 @@ export class BaseHub extends EventEmitter { private _type: Consts.HubType; private _attachCallbacks: ((device: Device) => boolean)[] = []; - constructor (bleDevice: IBLEAbstraction, portMap: {[portName: string]: number} = {}, type: Consts.HubType = Consts.HubType.UNKNOWN) { + constructor (bleDevice: IBLEAbstraction, portMap: {[portName: string]: number} = {}, type: Consts.HubType = Consts.HubType.UNKNOWN, autoParse: boolean = false) { super(); this.setMaxListeners(23); // Technic Medium Hub has 9 built in devices + 4 external ports. Node.js throws a warning after 10 attached event listeners. this._type = type; this._bleDevice = bleDevice; this._portMap = Object.assign({}, portMap); + this._autoParse = autoParse; bleDevice.on("disconnect", () => { /** * Emits when the hub is disconnected. @@ -159,6 +162,15 @@ export class BaseHub extends EventEmitter { } + /** + * @readonly + * @property {boolean} autoParse Load modes from port information messages + */ + public get autoParse () { + return this._autoParse; + } + + /** * Connect to the Hub. * @method Hub#connect @@ -333,21 +345,24 @@ export class BaseHub extends EventEmitter { protected _attachDevice (device: Device) { this._attachedDevices[device.portId] = device; - /** - * Emits when a device is attached to the Hub. - * @event Hub#attach - * @param {Device} device - */ - this.emit("attach", device); - debug(`Attached device type ${device.type} (${Consts.DeviceTypeNames[device.type]}) on port ${device.portName} (${device.portId})`); - - let i = this._attachCallbacks.length; - while (i--) { - const callback = this._attachCallbacks[i]; - if (callback(device)) { - this._attachCallbacks.splice(i, 1); + + device.on('ready', () => { + /** + * Emits when a device is attached to the Hub. + * @event Hub#attach + * @param {Device} device + */ + this.emit("attach", device); + debug(`Attached device type ${device.type} (${Consts.DeviceTypeNames[device.type]}) on port ${device.portName} (${device.portId})`); + + let i = this._attachCallbacks.length; + while (i--) { + const callback = this._attachCallbacks[i]; + if (callback(device)) { + this._attachCallbacks.splice(i, 1); + } } - } + }) } diff --git a/src/hubs/duplotrainbase.ts b/src/hubs/duplotrainbase.ts index 2002f795..823b1e16 100644 --- a/src/hubs/duplotrainbase.ts +++ b/src/hubs/duplotrainbase.ts @@ -30,8 +30,8 @@ export class DuploTrainBase extends LPF2Hub { } - constructor (device: IBLEAbstraction) { - super(device, PortMap, Consts.HubType.DUPLO_TRAIN_BASE); + constructor (device: IBLEAbstraction, autoParse: boolean) { + super(device, PortMap, Consts.HubType.DUPLO_TRAIN_BASE, autoParse); debug("Discovered Duplo Train Base"); } diff --git a/src/hubs/hub.ts b/src/hubs/hub.ts index e16ce13d..2aa83789 100644 --- a/src/hubs/hub.ts +++ b/src/hubs/hub.ts @@ -33,8 +33,8 @@ export class Hub extends LPF2Hub { protected _currentPort = 0x3b; - constructor (device: IBLEAbstraction) { - super(device, PortMap, Consts.HubType.HUB); + constructor (device: IBLEAbstraction, autoParse: boolean) { + super(device, PortMap, Consts.HubType.HUB, autoParse); debug("Discovered Powered UP Hub"); } diff --git a/src/hubs/lpf2hub.ts b/src/hubs/lpf2hub.ts index 7f93ac9c..9ba5521f 100644 --- a/src/hubs/lpf2hub.ts +++ b/src/hubs/lpf2hub.ts @@ -254,6 +254,9 @@ export class LPF2Hub extends BaseHub { const hwVersion = decodeVersion(message.readInt32LE(7)); const swVersion = decodeVersion(message.readInt32LE(11)); modeInfoDebug(`Port ${toHex(portId)}, hardware version ${hwVersion}, software version ${swVersion}`); + } + + if (modeInfoDebug.enabled || this._autoParse) { await this._sendPortInformationRequest(portId); } @@ -311,7 +314,16 @@ export class LPF2Hub extends BaseHub { const output = toBin(message.readUInt16LE(9), count); modeInfoDebug(`Port ${toHex(port)}, total modes ${count}, input modes ${input}, output modes ${output}`); + this._devicesModes[port] = new Array(+count); + for (let i = 0; i < count; i++) { + this._devicesModes[port][i] = { + name: '', + raw: { min: 0, max: 255 }, + pct: { min: 0, max: 100 }, + si: { min: 0, max: 255, symbol: '' }, + values: { count: 1, type: Consts.ValueType.Int8 }, + }; await this._sendModeInformationRequest(port, i, 0x00); // Mode Name await this._sendModeInformationRequest(port, i, 0x01); // RAW Range await this._sendModeInformationRequest(port, i, 0x02); // PCT Range @@ -328,31 +340,52 @@ export class LPF2Hub extends BaseHub { private _parseModeInformationResponse (message: Buffer) { - const port = toHex(message[3]); + const port = message[3]; + const portHex = toHex(port); const mode = message[4]; const type = message[5]; switch (type) { case 0x00: // Mode Name - modeInfoDebug(`Port ${port}, mode ${mode}, name ${message.slice(6, message.length).toString()}`); + const name = message.slice(6, message.length).toString().replace(/\0/g, ''); + modeInfoDebug(`Port ${portHex}, mode ${mode}, name ${name}`); + this._devicesModes[port][mode].name=name; break; case 0x01: // RAW Range - modeInfoDebug(`Port ${port}, mode ${mode}, RAW min ${message.readFloatLE(6)}, max ${message.readFloatLE(10)}`); + modeInfoDebug(`Port ${portHex}, mode ${mode}, RAW min ${message.readFloatLE(6)}, max ${message.readFloatLE(10)}`); + this._devicesModes[port][mode].raw.min=message.readFloatLE(6); + this._devicesModes[port][mode].raw.max=message.readFloatLE(10); break; case 0x02: // PCT Range - modeInfoDebug(`Port ${port}, mode ${mode}, PCT min ${message.readFloatLE(6)}, max ${message.readFloatLE(10)}`); + modeInfoDebug(`Port ${portHex}, mode ${mode}, PCT min ${message.readFloatLE(6)}, max ${message.readFloatLE(10)}`); + this._devicesModes[port][mode].pct.min=message.readFloatLE(6); + this._devicesModes[port][mode].pct.max=message.readFloatLE(10); break; case 0x03: // SI Range - modeInfoDebug(`Port ${port}, mode ${mode}, SI min ${message.readFloatLE(6)}, max ${message.readFloatLE(10)}`); + modeInfoDebug(`Port ${portHex}, mode ${mode}, SI min ${message.readFloatLE(6)}, max ${message.readFloatLE(10)}`); + this._devicesModes[port][mode].si.min=message.readFloatLE(6); + this._devicesModes[port][mode].si.max=message.readFloatLE(10); break; case 0x04: // SI Symbol - modeInfoDebug(`Port ${port}, mode ${mode}, SI symbol ${message.slice(6, message.length).toString()}`); + const symbol = message.slice(6, message.length).toString().replace(/\0/g, ''); + modeInfoDebug(`Port ${portHex}, mode ${mode}, SI symbol ${symbol}`); + this._devicesModes[port][mode].si.symbol=symbol; break; case 0x80: // Value Format const numValues = message[6]; - const dataType = ["8bit", "16bit", "32bit", "float"][message[7]]; + const dataType = message[7]; const totalFigures = message[8]; const decimals = message[9]; - modeInfoDebug(`Port ${port}, mode ${mode}, Value ${numValues} x ${dataType}, Decimal format ${totalFigures}.${decimals}`); + modeInfoDebug(`Port ${portHex}, mode ${mode}, Value ${numValues} x ${dataType}, Decimal format ${totalFigures}.${decimals}`); + this._devicesModes[port][mode].values.count=numValues; + this._devicesModes[port][mode].values.type=dataType; + + if (this._autoParse && mode === this._devicesModes[port].length - 1) { + const device = this._getDeviceByPortId(port); + + if (device) { + device.setModes(this._devicesModes[port]); + } + } } } diff --git a/src/hubs/movehub.ts b/src/hubs/movehub.ts index d2902702..35085172 100644 --- a/src/hubs/movehub.ts +++ b/src/hubs/movehub.ts @@ -31,8 +31,8 @@ export class MoveHub extends LPF2Hub { ); } - constructor (device: IBLEAbstraction) { - super(device, PortMap, Consts.HubType.MOVE_HUB); + constructor (device: IBLEAbstraction, autoParse: boolean) { + super(device, PortMap, Consts.HubType.MOVE_HUB, autoParse); debug("Discovered Move Hub"); } diff --git a/src/hubs/remotecontrol.ts b/src/hubs/remotecontrol.ts index cf01117a..bc838f74 100644 --- a/src/hubs/remotecontrol.ts +++ b/src/hubs/remotecontrol.ts @@ -31,8 +31,8 @@ export class RemoteControl extends LPF2Hub { } - constructor (device: IBLEAbstraction) { - super(device, PortMap, Consts.HubType.REMOTE_CONTROL); + constructor (device: IBLEAbstraction, autoParse: boolean) { + super(device, PortMap, Consts.HubType.REMOTE_CONTROL, autoParse); debug("Discovered Powered UP Remote"); } diff --git a/src/hubs/technicmediumhub.ts b/src/hubs/technicmediumhub.ts index f7baa841..fb30276e 100644 --- a/src/hubs/technicmediumhub.ts +++ b/src/hubs/technicmediumhub.ts @@ -30,8 +30,8 @@ export class TechnicMediumHub extends LPF2Hub { ); } - constructor (device: IBLEAbstraction) { - super(device, PortMap, Consts.HubType.TECHNIC_MEDIUM_HUB); + constructor (device: IBLEAbstraction, autoParse: boolean) { + super(device, PortMap, Consts.HubType.TECHNIC_MEDIUM_HUB, autoParse); debug("Discovered Control+ Hub"); } diff --git a/src/hubs/wedo2smarthub.ts b/src/hubs/wedo2smarthub.ts index 9c90a14d..323fe8d2 100644 --- a/src/hubs/wedo2smarthub.ts +++ b/src/hubs/wedo2smarthub.ts @@ -34,8 +34,8 @@ export class WeDo2SmartHub extends BaseHub { private _lastTiltY: number = 0; - constructor (device: IBLEAbstraction) { - super(device, PortMap, Consts.HubType.WEDO2_SMART_HUB); + constructor (device: IBLEAbstraction, autoParse: boolean) { + super(device, PortMap, Consts.HubType.WEDO2_SMART_HUB, autoParse); debug("Discovered WeDo 2.0 Smart Hub"); } diff --git a/src/interfaces.ts b/src/interfaces.ts index c06fd8df..58182698 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -18,9 +18,32 @@ export interface IBLEAbstraction extends EventEmitter { export interface IDeviceInterface extends EventEmitter { type: Consts.HubType; + autoParse: boolean; getPortNameForPortId: (portId: number) => string | undefined; send: (message: Buffer, uuid: string) => Promise; subscribe: (portId: number, deviceType: number, mode: number) => void; isPortVirtual: (portId: number) => boolean; sleep: (delay: number) => Promise; + } + +export interface IMode { + name: string; + raw: { + min: number; + max: number; + }; + pct: { + min: number; + max: number; + }; + si: { + min: number; + max: number; + symbol: string; + }; + values: { + count: number; + type: Consts.ValueType; + }; +} \ No newline at end of file diff --git a/src/poweredup-browser.ts b/src/poweredup-browser.ts index 55719bd6..14e42795 100644 --- a/src/poweredup-browser.ts +++ b/src/poweredup-browser.ts @@ -25,11 +25,13 @@ export class PoweredUP extends EventEmitter { private _connectedHubs: {[uuid: string]: BaseHub} = {}; + private _autoParse: boolean = false; - constructor () { + constructor ({ autoParse = false} = {}) { super(); this._discoveryEventHandler = this._discoveryEventHandler.bind(this); + this._autoParse = autoParse; } @@ -195,22 +197,22 @@ export class PoweredUP extends EventEmitter { switch (hubType) { case Consts.HubType.WEDO2_SMART_HUB: - hub = new WeDo2SmartHub(device); + hub = new WeDo2SmartHub(device, this._autoParse); break; case Consts.HubType.MOVE_HUB: - hub = new MoveHub(device); + hub = new MoveHub(device, this._autoParse); break; case Consts.HubType.HUB: - hub = new Hub(device); + hub = new Hub(device, this._autoParse); break; case Consts.HubType.REMOTE_CONTROL: - hub = new RemoteControl(device); + hub = new RemoteControl(device, this._autoParse); break; case Consts.HubType.DUPLO_TRAIN_BASE: - hub = new DuploTrainBase(device); + hub = new DuploTrainBase(device, this._autoParse); break; case Consts.HubType.TECHNIC_MEDIUM_HUB: - hub = new TechnicMediumHub(device); + hub = new TechnicMediumHub(device, this._autoParse); break; default: return; diff --git a/src/poweredup-node.ts b/src/poweredup-node.ts index e000adeb..6aebbcbb 100644 --- a/src/poweredup-node.ts +++ b/src/poweredup-node.ts @@ -46,11 +46,13 @@ export class PoweredUP extends EventEmitter { private _connectedHubs: {[uuid: string]: BaseHub} = {}; + private _autoParse: boolean = false; - constructor () { + constructor ({ autoParse = false} = {}) { super(); this._discoveryEventHandler = this._discoveryEventHandler.bind(this); + this._autoParse = autoParse; } @@ -153,17 +155,17 @@ export class PoweredUP extends EventEmitter { let hub: BaseHub; if (WeDo2SmartHub.IsWeDo2SmartHub(peripheral)) { - hub = new WeDo2SmartHub(device); + hub = new WeDo2SmartHub(device, this._autoParse); } else if (MoveHub.IsMoveHub(peripheral)) { - hub = new MoveHub(device); + hub = new MoveHub(device, this._autoParse); } else if (Hub.IsHub(peripheral)) { - hub = new Hub(device); + hub = new Hub(device, this._autoParse); } else if (RemoteControl.IsRemoteControl(peripheral)) { - hub = new RemoteControl(device); + hub = new RemoteControl(device, this._autoParse); } else if (DuploTrainBase.IsDuploTrainBase(peripheral)) { - hub = new DuploTrainBase(device); + hub = new DuploTrainBase(device, this._autoParse); } else if (TechnicMediumHub.IsTechnicMediumHub(peripheral)) { - hub = new TechnicMediumHub(device); + hub = new TechnicMediumHub(device, this._autoParse); } else { return; } From d4ea7cb55fb26a3dbfa99506b5d3eed92880f18f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Bonnargent?= Date: Tue, 23 Jun 2020 23:12:28 +0200 Subject: [PATCH 02/14] Add autoparse output write --- src/devices/device.ts | 42 ++++++++++++++++++++++++++++++++++++++++-- src/hubs/lpf2hub.ts | 2 ++ src/interfaces.ts | 2 ++ 3 files changed, 44 insertions(+), 2 deletions(-) diff --git a/src/devices/device.ts b/src/devices/device.ts index 53f70ba3..bd33ffd8 100644 --- a/src/devices/device.ts +++ b/src/devices/device.ts @@ -135,10 +135,18 @@ export class Device extends EventEmitter { /** * @readonly - * @property {string[]} events List of availlable events. + * @property {string[]} events List of availlable events (input modes). */ public get events () { - return Object.keys(this._modeMap); + return this._modes.filter(mode => mode.input).map(({ name }) => name); + } + + /** + * @readonly + * @property {string[]} writeModes List of availlable write (output modes). + */ + public get writeModes () { + return this._modes.filter(mode => mode.output).map(({ name }) => name); } public writeDirect (mode: number, data: Buffer) { @@ -149,6 +157,36 @@ export class Device extends EventEmitter { } } + public autoparseWriteDirect (mode: string, data: number[]) { + if (!this.hub.autoParse) return; + const modeId = this._modeMap[mode]; + if (modeId === undefined) return; + + const { values } = this._modes[modeId]; + const valueSize = Consts.ValueTypeSize[values.type]; + + const buf = Buffer.alloc(values.count * valueSize); + for(let v = 0; v < values.count; v++) { + const offset = v * valueSize; + switch(values.type) { + case Consts.ValueType.Int8: + buf.writeInt8(data[v] || 0, offset); + break; + case Consts.ValueType.Int16: + buf.writeInt16LE(data[v] || 0, offset); + break; + case Consts.ValueType.Int32: + buf.writeInt32LE(data[v] || 0, offset); + break; + case Consts.ValueType.Float: + buf.writeFloatLE(data[v] || 0, offset); + break; + } + } + + return this.send(Buffer.concat([Buffer.from([0x81, this.portId, 0x11, 0x51, modeId]), buf]), Consts.BLECharacteristic.LPF2_ALL); + } + public send (data: Buffer, characteristic: string = Consts.BLECharacteristic.LPF2_ALL) { this._ensureConnected(); return this.hub.send(data, characteristic); diff --git a/src/hubs/lpf2hub.ts b/src/hubs/lpf2hub.ts index 9ba5521f..a1ea5291 100644 --- a/src/hubs/lpf2hub.ts +++ b/src/hubs/lpf2hub.ts @@ -319,6 +319,8 @@ export class LPF2Hub extends BaseHub { for (let i = 0; i < count; i++) { this._devicesModes[port][i] = { name: '', + input: input[i] === '1', + output: output[i] === '1', raw: { min: 0, max: 255 }, pct: { min: 0, max: 100 }, si: { min: 0, max: 255, symbol: '' }, diff --git a/src/interfaces.ts b/src/interfaces.ts index 58182698..e0237cb0 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -29,6 +29,8 @@ export interface IDeviceInterface extends EventEmitter { export interface IMode { name: string; + input: boolean; + output: boolean; raw: { min: number; max: number; From 299963b5ce7a2a43f11730c4fe38b5a40d30cf68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Bonnargent?= Date: Wed, 24 Jun 2020 10:15:19 +0200 Subject: [PATCH 03/14] Improve autoparseWriteDirect --- src/devices/device.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/devices/device.ts b/src/devices/device.ts index bd33ffd8..5301c964 100644 --- a/src/devices/device.ts +++ b/src/devices/device.ts @@ -157,7 +157,7 @@ export class Device extends EventEmitter { } } - public autoparseWriteDirect (mode: string, data: number[]) { + public autoparseWriteDirect (mode: string, ...data: number[]) { if (!this.hub.autoParse) return; const modeId = this._modeMap[mode]; if (modeId === undefined) return; @@ -184,7 +184,7 @@ export class Device extends EventEmitter { } } - return this.send(Buffer.concat([Buffer.from([0x81, this.portId, 0x11, 0x51, modeId]), buf]), Consts.BLECharacteristic.LPF2_ALL); + return this.writeDirect(modeId, buf); } public send (data: Buffer, characteristic: string = Consts.BLECharacteristic.LPF2_ALL) { From b4f18802afceb0a38170b2690536fb8ed61a552d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Bonnargent?= Date: Wed, 24 Jun 2020 10:25:01 +0200 Subject: [PATCH 04/14] Enable autoparse for unknown devices --- src/devices/device.ts | 8 ++++++-- src/hubs/lpf2hub.ts | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/devices/device.ts b/src/devices/device.ts index 5301c964..e53a67d0 100644 --- a/src/devices/device.ts +++ b/src/devices/device.ts @@ -67,7 +67,7 @@ export class Device extends EventEmitter { this.on("newListener", eventAttachListener); this.hub.on("detach", deviceDetachListener); - if (!this.hub.autoParse) { + if (!this.autoparse) { this._ready = true; this.emit('ready'); } @@ -158,7 +158,7 @@ export class Device extends EventEmitter { } public autoparseWriteDirect (mode: string, ...data: number[]) { - if (!this.hub.autoParse) return; + if (!this.autoparse) return; const modeId = this._modeMap[mode]; if (modeId === undefined) return; @@ -283,4 +283,8 @@ export class Device extends EventEmitter { this._ready = true; this.emit('ready'); } + + private get autoparse() { + return this.hub.autoParse || this._type === Consts.DeviceType.UNKNOWN; + } } diff --git a/src/hubs/lpf2hub.ts b/src/hubs/lpf2hub.ts index a1ea5291..98caf8eb 100644 --- a/src/hubs/lpf2hub.ts +++ b/src/hubs/lpf2hub.ts @@ -256,7 +256,7 @@ export class LPF2Hub extends BaseHub { modeInfoDebug(`Port ${toHex(portId)}, hardware version ${hwVersion}, software version ${swVersion}`); } - if (modeInfoDebug.enabled || this._autoParse) { + if (modeInfoDebug.enabled || this._autoParse || !(message[5] in Consts.DeviceType)) { await this._sendPortInformationRequest(portId); } From 57c7baae892a64f3a64fe661a8ab930fe049fd25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Bonnargent?= Date: Thu, 25 Jun 2020 16:48:01 +0200 Subject: [PATCH 05/14] Add value normalization --- src/devices/device.ts | 11 +++++++++-- src/utils.ts | 20 ++++++++++++++------ 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/src/devices/device.ts b/src/devices/device.ts index e53a67d0..35bcd45e 100644 --- a/src/devices/device.ts +++ b/src/devices/device.ts @@ -4,6 +4,8 @@ import { IDeviceInterface, IMode } from "../interfaces"; import * as Consts from "../consts"; +import { normalize } from "../utils"; + /** * @class Device * @extends EventEmitter @@ -211,7 +213,7 @@ export class Device extends EventEmitter { if (mode === undefined) { return; } - const { name, values } = this._modes[mode]; + const { name, raw, pct, si, values } = this._modes[mode]; const valueSize = Consts.ValueTypeSize[values.type]; const data = []; @@ -232,7 +234,12 @@ export class Device extends EventEmitter { break; } } - this.notify(name, data); + + this.notify(name, { + raw: data, + pct: data.map(value => normalize(value, {raw, out: pct})), + si: data.map(value => normalize(value, {raw, out: si})) + }); } public notify (event: string, values: any) { diff --git a/src/utils.ts b/src/utils.ts index 632214f1..23d83a3c 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -12,16 +12,24 @@ export const toBin = (value: number, length: number = 8) => { return value.toString(2).padStart(length, "0"); }; +export const clamp = (value:number, { min = -100, max = 100 } = {}) =>{ + return Math.max(Math.min(value, max), min); +} + +export const normalize = (value: number, { raw = {min: 0, max: 100}, out = { min: 0, max: 100}} = {}) => { + const ranges = { + raw: raw.max - raw.min, + out: out.max - out.min, + }; + + return (clamp(value, raw) - raw.min) / ranges.raw * ranges.out + out.min; +} + export const mapSpeed = (speed: number) => { if (speed === 127) { return 127; } - if (speed > 100) { - speed = 100; - } else if (speed < -100) { - speed = -100; - } - return speed; + return clamp(speed); }; export const decodeVersion = (version: number) => { From 45b0c203c53244b12b00ede5da8e78a8d96bd6fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Bonnargent?= Date: Sun, 28 Jun 2020 11:32:09 +0200 Subject: [PATCH 06/14] naming --- src/devices/device.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/devices/device.ts b/src/devices/device.ts index 35bcd45e..41b3733c 100644 --- a/src/devices/device.ts +++ b/src/devices/device.ts @@ -69,7 +69,7 @@ export class Device extends EventEmitter { this.on("newListener", eventAttachListener); this.hub.on("detach", deviceDetachListener); - if (!this.autoparse) { + if (!this.autoParse) { this._ready = true; this.emit('ready'); } @@ -159,8 +159,8 @@ export class Device extends EventEmitter { } } - public autoparseWriteDirect (mode: string, ...data: number[]) { - if (!this.autoparse) return; + public autoParseWriteDirect (mode: string, ...data: number[]) { + if (!this.autoParse) return; const modeId = this._modeMap[mode]; if (modeId === undefined) return; @@ -291,7 +291,7 @@ export class Device extends EventEmitter { this.emit('ready'); } - private get autoparse() { + private get autoParse() { return this.hub.autoParse || this._type === Consts.DeviceType.UNKNOWN; } } From 4edd5ebf5715b388a97acc3be32a875943983a1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Bonnargent?= Date: Sun, 28 Jun 2020 12:37:05 +0200 Subject: [PATCH 07/14] fix condition --- src/hubs/lpf2hub.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hubs/lpf2hub.ts b/src/hubs/lpf2hub.ts index 98caf8eb..baff4aba 100644 --- a/src/hubs/lpf2hub.ts +++ b/src/hubs/lpf2hub.ts @@ -256,7 +256,7 @@ export class LPF2Hub extends BaseHub { modeInfoDebug(`Port ${toHex(portId)}, hardware version ${hwVersion}, software version ${swVersion}`); } - if (modeInfoDebug.enabled || this._autoParse || !(message[5] in Consts.DeviceType)) { + if (modeInfoDebug.enabled || this._autoParse || deviceType !== Consts.DeviceType.UNKNOWN) { await this._sendPortInformationRequest(portId); } From 8c451f2c425f76fddd78891a9c72544dc43d70f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Bonnargent?= Date: Sun, 28 Jun 2020 23:07:01 +0200 Subject: [PATCH 08/14] Defer port messages to devices - also added all known modes definition --- src/devices/absolutemotor.ts | 86 ++++--- src/devices/basicmotor.ts | 18 +- src/devices/colordistancesensor.ts | 237 +++++++++++------- src/devices/currentsensor.ts | 74 +++--- src/devices/device.ts | 191 ++++++++++++-- src/devices/duplotrainbasecolorsensor.ts | 142 ++++++----- src/devices/duplotrainbasemotor.ts | 2 +- src/devices/duplotrainbasespeaker.ts | 2 +- src/devices/duplotrainbasespeedometer.ts | 57 ++--- src/devices/hubled.ts | 35 ++- src/devices/light.ts | 2 +- src/devices/mediumlinearmotor.ts | 2 +- src/devices/motionsensor.ts | 52 ++-- src/devices/movehubmediumlinearmotor.ts | 2 +- src/devices/movehubtiltsensor.ts | 112 +++++++-- src/devices/piezobuzzer.ts | 2 +- src/devices/remotecontrolbutton.ts | 90 ++++--- src/devices/simplemediumlinearmotor.ts | 2 +- src/devices/tachomotor.ts | 71 +++--- src/devices/techniccolorsensor.ts | 134 +++++----- src/devices/technicdistancesensor.ts | 113 ++++++--- src/devices/technicforcesensor.ts | 120 ++++----- src/devices/techniclargeangularmotor.ts | 4 +- src/devices/techniclargelinearmotor.ts | 2 +- src/devices/technicmediumangularmotor.ts | 6 +- .../technicmediumhubaccelerometersensor.ts | 80 +++--- src/devices/technicmediumhubgyrosensor.ts | 62 ++--- src/devices/technicmediumhubtiltsensor.ts | 70 +++--- src/devices/technicxlargelinearmotor.ts | 2 +- src/devices/tiltsensor.ts | 59 ++--- src/devices/trainmotor.ts | 2 +- src/devices/voltagesensor.ts | 76 +++--- src/hubs/basehub.ts | 10 +- src/hubs/lpf2hub.ts | 151 +---------- src/interfaces.ts | 8 +- 35 files changed, 1159 insertions(+), 919 deletions(-) diff --git a/src/devices/absolutemotor.ts b/src/devices/absolutemotor.ts index 05501005..05ffbd41 100644 --- a/src/devices/absolutemotor.ts +++ b/src/devices/absolutemotor.ts @@ -1,43 +1,64 @@ -import { TachoMotor } from "./tachomotor"; +import { TachoMotor, modes as TachoMotorModes } from "./tachomotor"; -import { IDeviceInterface } from "../interfaces"; +import { IDeviceInterface, IMode, IEventData } from "../interfaces"; import * as Consts from "../consts"; import { mapSpeed, normalizeAngle, roundAngleToNearest90 } from "../utils"; +export const modes = TachoMotorModes.concat([ + // POWER + // SPEED/speed + // POS/rotate + { + name: "absolute", // APOS + input: true, + output: true, + raw: { min: -360, max: 360 }, + pct: { min: -100, max: 100 }, + si: { min: -360, max: 360, symbol: "DEG" }, + values: { count: 1, type: Consts.ValueType.Int16 } + }, + { + name: "LOAD", + input: true, + output: true, + raw: { min: 0, max: 127 }, + pct: { min: 0, max: 100 }, + si: { min: 0, max: 127, symbol: "PCT" }, + values: { count: 1, type: Consts.ValueType.Int8 } + }, + { + name: "CALIB", + input: false, + output: false, + raw: { min: 0, max: 512 }, + pct: { min: 0, max: 100 }, + si: { min: 0, max: 512, symbol: "RAW" }, + values: { count: 3, type: Consts.ValueType.Int16 } + } +]) + /** * @class AbsoluteMotor * @extends TachoMotor */ export class AbsoluteMotor extends TachoMotor { - constructor (hub: IDeviceInterface, portId: number, modeMap: {[event: string]: number} = {}, type: Consts.DeviceType = Consts.DeviceType.UNKNOWN) { - super(hub, portId, Object.assign({}, modeMap, ModeMap), type); + constructor (hub: IDeviceInterface, portId: number, _modes: IMode[] = [], type: Consts.DeviceType = Consts.DeviceType.UNKNOWN) { + super(hub, portId, _modes.length > 0 ? _modes : modes, type); + + this._eventHandlers.rotate = (data: IEventData) => { + const [angle] = data.raw; + /** + * Emits when a the motors absolute position is changed. + * @event AbsoluteMotor#absolute + * @type {object} + * @param {number} absolute + */ + this.notify("absolute", { angle }); + }; } - public receive (message: Buffer) { - if (this.hub.autoParse) { - return super.receive(message); - } - - const mode = this._mode; - - switch (mode) { - case Mode.ABSOLUTE: - const angle = normalizeAngle(message.readInt16LE(this.isWeDo2SmartHub ? 2 : 4)); - /** - * Emits when a the motors absolute position is changed. - * @event AbsoluteMotor#absolute - * @type {object} - * @param {number} absolute - */ - this.notify("absolute", { angle }); - break; - default: - super.receive(message); - break; - } - } /** * Rotate a motor by a given angle. @@ -121,15 +142,4 @@ export class AbsoluteMotor extends TachoMotor { }); } - } - -export enum Mode { - ROTATION = 0x02, - ABSOLUTE = 0x03 -} - -export const ModeMap: {[event: string]: number} = { - "rotate": Mode.ROTATION, - "absolute": Mode.ABSOLUTE -}; diff --git a/src/devices/basicmotor.ts b/src/devices/basicmotor.ts index 619bce3c..cff33685 100644 --- a/src/devices/basicmotor.ts +++ b/src/devices/basicmotor.ts @@ -1,11 +1,23 @@ import { Device } from "./device"; -import { IDeviceInterface } from "../interfaces"; +import { IDeviceInterface, IMode } from "../interfaces"; import * as Consts from "../consts"; import { calculateRamp, mapSpeed } from "../utils"; +export const modes = [ + { + name: "POWER", // or LPF2-MOTOR + input: false, + output: true, + raw: { min: -100, max: 100 }, + pct: { min: -100, max: 100 }, + si: { min: -100, max: 100, symbol: "PCT" }, + values: { count: 1, type: Consts.ValueType.Int8 } + } +]; + /** * @class BasicMotor * @extends Device @@ -13,8 +25,8 @@ import { calculateRamp, mapSpeed } from "../utils"; export class BasicMotor extends Device { - constructor (hub: IDeviceInterface, portId: number, modeMap: {[event: string]: number}, type: Consts.DeviceType = Consts.DeviceType.UNKNOWN) { - super(hub, portId, modeMap, type); + constructor (hub: IDeviceInterface, portId: number, _modes: IMode[] = [], type: Consts.DeviceType = Consts.DeviceType.UNKNOWN) { + super(hub, portId, _modes.length > 0 ? _modes : modes, type); } diff --git a/src/devices/colordistancesensor.ts b/src/devices/colordistancesensor.ts index 9956a189..4d77fac8 100644 --- a/src/devices/colordistancesensor.ts +++ b/src/devices/colordistancesensor.ts @@ -1,6 +1,6 @@ import { Device } from "./device"; -import { IDeviceInterface } from "../interfaces"; +import { IDeviceInterface, IEventData } from "../interfaces"; import * as Consts from "../consts"; @@ -11,83 +11,150 @@ import * as Consts from "../consts"; export class ColorDistanceSensor extends Device { constructor (hub: IDeviceInterface, portId: number) { - super(hub, portId, ModeMap, Consts.DeviceType.COLOR_DISTANCE_SENSOR); - } - - public receive (message: Buffer) { - if (this.hub.autoParse) { - return super.receive(message); - } - - const mode = this._mode; - - switch (mode) { - case Mode.COLOR: - if (message[this.isWeDo2SmartHub ? 2 : 4] <= 10) { - const color = message[this.isWeDo2SmartHub ? 2 : 4]; - - /** - * Emits when a color sensor is activated. - * @event ColorDistanceSensor#color - * @type {object} - * @param {Color} color - */ - this.notify("color", { color }); - } - break; - - case Mode.DISTANCE: - if (this.isWeDo2SmartHub) { - break; - } - if (message[4] <= 10) { - let distance = Math.floor(message[4] * 25.4); - - if (distance < 0) { - distance = 0; - } - - /** - * Emits when a distance sensor is activated. - * @event ColorDistanceSensor#distance - * @type {object} - * @param {number} distance Distance, in millimeters. - */ - this.notify("distance", { distance }); - } - break; - - case Mode.COLOR_AND_DISTANCE: - if (this.isWeDo2SmartHub) { - break; - } - - let distance = message[5]; - const partial = message[7]; - - if (partial > 0) { - distance += 1.0 / partial; - } - - distance = Math.floor(distance * 25.4) - 20; - - /** - * A combined color and distance event, emits when the sensor is activated. - * @event ColorDistanceSensor#colorAndDistance - * @type {object} - * @param {Color} color - * @param {number} distance Distance, in millimeters. - */ - if (message[4] <= 10) { - const color = message[4]; - this.notify("colorAndDistance", { color, distance }); - } - break; - - } + const modes = [ + { + name: "color", // COLOR + input: true, + output: false, + raw: { min: 0, max: 10 }, + pct: { min: 0, max: 100 }, + si: { min: 0, max: 10, symbol: "IDX" }, + values: { count: 1, type: Consts.ValueType.Int8 } + }, + { + name: "distance", // PROX + input: true, + output: false, + raw: { min: 0, max: 10 }, + pct: { min: 0, max: 100 }, + si: { min: 0, max: 10, symbol: "DIS" }, + values: { count: 1, type: Consts.ValueType.Int8 } + }, + { + name: "COUNT", + input: false, + output: false, + raw: { min: 0, max: 100 }, + pct: { min: 0, max: 100 }, + si: { min: 0, max: 100, symbol: "CNT" }, + values: { count: 1, type: Consts.ValueType.Int32 } + }, + { + name: "REFLT", + input: true, + output: false, + raw: { min: 0, max: 100 }, + pct: { min: 0, max: 100 }, + si: { min: 0, max: 100, symbol: "PCT" }, + values: { count: 1, type: Consts.ValueType.Int8 } + }, + { + name: "AMBI", + input: true, + output: false, + raw: { min: 0, max: 100 }, + pct: { min: 0, max: 100 }, + si: { min: 0, max: 100, symbol: "PCT" }, + values: { count: 1, type: Consts.ValueType.Int8 } + }, + { + name: "COL O", + input: false, + output: true, + raw: { min: 0, max: 10 }, + pct: { min: 0, max: 100 }, + si: { min: 0, max: 10, symbol: "IDX" }, + values: { count: 1, type: Consts.ValueType.Int8 } + }, + { + name: "RGB I", + input: true, + output: false, + raw: { min: 0, max: 1023 }, + pct: { min: 0, max: 100 }, + si: { min: 0, max: 1023, symbol: "RAW" }, + values: { count: 3, type: Consts.ValueType.Int16 } + }, + { + name: "IR Tx", + input: false, + output: true, + raw: { min: 0, max: 65535 }, + pct: { min: 0, max: 100 }, + si: { min: 0, max: 65535, symbol: "N/A" }, + values: { count: 1, type: Consts.ValueType.Int16 } + }, + { + name: "colorAndDistance", // SPEC 1 + input: true, + output: false, + raw: { min: 0, max: 255 }, + pct: { min: 0, max: 100 }, + si: { min: 0, max: 255, symbol: "N/A" }, + values: { count: 4, type: Consts.ValueType.Int8 } + }, + { + name: "DEBUG", + input: true, + output: false, + raw: { min: 0, max: 1023 }, + pct: { min: 0, max: 100 }, + si: { min: 0, max: 10, symbol: "N/A" }, + values: { count: 2, type: Consts.ValueType.Int16 } + }, + { + name: "CALIB", + input: true, + output: false, + raw: { min: 0, max: 65535 }, + pct: { min: 0, max: 100 }, + si: { min: 0, max: 65535, symbol: "N/A" }, + values: { count: 8, type: Consts.ValueType.Int16 } + } + ]; + super(hub, portId, modes, Consts.DeviceType.COLOR_DISTANCE_SENSOR); + + + this._eventHandlers.color = (data: IEventData) => { + const [color] = data.raw; + /** + * Emits when a color sensor is activated. + * @event ColorDistanceSensor#color + * @type {object} + * @param {Color} color + */ + this.notify("color", { color }); + }; + this._eventHandlers.distance = (data: IEventData) => { + const distance = data.si[0] * 25.4; + /** + * Emits when a distance sensor is activated. + * @event ColorDistanceSensor#distance + * @type {object} + * @param {number} distance Distance, in millimeters. + */ + this.notify("distance", { distance }); + }; + this._eventHandlers.colorAndDistance = (data: IEventData) => { + const [color, proximity, ledColor, reflectivity] = data.raw + + let distance = proximity; + if (reflectivity > 0) { + distance += 1.0 / reflectivity; + } + distance = Math.floor(distance * 25.4) - 20; + + /** + * A combined color and distance event, emits when the sensor is activated. + * @event ColorDistanceSensor#colorAndDistance + * @type {object} + * @param {Color} color + * @param {number} distance Distance, in millimeters. + */ + this.notify("colorAndDistance", { color, distance }); + }; } - /** * Switches the IR receiver into extended channel mode. After setting this, use channels 5-8 instead of 1-4 for this receiver. * @@ -169,7 +236,7 @@ export class ColorDistanceSensor extends Device { const payload = Buffer.alloc(2); payload[0] = (message[0] << 4) + (message[1] >> 4); payload[1] = message[0] >> 4; - this.subscribe(Mode.PF_IR); + this.subscribe(this._modeMap["IR Tx"]); return this.writeDirect(0x07, payload); } } @@ -189,7 +256,7 @@ export class ColorDistanceSensor extends Device { if (this.isWeDo2SmartHub) { throw new Error("Setting LED color is not available on the WeDo 2.0 Smart Hub"); } else { - this.subscribe(Mode.LED); + this.subscribe(this._modeMap['COL O']); this.writeDirect(0x05, Buffer.from([color])); } return resolve(); @@ -204,20 +271,6 @@ export class ColorDistanceSensor extends Device { } -export enum Mode { - COLOR = 0x00, - DISTANCE = 0x01, - LED = 0x05, - PF_IR = 0x07, - COLOR_AND_DISTANCE = 0x08 -} - -export const ModeMap: {[event: string]: number} = { - "color": Mode.COLOR, - "distance": Mode.DISTANCE, - "colorAndDistance": Mode.COLOR_AND_DISTANCE -}; - export enum Output { RED = "RED", BLUE = "BLUE" diff --git a/src/devices/currentsensor.ts b/src/devices/currentsensor.ts index 2132e03b..79444d8a 100644 --- a/src/devices/currentsensor.ts +++ b/src/devices/currentsensor.ts @@ -1,6 +1,6 @@ import { Device } from "./device"; -import { IDeviceInterface } from "../interfaces"; +import { IDeviceInterface, IEventData } from "../interfaces"; import * as Consts from "../consts"; @@ -11,49 +11,43 @@ import * as Consts from "../consts"; export class CurrentSensor extends Device { constructor (hub: IDeviceInterface, portId: number) { - super(hub, portId, ModeMap, Consts.DeviceType.CURRENT_SENSOR); - } - - public receive (message: Buffer) { - const mode = this.mode; - - switch (mode) { - case Mode.CURRENT: - if (this.isWeDo2SmartHub) { - const current = message.readInt16LE(2) / 1000; - this.notify("current", { current }); - } else { - let maxCurrentValue = MaxCurrentValue[this.hub.type]; - if (maxCurrentValue === undefined) { - maxCurrentValue = MaxCurrentValue[Consts.HubType.UNKNOWN]; - } - let maxCurrentRaw = MaxCurrentRaw[this.hub.type]; - if (maxCurrentRaw === undefined) { - maxCurrentRaw = MaxCurrentRaw[Consts.HubType.UNKNOWN]; - } - const current = message.readUInt16LE(4) * maxCurrentValue / maxCurrentRaw; - /** - * Emits when a current change is detected. - * @event CurrentSensor#current - * @type {object} - * @param {number} current - */ - this.notify("current", { current }); - } - break; - } + const modes = [ + { + name: "current", // CUR L + input: true, + output: false, + raw: {min: 0, max: MaxCurrentRaw[hub.type] || MaxCurrentRaw[Consts.HubType.UNKNOWN]}, + pct: {min: 0, max: 100}, + si: {min: 0, max: MaxCurrentValue[hub.type] || MaxCurrentValue[Consts.HubType.UNKNOWN], symbol: "mA"}, + values: {count: 1, type: Consts.ValueType.Int16} + }, + { + name: "CUR S", + input: true, + output: false, + raw: {min: 0, max: MaxCurrentRaw[hub.type] || MaxCurrentRaw[Consts.HubType.UNKNOWN]}, + pct: {min: 0, max: 100}, + si: {min: 0, max: MaxCurrentValue[hub.type] || MaxCurrentValue[Consts.HubType.UNKNOWN], symbol: "mA"}, + values: {count: 1, type: Consts.ValueType.Int16} + } + ] + + super(hub, portId, modes, Consts.DeviceType.CURRENT_SENSOR); + + this._eventHandlers.current = (data: IEventData) => { + const [current] = data.si; + /** + * Emits when a current change is detected. + * @event CurrentSensor#current + * @type {object} + * @param {number} current + */ + this.notify("current", { current }); + }; } } -export enum Mode { - CURRENT = 0x00 -} - -export const ModeMap: {[event: string]: number} = { - "current": Mode.CURRENT -}; - const MaxCurrentValue: {[hubType: number]: number} = { [Consts.HubType.UNKNOWN]: 2444, [Consts.HubType.TECHNIC_MEDIUM_HUB]: 4175, diff --git a/src/devices/device.ts b/src/devices/device.ts index 41b3733c..796053bf 100644 --- a/src/devices/device.ts +++ b/src/devices/device.ts @@ -1,11 +1,14 @@ import { EventEmitter } from "events"; -import { IDeviceInterface, IMode } from "../interfaces"; +import { IDeviceInterface, IMode, IEventData } from "../interfaces"; import * as Consts from "../consts"; -import { normalize } from "../utils"; +import { normalize, toHex, toBin } from "../utils"; +import Debug = require("debug"); +const debug = Debug("device"); +const modeInfoDebug = Debug("lpf2hubmodeinfo"); /** * @class Device * @extends EventEmitter @@ -15,33 +18,50 @@ export class Device extends EventEmitter { public autoSubscribe: boolean = true; public values: {[event: string]: any} = {}; + protected _modeCount: number = 0; protected _modes: IMode[] = []; protected _mode: number | undefined; + protected _modeMap: {[event: string]: number} = {}; protected _busy: boolean = false; protected _finished: (() => void) | undefined; + protected _eventHandlers: {[event: string]: (data: IEventData) => void} = {}; + private _ready: boolean = false; private _hub: IDeviceInterface; private _portId: number; private _connected: boolean = true; private _type: Consts.DeviceType; - private _modeMap: {[event: string]: number} = {}; private _isWeDo2SmartHub: boolean; private _isVirtualPort: boolean = false; private _eventTimer: NodeJS.Timer | null = null; - constructor (hub: IDeviceInterface, portId: number, modeMap: {[event: string]: number} = {}, type: Consts.DeviceType = Consts.DeviceType.UNKNOWN) { + constructor (hub: IDeviceInterface, portId: number, modes: IMode[] = [], type: Consts.DeviceType = Consts.DeviceType.UNKNOWN) { super(); this._hub = hub; this._portId = portId; this._type = type; - this._modeMap = modeMap; + this._modes = modes; this._isWeDo2SmartHub = (this.hub.type === Consts.HubType.WEDO2_SMART_HUB); this._isVirtualPort = this.hub.isPortVirtual(portId); + if (!this.autoParse) { + this._init(); + } + } + + private _init() { + if (this._ready) { + return; + } + this._modeMap = this._modes.reduce((map: {[name: string]: number}, mode, index) => { + map[mode.name] = index; + return map; + }, {}); + const eventAttachListener = (event: string) => { - if (event === "detach" || !this._ready) { + if (event === "detach") { return; } if (this.autoSubscribe) { @@ -69,10 +89,8 @@ export class Device extends EventEmitter { this.on("newListener", eventAttachListener); this.hub.on("detach", deviceDetachListener); - if (!this.autoParse) { - this._ready = true; - this.emit('ready'); - } + this._ready = true; + this.emit('ready'); } /** @@ -151,6 +169,14 @@ export class Device extends EventEmitter { return this._modes.filter(mode => mode.output).map(({ name }) => name); } + /** + * @readonly + * @property {boolean} ready ready state. + */ + public get isReady () { + return this._ready; + } + public writeDirect (mode: number, data: Buffer) { if (this.isWeDo2SmartHub) { return this.send(Buffer.concat([Buffer.from([this.portId, 0x01, 0x02]), data]), Consts.BLECharacteristic.WEDO2_MOTOR_VALUE_WRITE); @@ -209,6 +235,123 @@ export class Device extends EventEmitter { public receive (message: Buffer) { this.notify("receive", { message }); + switch (message[2]) { + case 0x43: { + this._parseInformationResponse(message); + break; + } + case 0x44: { + this._parseModeInformationResponse(message); + break; + } + case 0x45: { + this._parseSensorMessage(message); + break; + } + case 0x82: { + this._parsePortAction(message); + break; + } + } + } + + private async _parseInformationResponse (message: Buffer) { + if (message[4] === 2) { + const modeCombinationMasks: number[] = []; + for (let i = 5; i < message.length; i += 2) { + modeCombinationMasks.push(message.readUInt16LE(i)); + } + modeInfoDebug(`Port ${toHex(this.portId)}, mode combinations [${modeCombinationMasks.map((c) => toBin(c, 0)).join(", ")}]`); + return; + } + this._modeCount = message[6]; + const input = toBin(message.readUInt16LE(7), this._modeCount); + const output = toBin(message.readUInt16LE(9), this._modeCount); + modeInfoDebug(`Port ${toHex(this.portId)}, total modes ${this._modeCount}, input modes ${input}, output modes ${output}`); + + this._modes = new Array(+this._modeCount); + + for (let i = 0; i < this._modeCount; i++) { + this._modes[i] = { + name: '', + input: input[this._modeCount - i - 1] === '1', + output: output[this._modeCount - i - 1] === '1', + raw: { min: 0, max: 255 }, + pct: { min: 0, max: 100 }, + si: { min: 0, max: 255, symbol: '' }, + values: { count: 1, type: Consts.ValueType.Int8 }, + }; + await this._sendModeInformationRequest(i, 0x00); // Mode Name + await this._sendModeInformationRequest(i, 0x01); // RAW Range + await this._sendModeInformationRequest(i, 0x02); // PCT Range + await this._sendModeInformationRequest(i, 0x03); // SI Range + await this._sendModeInformationRequest(i, 0x04); // SI Symbol + await this._sendModeInformationRequest(i, 0x80); // Value Format + } + } + + private _sendModeInformationRequest (mode: number, type: number) { + return this.send(Buffer.from([0x22, this.portId, mode, type]), Consts.BLECharacteristic.LPF2_ALL); + } + + private _parseModeInformationResponse (message: Buffer) { + const mode = message[4]; + const debugHeader = `Port ${toHex(this.portId)}, mode ${mode},`; + const type = message[5]; + switch (type) { + case 0x00: { // Mode Name + const name = message.slice(6, message.length).toString().replace(/\0/g, ''); + modeInfoDebug(`${debugHeader} name ${name}`); + this._modes[mode].name=name; + break; + } + case 0x01: { // RAW Range + const min = message.readFloatLE(6); + const max = message.readFloatLE(10); + modeInfoDebug(`${debugHeader} RAW min ${min}, max ${max}`); + this._modes[mode].raw.min=min + this._modes[mode].raw.max=max; + break; + } + case 0x02: { // PCT Range + const min = message.readFloatLE(6); + const max = message.readFloatLE(10); + modeInfoDebug(`${debugHeader} PCT min ${min}, max ${max}`); + this._modes[mode].pct.min=min; + this._modes[mode].pct.max=max; + break; + } + case 0x03: {// SI Range + const min = message.readFloatLE(6); + const max = message.readFloatLE(10); + modeInfoDebug(`${debugHeader} SI min ${min}, max ${max}`); + this._modes[mode].si.min=min; + this._modes[mode].si.max=max; + break; + } + case 0x04: {// SI Symbol + const symbol = message.slice(6, message.length).toString().replace(/\0/g, ''); + modeInfoDebug(`${debugHeader} SI symbol ${symbol}`); + this._modes[mode].si.symbol=symbol; + break; + } + case 0x80: {// Value Format + const numValues = message[6]; + const dataType = message[7]; + const totalFigures = message[8]; + const decimals = message[9]; + modeInfoDebug(`${debugHeader} Value ${numValues} x ${dataType}, Decimal format ${totalFigures}.${decimals}`); + this._modes[mode].values.count=numValues; + this._modes[mode].values.type=dataType; + + if (this.autoParse && mode === this._modeCount - 1) { + this._init(); + } + } + } + } + + private _parseSensorMessage(message: Buffer) { const mode = this._mode; if (mode === undefined) { return; @@ -235,11 +378,23 @@ export class Device extends EventEmitter { } } - this.notify(name, { + const eventData = { raw: data, pct: data.map(value => normalize(value, {raw, out: pct})), si: data.map(value => normalize(value, {raw, out: si})) - }); + } + + if (this._eventHandlers[name]) { + this._eventHandlers[name](eventData); + } else { + this.notify(name, eventData); + } + } + + private _parsePortAction (message: Buffer) { + if (message[4] === 0x0a) { + this.finish(); + } } public notify (event: string, values: any) { @@ -279,18 +434,6 @@ export class Device extends EventEmitter { } } - public setModes(modes: IMode[]) { - this._modes = modes; - - this._modeMap = modes.reduce((map: {[name: string]: number}, mode, index) => { - map[mode.name] = index; - return map; - }, {}); - - this._ready = true; - this.emit('ready'); - } - private get autoParse() { return this.hub.autoParse || this._type === Consts.DeviceType.UNKNOWN; } diff --git a/src/devices/duplotrainbasecolorsensor.ts b/src/devices/duplotrainbasecolorsensor.ts index 1eed822b..6daf9d3c 100644 --- a/src/devices/duplotrainbasecolorsensor.ts +++ b/src/devices/duplotrainbasecolorsensor.ts @@ -1,6 +1,6 @@ import { Device } from "./device"; -import { IDeviceInterface } from "../interfaces"; +import { IDeviceInterface, IEventData } from "../interfaces"; import * as Consts from "../consts"; @@ -11,72 +11,78 @@ import * as Consts from "../consts"; export class DuploTrainBaseColorSensor extends Device { constructor (hub: IDeviceInterface, portId: number) { - super(hub, portId, ModeMap, Consts.DeviceType.DUPLO_TRAIN_BASE_COLOR_SENSOR); + const modes = [ + { + name: "color", + input: true, + output: false, + raw: { min: 0, max: 10 }, + pct: { min: 0, max: 100 }, + si: { min: 0, max: 10, symbol: "IDX" }, + values: { count: 1, type: Consts.ValueType.Int8 } + }, + { + name: "UNKNOW", + input: false, + output: false, + raw: { min: 0, max: 100 }, + pct: { min: 0, max: 100 }, + si: { min: 0, max: 100, symbol: "PCT" }, + values: { count: 1, type: Consts.ValueType.Int8 } + }, + { + name: "reflect", + input: true, + output: false, + raw: { min: 0, max: 100 }, + pct: { min: 0, max: 100 }, + si: { min: 0, max: 100, symbol: "PCT" }, + values: { count: 1, type: Consts.ValueType.Int8 } + }, + { + name: "rgb", + input: true, + output: false, + raw: { min: 0, max: 1023 }, + pct: { min: 0, max: 100 }, + si: { min: 0, max: 1023, symbol: "RAW" }, + values: { count: 3, type: Consts.ValueType.Int16 } + } + ]; + + super(hub, portId, modes, Consts.DeviceType.DUPLO_TRAIN_BASE_COLOR_SENSOR); + + this._eventHandlers.color = (data: IEventData) => { + const [color] = data.raw; + /** + * Emits when a color sensor is activated. + * @event DuploTrainBaseColorSensor#color + * @type {object} + * @param {Color} color + */ + this.notify("color", { color }); + }; + this._eventHandlers.reflectivity = (data: IEventData) => { + const [reflectivity] = data.raw; + /** + * Emits when the light reflectivity changes. + * @event DuploTrainBaseColorSensor#reflectivity + * @type {object} + * @param {Reflectivity} reflectivity + */ + this.notify("reflectivity", { reflectivity }); + }; + this._eventHandlers.rgb = (data: IEventData) => { + const [r, g, b] = data.raw; + /** + * Emits when color sensor is activated. + * @event DuploTrainBaseColorSensor#rgb + * @type {object} + * @param {number} red + * @param {number} green + * @param {number} blue + */ + this.notify("distance", { r, g, b }); + }; } - - public receive (message: Buffer) { - if (this.hub.autoParse) { - return super.receive(message); - } - - const mode = this._mode; - - switch (mode) { - case Mode.COLOR: - if (message[4] <= 10) { - const color = message[4]; - - /** - * Emits when a color sensor is activated. - * @event DuploTrainBaseColorSensor#color - * @type {object} - * @param {Color} color - */ - this.notify("color", { color }); - } - break; - - case Mode.REFLECTIVITY: - const reflect = message[4]; - - /** - * Emits when the light reflectivity changes. - * @event DuploTrainBaseColorSensor#reflect - * @type {object} - * @param {number} reflect Percentage, from 0 to 100. - */ - this.notify("reflect", { reflect }); - break; - - case Mode.RGB: - const red = Math.floor(message.readUInt16LE(4) / 4); - const green = Math.floor(message.readUInt16LE(6) / 4); - const blue = Math.floor(message.readUInt16LE(8) / 4); - - /** - * Emits when the light reflectivity changes. - * @event DuploTrainBaseColorSensor#rgb - * @type {object} - * @param {number} red - * @param {number} green - * @param {number} blue - */ - this.notify("rgb", { red, green, blue }); - break; - - } - } - } - -export enum Mode { - COLOR = 0x00, - REFLECTIVITY = 0x02, - RGB = 0x03 -} - -export const ModeMap: {[event: string]: number} = { - "color": Mode.COLOR, - "reflect": Mode.REFLECTIVITY, - "rgb": Mode.RGB -}; diff --git a/src/devices/duplotrainbasemotor.ts b/src/devices/duplotrainbasemotor.ts index 1e6f995b..19352c62 100644 --- a/src/devices/duplotrainbasemotor.ts +++ b/src/devices/duplotrainbasemotor.ts @@ -11,7 +11,7 @@ import * as Consts from "../consts"; export class DuploTrainBaseMotor extends BasicMotor { constructor (hub: IDeviceInterface, portId: number) { - super(hub, portId, {}, Consts.DeviceType.DUPLO_TRAIN_BASE_MOTOR); + super(hub, portId, [], Consts.DeviceType.DUPLO_TRAIN_BASE_MOTOR); } } diff --git a/src/devices/duplotrainbasespeaker.ts b/src/devices/duplotrainbasespeaker.ts index 2187a6ca..207b4212 100644 --- a/src/devices/duplotrainbasespeaker.ts +++ b/src/devices/duplotrainbasespeaker.ts @@ -11,7 +11,7 @@ import * as Consts from "../consts"; export class DuploTrainBaseSpeaker extends Device { constructor (hub: IDeviceInterface, portId: number) { - super(hub, portId, {}, Consts.DeviceType.DUPLO_TRAIN_BASE_SPEAKER); + super(hub, portId, [], Consts.DeviceType.DUPLO_TRAIN_BASE_SPEAKER); } /** diff --git a/src/devices/duplotrainbasespeedometer.ts b/src/devices/duplotrainbasespeedometer.ts index e58bd393..eaf55f69 100644 --- a/src/devices/duplotrainbasespeedometer.ts +++ b/src/devices/duplotrainbasespeedometer.ts @@ -1,6 +1,6 @@ import { Device } from "./device"; -import { IDeviceInterface } from "../interfaces"; +import { IDeviceInterface, IEventData } from "../interfaces"; import * as Consts from "../consts"; @@ -11,38 +11,29 @@ import * as Consts from "../consts"; export class DuploTrainBaseSpeedometer extends Device { constructor (hub: IDeviceInterface, portId: number) { - super(hub, portId, ModeMap, Consts.DeviceType.DUPLO_TRAIN_BASE_SPEEDOMETER); + const modes = [ + { + name: "speed", + input: true, + output: false, + raw: { min: -100, max: 100 }, + pct: { min: -100, max: 100 }, + si: { min: -100, max: 100, symbol: "" }, + values: { count: 1, type: Consts.ValueType.Int16 } + } + ] + super(hub, portId, modes, Consts.DeviceType.DUPLO_TRAIN_BASE_SPEEDOMETER); + + this._eventHandlers.color = (data: IEventData) => { + const [speed] = data.raw; + /** + * Emits on a speed change. + * @event DuploTrainBaseSpeedometer#speed + * @type {object} + * @param {number} speed + */ + this.notify("speed", { speed }); + }; } - public receive (message: Buffer) { - if (this.hub.autoParse) { - return super.receive(message); - } - - const mode = this._mode; - - switch (mode) { - case Mode.SPEED: - const speed = message.readInt16LE(4); - - /** - * Emits on a speed change. - * @event DuploTrainBaseSpeedometer#speed - * @type {object} - * @param {number} speed - */ - this.notify("speed", { speed }); - break; - - } - } - -} - -export enum Mode { - SPEED = 0x00 } - -export const ModeMap: {[event: string]: number} = { - "speed": Mode.SPEED -}; diff --git a/src/devices/hubled.ts b/src/devices/hubled.ts index a7bf9adb..a4ad19aa 100644 --- a/src/devices/hubled.ts +++ b/src/devices/hubled.ts @@ -12,7 +12,27 @@ export class HubLED extends Device { constructor (hub: IDeviceInterface, portId: number) { - super(hub, portId, {}, Consts.DeviceType.HUB_LED); + const modes = [ + { + name: "COL O", + input: false, + output: true, + raw: {min: 0, max: 10}, + pct: {min: 0, max: 100}, + si: {min: 0, max: 10, symbol: ""}, + values: {count: 1, type: Consts.ValueType.Int8}, + }, + { + name: "RGB O", + input: false, + output: true, + raw: {min: 0, max: 255}, + pct: {min: 0, max: 100}, + si: {min: 0, max: 255, symbol: ""}, + values: {count: 3, type: Consts.ValueType.Int8}, + } + ] + super(hub, portId, modes, Consts.DeviceType.HUB_LED); } @@ -31,8 +51,8 @@ export class HubLED extends Device { this.send(Buffer.from([0x06, 0x17, 0x01, 0x01]), Consts.BLECharacteristic.WEDO2_PORT_TYPE_WRITE); this.send(Buffer.from([0x06, 0x04, 0x01, color]), Consts.BLECharacteristic.WEDO2_MOTOR_VALUE_WRITE); } else { - this.subscribe(Mode.COLOR); - this.writeDirect(0x00, Buffer.from([color])); + this.subscribe(this._modeMap["COL O"]); + this.writeDirect(this._modeMap["COL O"], Buffer.from([color])); } return resolve(); }); @@ -53,8 +73,8 @@ export class HubLED extends Device { this.send(Buffer.from([0x06, 0x17, 0x01, 0x02]), Consts.BLECharacteristic.WEDO2_PORT_TYPE_WRITE); this.send(Buffer.from([0x06, 0x04, 0x03, red, green, blue]), Consts.BLECharacteristic.WEDO2_MOTOR_VALUE_WRITE); } else { - this.subscribe(Mode.RGB); - this.writeDirect(0x01, Buffer.from([red, green, blue])); + this.subscribe(this._modeMap["COL O"]); + this.writeDirect(this._modeMap["COL O"], Buffer.from([red, green, blue])); } return resolve(); }); @@ -62,8 +82,3 @@ export class HubLED extends Device { } - -export enum Mode { - COLOR = 0x00, - RGB = 0x01 -} diff --git a/src/devices/light.ts b/src/devices/light.ts index 7bb58d86..cd9b13b5 100644 --- a/src/devices/light.ts +++ b/src/devices/light.ts @@ -13,7 +13,7 @@ export class Light extends Device { constructor (hub: IDeviceInterface, portId: number) { - super(hub, portId, {}, Consts.DeviceType.LIGHT); + super(hub, portId, [], Consts.DeviceType.LIGHT); } diff --git a/src/devices/mediumlinearmotor.ts b/src/devices/mediumlinearmotor.ts index 8da5782e..fcfe69e5 100644 --- a/src/devices/mediumlinearmotor.ts +++ b/src/devices/mediumlinearmotor.ts @@ -11,7 +11,7 @@ import * as Consts from "../consts"; export class MediumLinearMotor extends TachoMotor { constructor (hub: IDeviceInterface, portId: number) { - super(hub, portId, {}, Consts.DeviceType.MEDIUM_LINEAR_MOTOR); + super(hub, portId, [], Consts.DeviceType.MEDIUM_LINEAR_MOTOR); } } diff --git a/src/devices/motionsensor.ts b/src/devices/motionsensor.ts index 6866527d..bd9b50de 100644 --- a/src/devices/motionsensor.ts +++ b/src/devices/motionsensor.ts @@ -1,6 +1,6 @@ import { Device } from "./device"; -import { IDeviceInterface } from "../interfaces"; +import { IDeviceInterface, IEventData } from "../interfaces"; import * as Consts from "../consts"; @@ -11,34 +11,30 @@ import * as Consts from "../consts"; export class MotionSensor extends Device { constructor (hub: IDeviceInterface, portId: number) { - super(hub, portId, ModeMap, Consts.DeviceType.MOTION_SENSOR); + const modes = [ + { + name: "distance", // PROX + input: true, + output: false, + raw: { min: 0, max: 512 }, + pct: { min: 0, max: 100 }, + si: { min: 0, max: 5120, symbol: "mm" }, + values: { count: 1, type: Consts.ValueType.Int16 } + } + ] + super(hub, portId, modes, Consts.DeviceType.MOTION_SENSOR); + + this._eventHandlers.distance = (data: IEventData) => { + const [distance] = data.si; + /** + * Emits when a distance sensor is activated. + * @event MotionSensor#distance + * @type {object} + * @param {number} distance Distance, in millimeters. + */ + this.notify("distance", { distance }); + }; } - - public receive (message: Buffer) { - if (this.hub.autoParse) { - return super.receive(message); - } - - const mode = this._mode; - - switch (mode) { - case Mode.DISTANCE: - let distance = message[this.isWeDo2SmartHub ? 2 : 4]; - if (message[this.isWeDo2SmartHub ? 3 : 5] === 1) { - distance = distance + 255; - } - distance *= 10; - /** - * Emits when a distance sensor is activated. - * @event MotionSensor#distance - * @type {object} - * @param {number} distance Distance, in millimeters. - */ - this.notify("distance", { distance }); - break; - } - } - } export enum Mode { diff --git a/src/devices/movehubmediumlinearmotor.ts b/src/devices/movehubmediumlinearmotor.ts index 7c304e75..8c38fab9 100644 --- a/src/devices/movehubmediumlinearmotor.ts +++ b/src/devices/movehubmediumlinearmotor.ts @@ -11,7 +11,7 @@ import * as Consts from "../consts"; export class MoveHubMediumLinearMotor extends TachoMotor { constructor (hub: IDeviceInterface, portId: number) { - super(hub, portId, {}, Consts.DeviceType.MOVE_HUB_MEDIUM_LINEAR_MOTOR); + super(hub, portId, [], Consts.DeviceType.MOVE_HUB_MEDIUM_LINEAR_MOTOR); } } diff --git a/src/devices/movehubtiltsensor.ts b/src/devices/movehubtiltsensor.ts index 8830abee..f8190ab2 100644 --- a/src/devices/movehubtiltsensor.ts +++ b/src/devices/movehubtiltsensor.ts @@ -1,6 +1,6 @@ import { Device } from "./device"; -import { IDeviceInterface } from "../interfaces"; +import { IDeviceInterface, IEventData } from "../interfaces"; import * as Consts from "../consts"; @@ -11,30 +11,94 @@ import * as Consts from "../consts"; export class MoveHubTiltSensor extends Device { constructor (hub: IDeviceInterface, portId: number) { - super(hub, portId, ModeMap, Consts.DeviceType.MOVE_HUB_TILT_SENSOR); - } + const modes = [ + { + name: "tilt", // ANGLE + input: true, + output: false, + raw: { min: -90, max: 90 }, + pct: { min: -100, max: 100 }, + si: { min: -90, max: 90, symbol: "DEG" }, + values: { count: 2, type: Consts.ValueType.Int8 } + }, + { + name: "TILT", + input: true, + output: false, + raw: { min: 0, max: 10 }, + pct: { min: 0, max: 100 }, + si: { min: 0, max: 10, symbol: "DIR" }, + values: { count: 1, type: Consts.ValueType.Int8 } + }, + { + name: "ORINT", + input: true, + output: false, + raw: { min: 0, max: 5 }, + pct: { min: 0, max: 100 }, + si: { min: 0, max: 5, symbol: "DIR" }, + values: { count: 1, type: Consts.ValueType.Int8 } + }, + { + name: "IMPCT", + input: true, + output: false, + raw: { min: 0, max: 100 }, + pct: { min: 0, max: 100 }, + si: { min: 0, max: 100, symbol: "IMP" }, + values: { count: 1, type: Consts.ValueType.Int32 } + }, + { + name: "ACCEL", + input: true, + output: false, + raw: { min: -65, max: 65 }, + pct: { min: -100, max: 100 }, + si: { min: -65, max: 65, symbol: "ACC" }, + values: { count: 3, type: Consts.ValueType.Int8 } + }, + { + name: "OR_CF", + input: true, + output: false, + raw: { min: 0, max: 6 }, + pct: { min: 0, max: 100 }, + si: { min: 0, max: 6, symbol: "SID" }, + values: { count: 1, type: Consts.ValueType.Int8 } + }, + { + name: "IM_CF", + input: true, + output: false, + raw: { min: 0, max: 255 }, + pct: { min: 0, max: 100 }, + si: { min: 0, max: 255, symbol: "SEN" }, + values: { count: 2, type: Consts.ValueType.Int8 } + }, + { + name: "CALIB", + input: true, + output: false, + raw: { min: 0, max: 255 }, + pct: { min: 0, max: 100 }, + si: { min: 0, max: 255, symbol: "CAL" }, + values: { count: 3, type: Consts.ValueType.Int8 } + } + + ] + super(hub, portId, modes, Consts.DeviceType.MOVE_HUB_TILT_SENSOR); - public receive (message: Buffer) { - if (this.hub.autoParse) { - return super.receive(message); - } - - const mode = this._mode; - - switch (mode) { - case Mode.TILT: - /** - * Emits when a tilt sensor is activated. - * @event MoveHubTiltSensor#tilt - * @type {object} - * @param {number} x - * @param {number} y - */ - const x = -message.readInt8(4); - const y = message.readInt8(5); - this.notify("tilt", { x, y }); - break; - } + this._eventHandlers.tilt = (data: IEventData) => { + const [x, y] = data.si; + /** + * Emits when a tilt sensor is activated. + * @event TechnicMediumHubTiltSensor#tilt + * @type {object} + * @param {number} x + * @param {number} y + */ + this.notify("tilt", { x, y }); + }; } } diff --git a/src/devices/piezobuzzer.ts b/src/devices/piezobuzzer.ts index 76461c8a..34ab5a35 100644 --- a/src/devices/piezobuzzer.ts +++ b/src/devices/piezobuzzer.ts @@ -12,7 +12,7 @@ export class PiezoBuzzer extends Device { constructor (hub: IDeviceInterface, portId: number) { - super(hub, portId, {}, Consts.DeviceType.PIEZO_BUZZER); + super(hub, portId, [], Consts.DeviceType.PIEZO_BUZZER); } diff --git a/src/devices/remotecontrolbutton.ts b/src/devices/remotecontrolbutton.ts index 32b95b16..d83619a6 100644 --- a/src/devices/remotecontrolbutton.ts +++ b/src/devices/remotecontrolbutton.ts @@ -1,6 +1,6 @@ import { Device } from "./device"; -import { IDeviceInterface } from "../interfaces"; +import { IDeviceInterface, IEventData } from "../interfaces"; import * as Consts from "../consts"; @@ -11,40 +11,70 @@ import * as Consts from "../consts"; export class RemoteControlButton extends Device { constructor (hub: IDeviceInterface, portId: number) { - super(hub, portId, ModeMap, Consts.DeviceType.REMOTE_CONTROL_BUTTON); - } + const modes = [ + { + name: "button", // RCKEY + input: true, + output: false, + raw: { min: -1, max: 1 }, + pct: { min: -100, max: 100 }, + si: { min: -1, max: 1, symbol: "btn" }, + values: { count: 1, type: Consts.ValueType.Int8 } + }, + { + name: "KEYA ", + input: true, + output: false, + raw: { min: -1, max: 1 }, + pct: { min: -100, max: 100 }, + si: { min: -1, max: 1, symbol: "btn" }, + values: { count: 1, type: Consts.ValueType.Int8 } + }, + { + name: "KEYR ", + input: true, + output: false, + raw: { min: -1, max: 1 }, + pct: { min: -100, max: 100 }, + si: { min: -1, max: 1, symbol: "btn" }, + values: { count: 1, type: Consts.ValueType.Int8 } + }, + { + name: "KEYD ", + input: true, + output: false, + raw: { min: 0, max: 7 }, + pct: { min: 0, max: 100 }, + si: { min: 0, max: 7, symbol: "btn" }, + values: { count: 1, type: Consts.ValueType.Int8 } + }, + { + name: "KEYSD", + input: true, + output: false, + raw: { min: 0, max: 1 }, + pct: { min: 0, max: 100 }, + si: { min: 0, max: 1, symbol: "btn" }, + values: { count: 3, type: Consts.ValueType.Int8 } + } + ] - public receive (message: Buffer) { - if (this.hub.autoParse) { - return super.receive(message); - } - - const mode = this._mode; - - switch (mode) { - case Mode.BUTTON_EVENTS: - /** - * Emits when a button on the remote is pressed or released. - * @event RemoteControlButton#button - * @type {object} - * @param {number} event - */ - const event = message[4]; - this.notify("remoteButton", { event }); - break; - } - } + super(hub, portId, modes, Consts.DeviceType.REMOTE_CONTROL_BUTTON); -} + this._eventHandlers.color = (data: IEventData) => { + const [event] = data.raw; + /** + * Emits when a button on the remote is pressed or released. + * @event RemoteControlButton#button + * @type {object} + * @param {number} event + */ + this.notify("remoteButton", { event }); + }; + } -export enum Mode { - BUTTON_EVENTS = 0x00 } -export const ModeMap: {[event: string]: number} = { - "remoteButton": Mode.BUTTON_EVENTS -}; - export const ButtonState: {[state: string]: number} = { "UP": 0x01, "DOWN": 0xff, diff --git a/src/devices/simplemediumlinearmotor.ts b/src/devices/simplemediumlinearmotor.ts index 4596ec3d..f0961213 100644 --- a/src/devices/simplemediumlinearmotor.ts +++ b/src/devices/simplemediumlinearmotor.ts @@ -11,7 +11,7 @@ import * as Consts from "../consts"; export class SimpleMediumLinearMotor extends BasicMotor { constructor (hub: IDeviceInterface, portId: number) { - super(hub, portId, {}, Consts.DeviceType.SIMPLE_MEDIUM_LINEAR_MOTOR); + super(hub, portId, [], Consts.DeviceType.SIMPLE_MEDIUM_LINEAR_MOTOR); } } diff --git a/src/devices/tachomotor.ts b/src/devices/tachomotor.ts index c5e79027..95725d5b 100644 --- a/src/devices/tachomotor.ts +++ b/src/devices/tachomotor.ts @@ -1,10 +1,32 @@ -import { BasicMotor } from "./basicmotor"; +import { BasicMotor, modes as BasicMotorModes } from "./basicmotor"; -import { IDeviceInterface } from "../interfaces"; +import { IDeviceInterface, IMode, IEventData } from "../interfaces"; import * as Consts from "../consts"; import { mapSpeed } from "../utils"; +export const modes = BasicMotorModes.concat([ + // POWER + { + name: "speed", // SPEED + input: true, + output: true, + raw: { min: -100, max: 100 }, + pct: { min: -100, max: 100 }, + si: { min: -100, max: 100, symbol: "PCT" }, + values: { count: 1, type: Consts.ValueType.Int8 } + }, + { + name: "rotate", // POS + input: true, + output: true, + raw: { min: -360, max: 360 }, + pct: { min: -100, max: 100 }, + si: { min: -360, max: 360, symbol: "DEG" }, + values: { count: 1, type: Consts.ValueType.Int32 } + }, +]) + /** * @class TachoMotor * @extends BasicMotor @@ -16,32 +38,21 @@ export class TachoMotor extends BasicMotor { public useAccelerationProfile: boolean = true; public useDecelerationProfile: boolean = true; - constructor (hub: IDeviceInterface, portId: number, modeMap: {[event: string]: number} = {}, type: Consts.DeviceType = Consts.DeviceType.UNKNOWN) { - super(hub, portId, Object.assign({}, modeMap, ModeMap), type); - } - - public receive (message: Buffer) { - if (this.hub.autoParse) { - return super.receive(message); - } - - const mode = this._mode; - - switch (mode) { - case Mode.ROTATION: - const degrees = message.readInt32LE(this.isWeDo2SmartHub ? 2 : 4); - /** - * Emits when a rotation sensor is activated. - * @event TachoMotor#rotate - * @type {object} - * @param {number} rotation - */ - this.notify("rotate", { degrees }); - break; - } + constructor (hub: IDeviceInterface, portId: number, _modes: IMode[] = [], type: Consts.DeviceType = Consts.DeviceType.UNKNOWN) { + super(hub, portId, _modes.length > 0 ? _modes : modes, type); + + this._eventHandlers.rotate = (data: IEventData) => { + const [degrees] = data.raw; + /** + * Emits when a rotation sensor is activated. + * @event TachoMotor#rotate + * @type {object} + * @param {number} rotation + */ + this.notify("rotate", { degrees }); + }; } - /** * Set the braking style of the motor. * @@ -182,11 +193,3 @@ export class TachoMotor extends BasicMotor { } - -export enum Mode { - ROTATION = 0x02 -} - -export const ModeMap: {[event: string]: number} = { - "rotate": Mode.ROTATION -}; diff --git a/src/devices/techniccolorsensor.ts b/src/devices/techniccolorsensor.ts index a810a30c..14a4bd8d 100644 --- a/src/devices/techniccolorsensor.ts +++ b/src/devices/techniccolorsensor.ts @@ -1,6 +1,6 @@ import { Device } from "./device"; -import { IDeviceInterface } from "../interfaces"; +import { IDeviceInterface, IEventData } from "../interfaces"; import * as Consts from "../consts"; @@ -11,55 +11,79 @@ import * as Consts from "../consts"; export class TechnicColorSensor extends Device { constructor (hub: IDeviceInterface, portId: number) { - super(hub, portId, ModeMap, Consts.DeviceType.TECHNIC_COLOR_SENSOR); - } - - public receive (message: Buffer) { - if (this.hub.autoParse) { - return super.receive(message); - } - - const mode = this._mode; - - switch (mode) { - case Mode.COLOR: - if (message[4] <= 10) { - const color = message[4]; - - /** - * Emits when a color sensor is activated. - * @event TechnicColorSensor#color - * @type {object} - * @param {Color} color - */ - this.notify("color", { color }); - } - break; - - case Mode.REFLECTIVITY: - const reflect = message[4]; + const modes = [ + + { + name: "color", + input: true, + output: false, + raw: { min: 0, max: 10 }, + pct: { min: 0, max: 100 }, + si: { min: 0, max: 10, symbol: "IDX" }, + values: { count: 1, type: Consts.ValueType.Int8 } + }, + { + name: "reflect", + input: true, + output: false, + raw: { min: 0, max: 100 }, + pct: { min: 0, max: 100 }, + si: { min: 0, max: 100, symbol: "PCT" }, + values: { count: 1, type: Consts.ValueType.Int8 } + }, + { + name: "ambient", + input: true, + output: false, + raw: { min: 0, max: 100 }, + pct: { min: 0, max: 100 }, + si: { min: 0, max: 100, symbol: "PCT" }, + values: { count: 1, type: Consts.ValueType.Int8 } + }, + { + name: "brightness", + input: false, + output: true, + raw: { min: 0, max: 100 }, + pct: { min: 0, max: 100 }, + si: { min: 0, max: 100, symbol: "PCT" }, + values: { count: 3, type: Consts.ValueType.Int8 } + } + ] + super(hub, portId, modes, Consts.DeviceType.TECHNIC_COLOR_SENSOR); + + + this._eventHandlers.color = (data: IEventData) => { + const [color] = data.raw; + /** + * Emits when a color sensor is activated. + * @event TechnicColorSensor#color + * @type {object} + * @param {Color} color + */ + this.notify("color", { color }); + }; + this._eventHandlers.reflect = (data: IEventData) => { + const [reflect] = data.raw; + /** + * Emits when the light reflectivity changes. + * @event TechnicColorSensor#reflect + * @type {object} + * @param {number} reflect Percentage, from 0 to 100. + */ + this.notify("reflect", { reflect }); + }; + this._eventHandlers.ambient = (data: IEventData) => { + const [ambient] = data.raw; + /** + * Emits when the ambient light changes. + * @event TechnicColorSensor#ambient + * @type {object} + * @param {number} ambient Percentage, from 0 to 100. + */ + this.notify("ambient", { ambient }); + }; - /** - * Emits when the light reflectivity changes. - * @event TechnicColorSensor#reflect - * @type {object} - * @param {number} reflect Percentage, from 0 to 100. - */ - this.notify("reflect", { reflect }); - break; - - case Mode.AMBIENT_LIGHT: - const ambient = message[4]; - - /** - * Emits when the ambient light changes. - * @event TechnicColorSensor#ambient - * @type {object} - * @param {number} ambient Percentage, from 0 to 100. - */ - this.notify("ambient", { ambient }); - break; - } } /** @@ -75,15 +99,3 @@ export class TechnicColorSensor extends Device { } } - -export enum Mode { - COLOR = 0x00, - REFLECTIVITY = 0x01, - AMBIENT_LIGHT = 0x02 -} - -export const ModeMap: {[event: string]: number} = { - "color": Mode.COLOR, - "reflect": Mode.REFLECTIVITY, - "ambient": Mode.AMBIENT_LIGHT -}; diff --git a/src/devices/technicdistancesensor.ts b/src/devices/technicdistancesensor.ts index 82ef3335..b3ff94a4 100644 --- a/src/devices/technicdistancesensor.ts +++ b/src/devices/technicdistancesensor.ts @@ -1,6 +1,6 @@ import { Device } from "./device"; -import { IDeviceInterface } from "../interfaces"; +import { IDeviceInterface, IEventData } from "../interfaces"; import * as Consts from "../consts"; @@ -11,41 +11,84 @@ import * as Consts from "../consts"; export class TechnicDistanceSensor extends Device { constructor (hub: IDeviceInterface, portId: number) { - super(hub, portId, ModeMap, Consts.DeviceType.TECHNIC_DISTANCE_SENSOR); - } - - public receive (message: Buffer) { - if (this.hub.autoParse) { - return super.receive(message); - } - - const mode = this._mode; - - switch (mode) { - case Mode.DISTANCE: - const distance = message.readUInt16LE(4); - - /** - * Emits when the detected distance changes (Slow sampling covers 40mm to 2500mm). - * @event TechnicDistanceSensor#distance - * @type {object} - * @param {number} distance Distance, from 40 to 2500mm - */ - this.notify("distance", { distance }); - break; - - case Mode.FAST_DISTANCE: - const fastDistance = message.readUInt16LE(4); + const modes = [ + { + name: "distance", + input: true, + output: false, + raw: { min: 0, max: 2500 }, + pct: { min: 0, max: 100 }, + si: { min: 0, max: 2500, symbol: "mm" }, + values: { count: 1, type: Consts.ValueType.Int16 } + }, + { + name: "fastDistance", + input: true, + output: false, + raw: { min: 0, max: 320 }, + pct: { min: 0, max: 100 }, + si: { min: 0, max: 320, symbol: "mm" }, + values: { count: 1, type: Consts.ValueType.Int16 } + }, + { + name: "UNKNOW-2", + input: true, + output: false, + raw: { min: 0, max: 100 }, + pct: { min: 0, max: 100 }, + si: { min: 0, max: 100, symbol: "" }, + values: { count: 1, type: Consts.ValueType.Int8 } + }, + { + name: "UNKNOW-3", + input: true, + output: false, + raw: { min: 0, max: 100 }, + pct: { min: 0, max: 100 }, + si: { min: 0, max: 100, symbol: "" }, + values: { count: 1, type: Consts.ValueType.Int8 } + }, + { + name: "UNKNOW-4", + input: true, + output: false, + raw: { min: 0, max: 100 }, + pct: { min: 0, max: 100 }, + si: { min: 0, max: 100, symbol: "" }, + values: { count: 1, type: Consts.ValueType.Int8 } + }, + { + name: "brightness", + input: false, + output: true, + raw: { min: 0, max: 100 }, + pct: { min: 0, max: 100 }, + si: { min: 0, max: 100, symbol: "" }, + values: { count: 4, type: Consts.ValueType.Int8 } + } + ] + super(hub, portId, modes, Consts.DeviceType.TECHNIC_DISTANCE_SENSOR); - /** - * Emits when the detected distance changes (Fast sampling covers 50mm to 320mm). - * @event TechnicDistanceSensor#fastDistance - * @type {object} - * @param {number} fastDistance Distance, from 50 to 320mm - */ - this.notify("fastDistance", { fastDistance }); - break; - } + this._eventHandlers.distance = (data: IEventData) => { + const [distance] = data.raw; + /** + * Emits when the detected distance changes (Slow sampling covers 40mm to 2500mm). + * @event TechnicDistanceSensor#distance + * @type {object} + * @param {number} distance Distance, from 40 to 2500mm + */ + this.notify("distance", { distance }); + }; + this._eventHandlers.fastDistance = (data: IEventData) => { + const [fastDistance] = data.raw; + /** + * Emits when the detected distance changes (Fast sampling covers 50mm to 320mm). + * @event TechnicDistanceSensor#fastDistance + * @type {object} + * @param {number} fastDistance Distance, from 50 to 320mm + */ + this.notify("fastDistance", { fastDistance }); + }; } /** diff --git a/src/devices/technicforcesensor.ts b/src/devices/technicforcesensor.ts index deeefe38..e1ee2ca5 100644 --- a/src/devices/technicforcesensor.ts +++ b/src/devices/technicforcesensor.ts @@ -1,6 +1,6 @@ import { Device } from "./device"; -import { IDeviceInterface } from "../interfaces"; +import { IDeviceInterface, IEventData } from "../interfaces"; import * as Consts from "../consts"; @@ -11,65 +11,67 @@ import * as Consts from "../consts"; export class TechnicForceSensor extends Device { constructor (hub: IDeviceInterface, portId: number) { - super(hub, portId, ModeMap, Consts.DeviceType.TECHNIC_FORCE_SENSOR); - } - - public receive (message: Buffer) { - if (this.hub.autoParse) { - return super.receive(message); - } - - const mode = this._mode; - - switch (mode) { - case Mode.FORCE: - const force = message[this.isWeDo2SmartHub ? 2 : 4] / 10; - - /** - * Emits when force is applied. - * @event TechnicForceSensor#force - * @type {object} - * @param {number} force Force, in newtons (0-10). - */ - this.notify("force", { force }); - break; - - case Mode.TOUCHED: - const touched = message[4] ? true : false; + const modes = [ + { + name: "force", + input: true, + output: false, + raw: { min: 0, max: 255 }, + pct: { min: 0, max: 100 }, + si: { min: 0, max: 25.5, symbol: "N" }, + values: { count: 1, type: Consts.ValueType.Int8 } + }, + { + name: "touched", + input: true, + output: false, + raw: { min: 0, max: 320 }, + pct: { min: 0, max: 100 }, + si: { min: 0, max: 320, symbol: "" }, + values: { count: 1, type: Consts.ValueType.Int8 } + }, + { + name: "tapped", + input: true, + output: false, + raw: { min: 0, max: 3 }, + pct: { min: 0, max: 100 }, + si: { min: 0, max: 3, symbol: "" }, + values: { count: 1, type: Consts.ValueType.Int8 } + }, + ]; - /** - * Emits when the sensor is touched. - * @event TechnicForceSensor#touch - * @type {object} - * @param {boolean} touch Touched on/off (boolean). - */ - this.notify("touched", { touched }); - break; + super(hub, portId, modes, Consts.DeviceType.TECHNIC_FORCE_SENSOR); - case Mode.TAPPED: - const tapped = message[4]; - - /** - * Emits when the sensor is tapped. - * @event TechnicForceSensor#tapped - * @type {object} - * @param {number} tapped How hard the sensor was tapped, from 0-3. - */ - this.notify("tapped", { tapped }); - break; - } + this._eventHandlers.force = (data: IEventData) => { + const [force] = data.si; + /** + * Emits when force is applied. + * @event TechnicForceSensor#force + * @type {object} + * @param {number} force Force, in newtons (0-10). + */ + this.notify("force", { force }); + }; + this._eventHandlers.touched = (data: IEventData) => { + const touched = !!data.raw[0]; + /** + * Emits when the sensor is touched. + * @event TechnicForceSensor#touch + * @type {object} + * @param {boolean} touch Touched on/off (boolean). + */ + this.notify("touched", { touched }); + }; + this._eventHandlers.tapped = (data: IEventData) => { + const [tapped] = data.raw; + /** + * Emits when the sensor is tapped. + * @event TechnicForceSensor#tapped + * @type {object} + * @param {number} tapped How hard the sensor was tapped, from 0-3. + */ + this.notify("tapped", { tapped }); + }; } - } - -export enum Mode { - FORCE = 0x00, - TOUCHED = 0x01, - TAPPED = 0x02 -} - -export const ModeMap: {[event: string]: number} = { - "force": Mode.FORCE, - "touched": Mode.TOUCHED, - "tapped": Mode.TAPPED -}; diff --git a/src/devices/techniclargeangularmotor.ts b/src/devices/techniclargeangularmotor.ts index 689b8f58..6c58ee88 100644 --- a/src/devices/techniclargeangularmotor.ts +++ b/src/devices/techniclargeangularmotor.ts @@ -10,8 +10,8 @@ import * as Consts from "../consts"; */ export class TechnicLargeAngularMotor extends AbsoluteMotor { - constructor (hub: IDeviceInterface, portId: number, modeMap: {[event: string]: number} = {}, type: Consts.DeviceType = Consts.DeviceType.TECHNIC_LARGE_ANGULAR_MOTOR) { - super(hub, portId, {}, type); + constructor (hub: IDeviceInterface, portId: number) { + super(hub, portId, [], Consts.DeviceType.TECHNIC_LARGE_ANGULAR_MOTOR); } } diff --git a/src/devices/techniclargelinearmotor.ts b/src/devices/techniclargelinearmotor.ts index 25a48f6b..0a0bfeb8 100644 --- a/src/devices/techniclargelinearmotor.ts +++ b/src/devices/techniclargelinearmotor.ts @@ -11,7 +11,7 @@ import * as Consts from "../consts"; export class TechnicLargeLinearMotor extends AbsoluteMotor { constructor (hub: IDeviceInterface, portId: number) { - super(hub, portId, {}, Consts.DeviceType.TECHNIC_LARGE_LINEAR_MOTOR); + super(hub, portId, [], Consts.DeviceType.TECHNIC_LARGE_LINEAR_MOTOR); } } diff --git a/src/devices/technicmediumangularmotor.ts b/src/devices/technicmediumangularmotor.ts index d97d48b5..7aae48af 100644 --- a/src/devices/technicmediumangularmotor.ts +++ b/src/devices/technicmediumangularmotor.ts @@ -1,6 +1,6 @@ import { AbsoluteMotor } from "./absolutemotor"; -import { IDeviceInterface } from "../interfaces"; +import { IDeviceInterface, IMode } from "../interfaces"; import * as Consts from "../consts"; @@ -10,8 +10,8 @@ import * as Consts from "../consts"; */ export class TechnicMediumAngularMotor extends AbsoluteMotor { - constructor (hub: IDeviceInterface, portId: number, modeMap: {[event: string]: number} = {}, type: Consts.DeviceType = Consts.DeviceType.TECHNIC_MEDIUM_ANGULAR_MOTOR) { - super(hub, portId, {}, type); + constructor (hub: IDeviceInterface, portId: number) { + super(hub, portId, [], Consts.DeviceType.TECHNIC_MEDIUM_ANGULAR_MOTOR); } } diff --git a/src/devices/technicmediumhubaccelerometersensor.ts b/src/devices/technicmediumhubaccelerometersensor.ts index ef46ce22..d34a7bc6 100644 --- a/src/devices/technicmediumhubaccelerometersensor.ts +++ b/src/devices/technicmediumhubaccelerometersensor.ts @@ -1,6 +1,6 @@ import { Device } from "./device"; -import { IDeviceInterface } from "../interfaces"; +import { IDeviceInterface, IEventData } from "../interfaces"; import * as Consts from "../consts"; @@ -11,40 +11,48 @@ import * as Consts from "../consts"; export class TechnicMediumHubAccelerometerSensor extends Device { constructor (hub: IDeviceInterface, portId: number) { - super(hub, portId, ModeMap, Consts.DeviceType.TECHNIC_MEDIUM_HUB_ACCELEROMETER); + const modes = [ + { + name: "accel", // GRV + input: true, + output: false, + raw: {min: -32768, max: 32768}, + pct: {min: -100, max: 100}, + si: {min: -8000, max: 8000, symbol: "mG"}, + values: {count: 3, type: Consts.ValueType.Int16} + }, + { + name: "CAL", + input: true, + output: false, + raw: {min: 1, max: 1}, + pct: {min: -100, max: 100}, + si: {min: 1, max: 1, symbol: ""}, + values: {count: 1, type: Consts.ValueType.Int8} + }, + { + name: "CFG", + input: false, + output: true, + raw: {min: 0, max: 255}, + pct: {min: 0, max: 100}, + si: {min: 0, max: 255, symbol: ""}, + values: {count: 2, type: Consts.ValueType.Int8} + } + ] + super(hub, portId, modes, Consts.DeviceType.TECHNIC_MEDIUM_HUB_ACCELEROMETER); + + this._eventHandlers.accel = (data: IEventData) => { + const [x, y, z] = data.si; + /** + * Emits when accelerometer detects movement. Measured in mG. + * @event TechnicMediumHubAccelerometerSensor#accel + * @type {object} + * @param {number} x + * @param {number} y + * @param {number} z + */ + this.notify("accel", { x, y, z }); + }; } - - public receive (message: Buffer) { - if (this.hub.autoParse) { - return super.receive(message); - } - - const mode = this._mode; - - switch (mode) { - case Mode.ACCEL: - /** - * Emits when accelerometer detects movement. Measured in mG. - * @event TechnicMediumHubAccelerometerSensor#accel - * @type {object} - * @param {number} x - * @param {number} y - * @param {number} z - */ - const x = Math.round(message.readInt16LE(4) / 4.096); - const y = Math.round(message.readInt16LE(6) / 4.096); - const z = Math.round(message.readInt16LE(8) / 4.096); - this.notify("accel", { x, y, z }); - break; - } - } - -} - -export enum Mode { - ACCEL = 0x00 } - -export const ModeMap: {[event: string]: number} = { - "accel": Mode.ACCEL -}; diff --git a/src/devices/technicmediumhubgyrosensor.ts b/src/devices/technicmediumhubgyrosensor.ts index 961275e7..abff4428 100644 --- a/src/devices/technicmediumhubgyrosensor.ts +++ b/src/devices/technicmediumhubgyrosensor.ts @@ -1,6 +1,6 @@ import { Device } from "./device"; -import { IDeviceInterface } from "../interfaces"; +import { IDeviceInterface, IEventData } from "../interfaces"; import * as Consts from "../consts"; @@ -11,40 +11,30 @@ import * as Consts from "../consts"; export class TechnicMediumHubGyroSensor extends Device { constructor (hub: IDeviceInterface, portId: number) { - super(hub, portId, ModeMap, Consts.DeviceType.TECHNIC_MEDIUM_HUB_GYRO_SENSOR); + const modes = [ + { + name: "gyro", + input: true, + output: false, + raw: {min: -28571.419921875, max: 28571.419921875}, + pct: {min: -100, max: 100}, + si: {min: -2000, max: 2000, symbol: "DPS"}, + values: {count: 3, type: Consts.ValueType.Int16} + } + ]; + super(hub, portId, modes, Consts.DeviceType.TECHNIC_MEDIUM_HUB_GYRO_SENSOR); + + this._eventHandlers.gyro = (data: IEventData) => { + const [x, y, z] = data.si; + /** + * Emits when gyroscope detects movement. Measured in DPS - degrees per second. + * @event TechnicMediumHubGyroSensor#gyro + * @type {object} + * @param {number} x + * @param {number} y + * @param {number} z + */ + this.notify("gyro", { x, y, z }); + }; } - - public receive (message: Buffer) { - if (this.hub.autoParse) { - return super.receive(message); - } - - const mode = this._mode; - - switch (mode) { - case Mode.GYRO: - /** - * Emits when gyroscope detects movement. Measured in DPS - degrees per second. - * @event TechnicMediumHubGyroSensor#gyro - * @type {object} - * @param {number} x - * @param {number} y - * @param {number} z - */ - const x = Math.round(message.readInt16LE(4) * 7 / 400); - const y = Math.round(message.readInt16LE(6) * 7 / 400); - const z = Math.round(message.readInt16LE(8) * 7 / 400); - this.notify("gyro", { x, y, z }); - break; - } - } - -} - -export enum Mode { - GYRO = 0x00 } - -export const ModeMap: {[event: string]: number} = { - "gyro": Mode.GYRO -}; diff --git a/src/devices/technicmediumhubtiltsensor.ts b/src/devices/technicmediumhubtiltsensor.ts index 1fefcb1c..09c2e1d3 100644 --- a/src/devices/technicmediumhubtiltsensor.ts +++ b/src/devices/technicmediumhubtiltsensor.ts @@ -1,6 +1,6 @@ import { Device } from "./device"; -import { IDeviceInterface } from "../interfaces"; +import { IDeviceInterface, IEventData } from "../interfaces"; import * as Consts from "../consts"; @@ -11,40 +11,40 @@ import * as Consts from "../consts"; export class TechnicMediumHubTiltSensor extends Device { constructor (hub: IDeviceInterface, portId: number) { - super(hub, portId, ModeMap, Consts.DeviceType.TECHNIC_MEDIUM_HUB_TILT_SENSOR); - } - - public receive (message: Buffer) { - if (this.hub.autoParse) { - return super.receive(message); - } - - const mode = this._mode; - - switch (mode) { - case Mode.TILT: - /** - * Emits when a tilt sensor is activated. - * @event TechnicMediumHubTiltSensor#tilt - * @type {object} - * @param {number} x - * @param {number} y - * @param {number} z - */ - const z = -message.readInt16LE(4); - const y = message.readInt16LE(6); - const x = message.readInt16LE(8); - this.notify("tilt", { x, y, z }); - break; - } + const modes = [ + { + name: "tilt", // POS + input: false, + output: true, + raw: {min: -180, max: 180}, + pct: {min: -100, max: 100}, + si: {min: -180, max: 180, symbol: "DEG"}, + values: {count: 3, type: Consts.ValueType.Int16}, + }, + { + name: "IMP", + input: true, + output: false, + raw: {min: 0, max: 100}, + pct: {min: 0, max: 100}, + si: {min: 0, max: 100, symbol: "CNT"}, + values: {count: 1, type: Consts.ValueType.Int32}, + } + ] + super(hub, portId, modes, Consts.DeviceType.TECHNIC_MEDIUM_HUB_TILT_SENSOR); + + this._eventHandlers.tilt = (data: IEventData) => { + const [x, y, z] = data.si; + /** + * Emits when a tilt sensor is activated. + * @event TechnicMediumHubTiltSensor#tilt + * @type {object} + * @param {number} x + * @param {number} y + * @param {number} z + */ + this.notify("tilt", { x, y, z }); + }; } } - -export enum Mode { - TILT = 0x00 -} - -export const ModeMap: {[event: string]: number} = { - "tilt": Mode.TILT -}; diff --git a/src/devices/technicxlargelinearmotor.ts b/src/devices/technicxlargelinearmotor.ts index 7f2503c1..c55b0249 100644 --- a/src/devices/technicxlargelinearmotor.ts +++ b/src/devices/technicxlargelinearmotor.ts @@ -11,7 +11,7 @@ import * as Consts from "../consts"; export class TechnicXLargeLinearMotor extends AbsoluteMotor { constructor (hub: IDeviceInterface, portId: number) { - super(hub, portId, {}, Consts.DeviceType.TECHNIC_XLARGE_LINEAR_MOTOR); + super(hub, portId, [], Consts.DeviceType.TECHNIC_XLARGE_LINEAR_MOTOR); } } diff --git a/src/devices/tiltsensor.ts b/src/devices/tiltsensor.ts index bd779388..9e707716 100644 --- a/src/devices/tiltsensor.ts +++ b/src/devices/tiltsensor.ts @@ -1,6 +1,6 @@ import { Device } from "./device"; -import { IDeviceInterface } from "../interfaces"; +import { IDeviceInterface, IEventData } from "../interfaces"; import * as Consts from "../consts"; @@ -11,38 +11,29 @@ import * as Consts from "../consts"; export class TiltSensor extends Device { constructor (hub: IDeviceInterface, portId: number) { - super(hub, portId, ModeMap, Consts.DeviceType.TILT_SENSOR); + const modes = [ + { + name: "tilt", // ANGLE + input: true, + output: false, + raw: { min: -90, max: 90 }, + pct: { min: -100, max: 100 }, + si: { min: -90, max: 90, symbol: "DEG" }, + values: { count: 2, type: Consts.ValueType.Int8 } + } + ] + super(hub, portId, modes, Consts.DeviceType.TILT_SENSOR); + + this._eventHandlers.tilt = (data: IEventData) => { + const [x, y] = data.si; + /** + * Emits when a tilt sensor is activated. + * @event TiltSensor#tilt + * @type {object} + * @param {number} x + * @param {number} y + */ + this.notify("tilt", { x, y }); + }; } - - public receive (message: Buffer) { - if (this.hub.autoParse) { - return super.receive(message); - } - - const mode = this._mode; - - switch (mode) { - case Mode.TILT: - const x = message.readInt8(this.isWeDo2SmartHub ? 2 : 4); - const y = message.readInt8(this.isWeDo2SmartHub ? 3 : 5); - /** - * Emits when a tilt sensor is activated. - * @event TiltSensor#tilt - * @type {object} - * @param {number} x - * @param {number} y - */ - this.notify("tilt", { x, y }); - break; - } - } - -} - -export enum Mode { - TILT = 0x00 } - -export const ModeMap: {[event: string]: number} = { - "tilt": Mode.TILT -}; diff --git a/src/devices/trainmotor.ts b/src/devices/trainmotor.ts index 0315fbf5..998520d3 100644 --- a/src/devices/trainmotor.ts +++ b/src/devices/trainmotor.ts @@ -11,7 +11,7 @@ import * as Consts from "../consts"; export class TrainMotor extends BasicMotor { constructor (hub: IDeviceInterface, portId: number) { - super(hub, portId, {}, Consts.DeviceType.TRAIN_MOTOR); + super(hub, portId, [], Consts.DeviceType.TRAIN_MOTOR); } } diff --git a/src/devices/voltagesensor.ts b/src/devices/voltagesensor.ts index ba3b3ecd..14db4e68 100644 --- a/src/devices/voltagesensor.ts +++ b/src/devices/voltagesensor.ts @@ -1,6 +1,6 @@ import { Device } from "./device"; -import { IDeviceInterface } from "../interfaces"; +import { IDeviceInterface, IEventData } from "../interfaces"; import * as Consts from "../consts"; @@ -9,51 +9,43 @@ import * as Consts from "../consts"; * @extends Device */ export class VoltageSensor extends Device { - constructor (hub: IDeviceInterface, portId: number) { - super(hub, portId, ModeMap, Consts.DeviceType.VOLTAGE_SENSOR); + const modes = [ + { + name: "voltage", // VLT L + input: true, + output: false, + raw: {min: 0, max: MaxVoltageRaw[hub.type] || MaxVoltageRaw[Consts.HubType.UNKNOWN]}, + pct: {min: 0, max: 100}, + si: {min: 0, max: MaxVoltageValue[hub.type] || MaxVoltageValue[Consts.HubType.UNKNOWN], symbol: "mV"}, + values: {count: 1, type: Consts.ValueType.Int16} + }, + { + name: "VLT S", + input: true, + output: false, + raw: {min: 0, max: MaxVoltageRaw[hub.type] || MaxVoltageRaw[Consts.HubType.UNKNOWN]}, + pct: {min: 0, max: 100}, + si: {min: 0, max: MaxVoltageValue[hub.type] || MaxVoltageValue[Consts.HubType.UNKNOWN], symbol: "mV"}, + values: {count: 1, type: Consts.ValueType.Int16} + } + ]; + + super(hub, portId, modes, Consts.DeviceType.VOLTAGE_SENSOR); + + this._eventHandlers.voltage = (data: IEventData) => { + const [voltage] = data.si; + /** + * Emits when a voltage change is detected. + * @event VoltageSensor#voltage + * @type {object} + * @param {number} voltage + */ + this.notify("voltage", { voltage }); + }; } - - public receive (message: Buffer) { - const mode = this._mode; - - switch (mode) { - case Mode.VOLTAGE: - if (this.isWeDo2SmartHub) { - const voltage = message.readInt16LE(2) / 40; - this.notify("voltage", { voltage }); - } else { - let maxVoltageValue = MaxVoltageValue[this.hub.type]; - if (maxVoltageValue === undefined) { - maxVoltageValue = MaxVoltageValue[Consts.HubType.UNKNOWN]; - } - let maxVoltageRaw = MaxVoltageRaw[this.hub.type]; - if (maxVoltageRaw === undefined) { - maxVoltageRaw = MaxVoltageRaw[Consts.HubType.UNKNOWN]; - } - const voltage = message.readUInt16LE(4) * maxVoltageValue / maxVoltageRaw; - /** - * Emits when a voltage change is detected. - * @event VoltageSensor#voltage - * @type {object} - * @param {number} voltage - */ - this.notify("voltage", { voltage }); - } - break; - } - } - } -export enum Mode { - VOLTAGE = 0x00 -} - -export const ModeMap: {[event: string]: number} = { - "voltage": Mode.VOLTAGE -}; - const MaxVoltageValue: {[hubType: number]: number} = { [Consts.HubType.UNKNOWN]: 9.615, [Consts.HubType.DUPLO_TRAIN_BASE]: 6.4, diff --git a/src/hubs/basehub.ts b/src/hubs/basehub.ts index 2df24006..3f12fba3 100644 --- a/src/hubs/basehub.ts +++ b/src/hubs/basehub.ts @@ -366,7 +366,7 @@ export class BaseHub extends EventEmitter { } this._attachedDevices[device.portId] = device; - device.on('ready', () => { + const deviceAttach = () => { /** * Emits when a device is attached to the Hub. * @event Hub#attach @@ -382,7 +382,13 @@ export class BaseHub extends EventEmitter { this._attachCallbacks.splice(i, 1); } } - }) + }; + + if (device.isReady) { + deviceAttach() + } else { + device.once('ready', deviceAttach) + } } diff --git a/src/hubs/lpf2hub.ts b/src/hubs/lpf2hub.ts index baff4aba..188952b4 100644 --- a/src/hubs/lpf2hub.ts +++ b/src/hubs/lpf2hub.ts @@ -147,20 +147,16 @@ export class LPF2Hub extends BaseHub { this._parsePortMessage(message); break; } - case 0x43: { - this._parsePortInformationResponse(message); - break; - } - case 0x44: { - this._parseModeInformationResponse(message); - break; - } - case 0x45: { - this._parseSensorMessage(message); - break; - } - case 0x82: { - this._parsePortAction(message); + case 0x43: // PortInformationResponse + case 0x44: // ModeInformationResponse + case 0x45: // SensorMessage + case 0x82: { // PortAction + const portId = message[3]; + const device = this._getDeviceByPortId(portId); + + if (device) { + device.receive(message); + } break; } } @@ -240,13 +236,14 @@ export class LPF2Hub extends BaseHub { } private async _parsePortMessage (message: Buffer) { - const portId = message[3]; const event = message[4]; const deviceType = event ? message.readUInt16LE(5) : 0; // Handle device attachments if (event === 0x01) { + const device = this._createDevice(deviceType, portId); + this._attachDevice(device); if (modeInfoDebug.enabled) { const deviceTypeName = Consts.DeviceTypeNames[message[5]] || "Unknown"; @@ -260,8 +257,6 @@ export class LPF2Hub extends BaseHub { await this._sendPortInformationRequest(portId); } - const device = this._createDevice(deviceType, portId); - this._attachDevice(device); // Handle device detachments } else if (event === 0x00) { @@ -297,126 +292,4 @@ export class LPF2Hub extends BaseHub { await this.send(Buffer.from([0x21, port, 0x01]), Consts.BLECharacteristic.LPF2_ALL); await this.send(Buffer.from([0x21, port, 0x02]), Consts.BLECharacteristic.LPF2_ALL); // Mode combinations } - - - private async _parsePortInformationResponse (message: Buffer) { - const port = message[3]; - if (message[4] === 2) { - const modeCombinationMasks: number[] = []; - for (let i = 5; i < message.length; i += 2) { - modeCombinationMasks.push(message.readUInt16LE(i)); - } - modeInfoDebug(`Port ${toHex(port)}, mode combinations [${modeCombinationMasks.map((c) => toBin(c, 0)).join(", ")}]`); - return; - } - const count = message[6]; - const input = toBin(message.readUInt16LE(7), count); - const output = toBin(message.readUInt16LE(9), count); - modeInfoDebug(`Port ${toHex(port)}, total modes ${count}, input modes ${input}, output modes ${output}`); - - this._devicesModes[port] = new Array(+count); - - for (let i = 0; i < count; i++) { - this._devicesModes[port][i] = { - name: '', - input: input[i] === '1', - output: output[i] === '1', - raw: { min: 0, max: 255 }, - pct: { min: 0, max: 100 }, - si: { min: 0, max: 255, symbol: '' }, - values: { count: 1, type: Consts.ValueType.Int8 }, - }; - await this._sendModeInformationRequest(port, i, 0x00); // Mode Name - await this._sendModeInformationRequest(port, i, 0x01); // RAW Range - await this._sendModeInformationRequest(port, i, 0x02); // PCT Range - await this._sendModeInformationRequest(port, i, 0x03); // SI Range - await this._sendModeInformationRequest(port, i, 0x04); // SI Symbol - await this._sendModeInformationRequest(port, i, 0x80); // Value Format - } - } - - - private _sendModeInformationRequest (port: number, mode: number, type: number) { - return this.send(Buffer.from([0x22, port, mode, type]), Consts.BLECharacteristic.LPF2_ALL); - } - - - private _parseModeInformationResponse (message: Buffer) { - const port = message[3]; - const portHex = toHex(port); - const mode = message[4]; - const type = message[5]; - switch (type) { - case 0x00: // Mode Name - const name = message.slice(6, message.length).toString().replace(/\0/g, ''); - modeInfoDebug(`Port ${portHex}, mode ${mode}, name ${name}`); - this._devicesModes[port][mode].name=name; - break; - case 0x01: // RAW Range - modeInfoDebug(`Port ${portHex}, mode ${mode}, RAW min ${message.readFloatLE(6)}, max ${message.readFloatLE(10)}`); - this._devicesModes[port][mode].raw.min=message.readFloatLE(6); - this._devicesModes[port][mode].raw.max=message.readFloatLE(10); - break; - case 0x02: // PCT Range - modeInfoDebug(`Port ${portHex}, mode ${mode}, PCT min ${message.readFloatLE(6)}, max ${message.readFloatLE(10)}`); - this._devicesModes[port][mode].pct.min=message.readFloatLE(6); - this._devicesModes[port][mode].pct.max=message.readFloatLE(10); - break; - case 0x03: // SI Range - modeInfoDebug(`Port ${portHex}, mode ${mode}, SI min ${message.readFloatLE(6)}, max ${message.readFloatLE(10)}`); - this._devicesModes[port][mode].si.min=message.readFloatLE(6); - this._devicesModes[port][mode].si.max=message.readFloatLE(10); - break; - case 0x04: // SI Symbol - const symbol = message.slice(6, message.length).toString().replace(/\0/g, ''); - modeInfoDebug(`Port ${portHex}, mode ${mode}, SI symbol ${symbol}`); - this._devicesModes[port][mode].si.symbol=symbol; - break; - case 0x80: // Value Format - const numValues = message[6]; - const dataType = message[7]; - const totalFigures = message[8]; - const decimals = message[9]; - modeInfoDebug(`Port ${portHex}, mode ${mode}, Value ${numValues} x ${dataType}, Decimal format ${totalFigures}.${decimals}`); - this._devicesModes[port][mode].values.count=numValues; - this._devicesModes[port][mode].values.type=dataType; - - if (this._autoParse && mode === this._devicesModes[port].length - 1) { - const device = this._getDeviceByPortId(port); - - if (device) { - device.setModes(this._devicesModes[port]); - } - } - } - } - - - private _parsePortAction (message: Buffer) { - - const portId = message[3]; - const device = this._getDeviceByPortId(portId); - - if (device) { - const finished = (message[4] === 0x0a); - if (finished) { - device.finish(); - } - } - - } - - - private _parseSensorMessage (message: Buffer) { - - const portId = message[3]; - const device = this._getDeviceByPortId(portId); - - if (device) { - device.receive(message); - } - - } - - } diff --git a/src/interfaces.ts b/src/interfaces.ts index e0237cb0..a839888c 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -48,4 +48,10 @@ export interface IMode { count: number; type: Consts.ValueType; }; -} \ No newline at end of file +} + +export interface IEventData { + raw: number[], + pct: number[], + si: number[], +} From 8feffd60527501c807a1869abf650c1893043941 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Bonnargent?= Date: Sun, 28 Jun 2020 23:09:47 +0200 Subject: [PATCH 09/14] Prevent modeInfoDebug to overwite modes --- src/devices/device.ts | 56 +++++++++++++++++++++++++++---------------- 1 file changed, 36 insertions(+), 20 deletions(-) diff --git a/src/devices/device.ts b/src/devices/device.ts index 796053bf..699d48e4 100644 --- a/src/devices/device.ts +++ b/src/devices/device.ts @@ -269,18 +269,22 @@ export class Device extends EventEmitter { const output = toBin(message.readUInt16LE(9), this._modeCount); modeInfoDebug(`Port ${toHex(this.portId)}, total modes ${this._modeCount}, input modes ${input}, output modes ${output}`); - this._modes = new Array(+this._modeCount); + if (this.autoParse) { + this._modes = new Array(+this._modeCount); + } for (let i = 0; i < this._modeCount; i++) { - this._modes[i] = { - name: '', - input: input[this._modeCount - i - 1] === '1', - output: output[this._modeCount - i - 1] === '1', - raw: { min: 0, max: 255 }, - pct: { min: 0, max: 100 }, - si: { min: 0, max: 255, symbol: '' }, - values: { count: 1, type: Consts.ValueType.Int8 }, - }; + if (this.autoParse) { + this._modes[i] = { + name: '', + input: input[this._modeCount - i - 1] === '1', + output: output[this._modeCount - i - 1] === '1', + raw: { min: 0, max: 255 }, + pct: { min: 0, max: 100 }, + si: { min: 0, max: 255, symbol: '' }, + values: { count: 1, type: Consts.ValueType.Int8 }, + }; + } await this._sendModeInformationRequest(i, 0x00); // Mode Name await this._sendModeInformationRequest(i, 0x01); // RAW Range await this._sendModeInformationRequest(i, 0x02); // PCT Range @@ -302,37 +306,47 @@ export class Device extends EventEmitter { case 0x00: { // Mode Name const name = message.slice(6, message.length).toString().replace(/\0/g, ''); modeInfoDebug(`${debugHeader} name ${name}`); - this._modes[mode].name=name; + if (this.autoParse) { + this._modes[mode].name=name; + } break; } case 0x01: { // RAW Range const min = message.readFloatLE(6); const max = message.readFloatLE(10); modeInfoDebug(`${debugHeader} RAW min ${min}, max ${max}`); - this._modes[mode].raw.min=min - this._modes[mode].raw.max=max; + if (this.autoParse) { + this._modes[mode].raw.min=min + this._modes[mode].raw.max=max; + } break; } case 0x02: { // PCT Range const min = message.readFloatLE(6); const max = message.readFloatLE(10); modeInfoDebug(`${debugHeader} PCT min ${min}, max ${max}`); - this._modes[mode].pct.min=min; - this._modes[mode].pct.max=max; + if (this.autoParse) { + this._modes[mode].pct.min=min; + this._modes[mode].pct.max=max; + } break; } case 0x03: {// SI Range const min = message.readFloatLE(6); const max = message.readFloatLE(10); modeInfoDebug(`${debugHeader} SI min ${min}, max ${max}`); - this._modes[mode].si.min=min; - this._modes[mode].si.max=max; + if (this.autoParse) { + this._modes[mode].si.min=min; + this._modes[mode].si.max=max; + } break; } case 0x04: {// SI Symbol const symbol = message.slice(6, message.length).toString().replace(/\0/g, ''); modeInfoDebug(`${debugHeader} SI symbol ${symbol}`); - this._modes[mode].si.symbol=symbol; + if (this.autoParse) { + this._modes[mode].si.symbol=symbol; + } break; } case 0x80: {// Value Format @@ -341,11 +355,13 @@ export class Device extends EventEmitter { const totalFigures = message[8]; const decimals = message[9]; modeInfoDebug(`${debugHeader} Value ${numValues} x ${dataType}, Decimal format ${totalFigures}.${decimals}`); + if (this.autoParse) { this._modes[mode].values.count=numValues; this._modes[mode].values.type=dataType; - if (this.autoParse && mode === this._modeCount - 1) { - this._init(); + if (mode === this._modeCount - 1) { + this._init(); + } } } } From 4c429e24e5c0992b330b567732364c47925f0cd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Bonnargent?= Date: Sun, 28 Jun 2020 23:46:34 +0200 Subject: [PATCH 10/14] Fix hubled RGB mode --- src/devices/hubled.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/devices/hubled.ts b/src/devices/hubled.ts index a4ad19aa..75fab718 100644 --- a/src/devices/hubled.ts +++ b/src/devices/hubled.ts @@ -73,8 +73,8 @@ export class HubLED extends Device { this.send(Buffer.from([0x06, 0x17, 0x01, 0x02]), Consts.BLECharacteristic.WEDO2_PORT_TYPE_WRITE); this.send(Buffer.from([0x06, 0x04, 0x03, red, green, blue]), Consts.BLECharacteristic.WEDO2_MOTOR_VALUE_WRITE); } else { - this.subscribe(this._modeMap["COL O"]); - this.writeDirect(this._modeMap["COL O"], Buffer.from([red, green, blue])); + this.subscribe(this._modeMap["RGB O"]); + this.writeDirect(this._modeMap["RGB O"], Buffer.from([red, green, blue])); } return resolve(); }); From c830cb97a75e2b6b2597f448d1a7bb0e0c674ae8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Bonnargent?= Date: Mon, 29 Jun 2020 09:21:52 +0200 Subject: [PATCH 11/14] Attemp to be weDo2.0 compatible --- src/devices/basicmotor.ts | 2 +- src/devices/colordistancesensor.ts | 1 + src/devices/currentsensor.ts | 3 +++ src/devices/device.ts | 13 +++++++++---- src/devices/motionsensor.ts | 1 + src/devices/tachomotor.ts | 3 ++- src/devices/technicforcesensor.ts | 1 + src/devices/tiltsensor.ts | 1 + src/devices/voltagesensor.ts | 3 +++ src/hubs/wedo2smarthub.ts | 2 +- src/interfaces.ts | 1 + 11 files changed, 24 insertions(+), 7 deletions(-) diff --git a/src/devices/basicmotor.ts b/src/devices/basicmotor.ts index cff33685..44bf4ec9 100644 --- a/src/devices/basicmotor.ts +++ b/src/devices/basicmotor.ts @@ -6,7 +6,7 @@ import * as Consts from "../consts"; import { calculateRamp, mapSpeed } from "../utils"; -export const modes = [ +export const modes: IMode[] = [ { name: "POWER", // or LPF2-MOTOR input: false, diff --git a/src/devices/colordistancesensor.ts b/src/devices/colordistancesensor.ts index 4d77fac8..6d629562 100644 --- a/src/devices/colordistancesensor.ts +++ b/src/devices/colordistancesensor.ts @@ -16,6 +16,7 @@ export class ColorDistanceSensor extends Device { name: "color", // COLOR input: true, output: false, + weDo2SmartHub: true, raw: { min: 0, max: 10 }, pct: { min: 0, max: 100 }, si: { min: 0, max: 10, symbol: "IDX" }, diff --git a/src/devices/currentsensor.ts b/src/devices/currentsensor.ts index 79444d8a..375cdf93 100644 --- a/src/devices/currentsensor.ts +++ b/src/devices/currentsensor.ts @@ -16,6 +16,7 @@ export class CurrentSensor extends Device { name: "current", // CUR L input: true, output: false, + weDo2SmartHub: true, raw: {min: 0, max: MaxCurrentRaw[hub.type] || MaxCurrentRaw[Consts.HubType.UNKNOWN]}, pct: {min: 0, max: 100}, si: {min: 0, max: MaxCurrentValue[hub.type] || MaxCurrentValue[Consts.HubType.UNKNOWN], symbol: "mA"}, @@ -51,8 +52,10 @@ export class CurrentSensor extends Device { const MaxCurrentValue: {[hubType: number]: number} = { [Consts.HubType.UNKNOWN]: 2444, [Consts.HubType.TECHNIC_MEDIUM_HUB]: 4175, + [Consts.HubType.WEDO2_SMART_HUB]: 5000, }; const MaxCurrentRaw: {[hubType: number]: number} = { [Consts.HubType.UNKNOWN]: 4095, + [Consts.HubType.WEDO2_SMART_HUB]: 5000000, }; diff --git a/src/devices/device.ts b/src/devices/device.ts index 699d48e4..44f83c69 100644 --- a/src/devices/device.ts +++ b/src/devices/device.ts @@ -245,7 +245,7 @@ export class Device extends EventEmitter { break; } case 0x45: { - this._parseSensorMessage(message); + this.parseSensorMessage(message); break; } case 0x82: { @@ -367,17 +367,22 @@ export class Device extends EventEmitter { } } - private _parseSensorMessage(message: Buffer) { + public parseSensorMessage(message: Buffer) { const mode = this._mode; if (mode === undefined) { return; } - const { name, raw, pct, si, values } = this._modes[mode]; + + const { name, raw, pct, si, values, weDo2SmartHub } = this._modes[mode]; + if (this._isWeDo2SmartHub && !weDo2SmartHub) { + return; + } const valueSize = Consts.ValueTypeSize[values.type]; const data = []; + const byteStart = this._isWeDo2SmartHub ? 2 : 4; for(let v = 0; v < values.count; v++) { - const offset = 4 + v * valueSize; + const offset = byteStart + v * valueSize; switch(values.type) { case Consts.ValueType.Int8: data.push(message.readInt8(offset)); diff --git a/src/devices/motionsensor.ts b/src/devices/motionsensor.ts index bd9b50de..b37da5bb 100644 --- a/src/devices/motionsensor.ts +++ b/src/devices/motionsensor.ts @@ -16,6 +16,7 @@ export class MotionSensor extends Device { name: "distance", // PROX input: true, output: false, + weDo2SmartHub: true, raw: { min: 0, max: 512 }, pct: { min: 0, max: 100 }, si: { min: 0, max: 5120, symbol: "mm" }, diff --git a/src/devices/tachomotor.ts b/src/devices/tachomotor.ts index 95725d5b..00626f53 100644 --- a/src/devices/tachomotor.ts +++ b/src/devices/tachomotor.ts @@ -5,7 +5,7 @@ import { IDeviceInterface, IMode, IEventData } from "../interfaces"; import * as Consts from "../consts"; import { mapSpeed } from "../utils"; -export const modes = BasicMotorModes.concat([ +export const modes: IMode[] = BasicMotorModes.concat([ // POWER { name: "speed", // SPEED @@ -20,6 +20,7 @@ export const modes = BasicMotorModes.concat([ name: "rotate", // POS input: true, output: true, + weDo2SmartHub: true, raw: { min: -360, max: 360 }, pct: { min: -100, max: 100 }, si: { min: -360, max: 360, symbol: "DEG" }, diff --git a/src/devices/technicforcesensor.ts b/src/devices/technicforcesensor.ts index e1ee2ca5..ef76b2f5 100644 --- a/src/devices/technicforcesensor.ts +++ b/src/devices/technicforcesensor.ts @@ -16,6 +16,7 @@ export class TechnicForceSensor extends Device { name: "force", input: true, output: false, + weDo2SmartHub: true, raw: { min: 0, max: 255 }, pct: { min: 0, max: 100 }, si: { min: 0, max: 25.5, symbol: "N" }, diff --git a/src/devices/tiltsensor.ts b/src/devices/tiltsensor.ts index 9e707716..0cf1855f 100644 --- a/src/devices/tiltsensor.ts +++ b/src/devices/tiltsensor.ts @@ -16,6 +16,7 @@ export class TiltSensor extends Device { name: "tilt", // ANGLE input: true, output: false, + weDo2SmartHub: true, raw: { min: -90, max: 90 }, pct: { min: -100, max: 100 }, si: { min: -90, max: 90, symbol: "DEG" }, diff --git a/src/devices/voltagesensor.ts b/src/devices/voltagesensor.ts index 14db4e68..0f64c48b 100644 --- a/src/devices/voltagesensor.ts +++ b/src/devices/voltagesensor.ts @@ -15,6 +15,7 @@ export class VoltageSensor extends Device { name: "voltage", // VLT L input: true, output: false, + weDo2SmartHub: true, raw: {min: 0, max: MaxVoltageRaw[hub.type] || MaxVoltageRaw[Consts.HubType.UNKNOWN]}, pct: {min: 0, max: 100}, si: {min: 0, max: MaxVoltageValue[hub.type] || MaxVoltageValue[Consts.HubType.UNKNOWN], symbol: "mV"}, @@ -50,6 +51,7 @@ const MaxVoltageValue: {[hubType: number]: number} = { [Consts.HubType.UNKNOWN]: 9.615, [Consts.HubType.DUPLO_TRAIN_BASE]: 6.4, [Consts.HubType.REMOTE_CONTROL]: 6.4, + [Consts.HubType.WEDO2_SMART_HUB]: 10, }; const MaxVoltageRaw: {[hubType: number]: number} = { @@ -57,4 +59,5 @@ const MaxVoltageRaw: {[hubType: number]: number} = { [Consts.HubType.DUPLO_TRAIN_BASE]: 3047, [Consts.HubType.REMOTE_CONTROL]: 3200, [Consts.HubType.TECHNIC_MEDIUM_HUB]: 4095, + [Consts.HubType.WEDO2_SMART_HUB]: 400, }; diff --git a/src/hubs/wedo2smarthub.ts b/src/hubs/wedo2smarthub.ts index 323fe8d2..8aacd840 100644 --- a/src/hubs/wedo2smarthub.ts +++ b/src/hubs/wedo2smarthub.ts @@ -216,7 +216,7 @@ export class WeDo2SmartHub extends BaseHub { const device = this._getDeviceByPortId(portId); if (device) { - device.receive(message); + device.parseSensorMessage(message); } } diff --git a/src/interfaces.ts b/src/interfaces.ts index a839888c..9dc2ed77 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -31,6 +31,7 @@ export interface IMode { name: string; input: boolean; output: boolean; + weDo2SmartHub?: boolean; raw: { min: number; max: number; From df8ebd205f84d53192ba6971ccde219338a00dee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Bonnargent?= Date: Mon, 29 Jun 2020 09:22:16 +0200 Subject: [PATCH 12/14] Renamed input/output modes getters --- src/devices/device.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/devices/device.ts b/src/devices/device.ts index 44f83c69..cb056cf7 100644 --- a/src/devices/device.ts +++ b/src/devices/device.ts @@ -155,17 +155,17 @@ export class Device extends EventEmitter { /** * @readonly - * @property {string[]} events List of availlable events (input modes). + * @property {string[]} inputs List of availlable input modes. */ - public get events () { + public get inputs () { return this._modes.filter(mode => mode.input).map(({ name }) => name); } /** * @readonly - * @property {string[]} writeModes List of availlable write (output modes). + * @property {string[]} outputs List of availlable output modes). */ - public get writeModes () { + public get outputs () { return this._modes.filter(mode => mode.output).map(({ name }) => name); } From 405b5f2bdea70a92f656d9be8b3b59ff12ade36e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Bonnargent?= Date: Sat, 4 Jul 2020 11:08:57 +0200 Subject: [PATCH 13/14] fix waitForDevice[...] using isReady --- src/hubs/basehub.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/hubs/basehub.ts b/src/hubs/basehub.ts index 3f12fba3..29b986df 100644 --- a/src/hubs/basehub.ts +++ b/src/hubs/basehub.ts @@ -223,7 +223,7 @@ export class BaseHub extends EventEmitter { public waitForDeviceAtPort (portName: string) { return new Promise((resolve) => { const existingDevice = this.getDeviceAtPort(portName); - if (existingDevice) { + if (existingDevice && existingDevice.isReady) { return resolve(existingDevice); } this._attachCallbacks.push((device) => { @@ -269,7 +269,7 @@ export class BaseHub extends EventEmitter { */ public waitForDeviceByType (deviceType: number) { return new Promise((resolve) => { - const existingDevices = this.getDevicesByType(deviceType); + const existingDevices = this.getDevicesByType(deviceType).filter(device => device.isReady); if (existingDevices.length >= 1) { return resolve(existingDevices[0]); } From 351bc90d7acb532088caf19bab2521a35ea68f07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Bonnargent?= Date: Sat, 4 Jul 2020 11:11:36 +0200 Subject: [PATCH 14/14] No more clamping in normalize --- src/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils.ts b/src/utils.ts index 23d83a3c..6790e06a 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -22,7 +22,7 @@ export const normalize = (value: number, { raw = {min: 0, max: 100}, out = { min out: out.max - out.min, }; - return (clamp(value, raw) - raw.min) / ranges.raw * ranges.out + out.min; + return (value - raw.min) / ranges.raw * ranges.out + out.min; } export const mapSpeed = (speed: number) => {