diff --git a/src/consts.ts b/src/consts.ts index a06f70a0..897f5ce7 100644 --- a/src/consts.ts +++ b/src/consts.ts @@ -636,4 +636,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..05ffbd41 100644 --- a/src/devices/absolutemotor.ts +++ b/src/devices/absolutemotor.ts @@ -1,40 +1,65 @@ -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); - public receive (message: Buffer) { - 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; - } + 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 }); + }; } + /** * Rotate a motor by a given angle. * @method AbsoluteMotor#gotoAngle @@ -117,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..44bf4ec9 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: IMode[] = [ + { + 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 67868cc9..6d629562 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,79 +11,151 @@ 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) { - 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, + weDo2SmartHub: 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: "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. * @@ -165,7 +237,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); } } @@ -185,7 +257,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(); @@ -200,20 +272,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..375cdf93 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,54 +11,51 @@ 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, + 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"}, + 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, + [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 dfd86475..cb056cf7 100644 --- a/src/devices/device.ts +++ b/src/devices/device.ts @@ -1,9 +1,14 @@ import { EventEmitter } from "events"; -import { IDeviceInterface } from "../interfaces"; +import { IDeviceInterface, IMode, IEventData } from "../interfaces"; import * as Consts from "../consts"; +import { normalize, toHex, toBin } from "../utils"; + +import Debug = require("debug"); +const debug = Debug("device"); +const modeInfoDebug = Debug("lpf2hubmodeinfo"); /** * @class Device * @extends EventEmitter @@ -13,29 +18,48 @@ 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") { return; @@ -64,6 +88,9 @@ export class Device extends EventEmitter { this.hub.on("newListener", eventAttachListener); this.on("newListener", eventAttachListener); this.hub.on("detach", deviceDetachListener); + + this._ready = true; + this.emit('ready'); } /** @@ -126,6 +153,30 @@ export class Device extends EventEmitter { return this._isVirtualPort; } + /** + * @readonly + * @property {string[]} inputs List of availlable input modes. + */ + public get inputs () { + return this._modes.filter(mode => mode.input).map(({ name }) => name); + } + + /** + * @readonly + * @property {string[]} outputs List of availlable output modes). + */ + public get outputs () { + 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); @@ -134,6 +185,36 @@ export class Device extends EventEmitter { } } + public autoParseWriteDirect (mode: string, ...data: number[]) { + if (!this.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.writeDirect(modeId, buf); + } + public send (data: Buffer, characteristic: string = Consts.BLECharacteristic.LPF2_ALL) { this._ensureConnected(); return this.hub.send(data, characteristic); @@ -153,6 +234,188 @@ 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}`); + + if (this.autoParse) { + this._modes = new Array(+this._modeCount); + } + + for (let i = 0; i < this._modeCount; i++) { + 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 + 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}`); + 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}`); + 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}`); + 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}`); + 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}`); + if (this.autoParse) { + 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}`); + if (this.autoParse) { + this._modes[mode].values.count=numValues; + this._modes[mode].values.type=dataType; + + if (mode === this._modeCount - 1) { + this._init(); + } + } + } + } + } + + public parseSensorMessage(message: Buffer) { + const mode = this._mode; + if (mode === undefined) { + return; + } + + 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 = byteStart + 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; + } + } + + 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) { @@ -192,4 +455,7 @@ export class Device extends EventEmitter { } } + 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 40183657..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,68 +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); - } - - public receive (message: Buffer) { - 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; + 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 } + } + ]; - 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); + super(hub, portId, modes, Consts.DeviceType.DUPLO_TRAIN_BASE_COLOR_SENSOR); - /** - * 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; - - } + 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 }); + }; } - } - -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 d45b9d78..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,34 +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) { - 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..75fab718 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["RGB O"]); + this.writeDirect(this._modeMap["RGB 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 a46e3a76..b37da5bb 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,30 +11,31 @@ 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, + weDo2SmartHub: true, + 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) { - 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 cdc002a9..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,26 +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) { - 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 c2cd644b..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,36 +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) { - 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 6650d064..00626f53 100644 --- a/src/devices/tachomotor.ts +++ b/src/devices/tachomotor.ts @@ -1,10 +1,33 @@ -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: IMode[] = 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, + weDo2SmartHub: 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,28 +39,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) { - 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. * @@ -178,11 +194,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 2807f9d7..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,51 +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) { - 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; + const modes = [ - case Mode.REFLECTIVITY: - const reflect = message[4]; + { + 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); - /** - * 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]; + 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 ambient light changes. - * @event TechnicColorSensor#ambient - * @type {object} - * @param {number} ambient Percentage, from 0 to 100. - */ - this.notify("ambient", { ambient }); - break; - } } /** @@ -71,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 7339c02c..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,37 +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) { - 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 30652373..ef76b2f5 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,61 +11,68 @@ 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) { - 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, + weDo2SmartHub: true, + 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 092a63f4..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,36 +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) { - 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 ac994f07..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,36 +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) { - 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 b421a724..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,36 +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) { - 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 0c38b559..0cf1855f 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,34 +11,30 @@ 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, + weDo2SmartHub: true, + 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) { - 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..0f64c48b 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,55 +9,49 @@ 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, + 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"}, + 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, [Consts.HubType.REMOTE_CONTROL]: 6.4, + [Consts.HubType.WEDO2_SMART_HUB]: 10, }; const MaxVoltageRaw: {[hubType: number]: number} = { @@ -65,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/basehub.ts b/src/hubs/basehub.ts index a6c06b11..29b986df 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 @@ -211,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) => { @@ -257,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]); } @@ -354,20 +366,28 @@ export class BaseHub extends EventEmitter { } 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); + const deviceAttach = () => { + /** + * 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); + } } + }; + + if (device.isReady) { + deviceAttach() + } else { + device.once('ready', deviceAttach) } } 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..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"; @@ -254,11 +251,12 @@ 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 || deviceType !== Consts.DeviceType.UNKNOWN) { await this._sendPortInformationRequest(portId); } - const device = this._createDevice(deviceType, portId); - this._attachDevice(device); // Handle device detachments } else if (event === 0x00) { @@ -294,94 +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}`); - - for (let i = 0; i < count; i++) { - 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 = toHex(message[3]); - 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()}`); - break; - case 0x01: // RAW Range - modeInfoDebug(`Port ${port}, mode ${mode}, RAW min ${message.readFloatLE(6)}, max ${message.readFloatLE(10)}`); - break; - case 0x02: // PCT Range - modeInfoDebug(`Port ${port}, mode ${mode}, PCT min ${message.readFloatLE(6)}, max ${message.readFloatLE(10)}`); - break; - case 0x03: // SI Range - modeInfoDebug(`Port ${port}, mode ${mode}, SI min ${message.readFloatLE(6)}, max ${message.readFloatLE(10)}`); - break; - case 0x04: // SI Symbol - modeInfoDebug(`Port ${port}, mode ${mode}, SI symbol ${message.slice(6, message.length).toString()}`); - break; - case 0x80: // Value Format - const numValues = message[6]; - const dataType = ["8bit", "16bit", "32bit", "float"][message[7]]; - const totalFigures = message[8]; - const decimals = message[9]; - modeInfoDebug(`Port ${port}, mode ${mode}, Value ${numValues} x ${dataType}, Decimal format ${totalFigures}.${decimals}`); - } - } - - - 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/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..8aacd840 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"); } @@ -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 c06fd8df..9dc2ed77 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -18,9 +18,41 @@ 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; + input: boolean; + output: boolean; + weDo2SmartHub?: boolean; + raw: { + min: number; + max: number; + }; + pct: { + min: number; + max: number; + }; + si: { + min: number; + max: number; + symbol: string; + }; + values: { + count: number; + type: Consts.ValueType; + }; +} + +export interface IEventData { + raw: number[], + pct: number[], + si: number[], } diff --git a/src/poweredup-browser.ts b/src/poweredup-browser.ts index 779f7acc..79f8b4d3 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; } @@ -193,22 +195,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 57aa53c2..f4b22e1d 100644 --- a/src/poweredup-node.ts +++ b/src/poweredup-node.ts @@ -51,11 +51,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; } @@ -158,17 +160,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; } diff --git a/src/utils.ts b/src/utils.ts index 632214f1..6790e06a 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 (value - 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) => {