Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dynamic modes #97

Draft
wants to merge 15 commits into
base: master
Choose a base branch
from
19 changes: 19 additions & 0 deletions src/consts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
4 changes: 4 additions & 0 deletions src/devices/absolutemotor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
4 changes: 4 additions & 0 deletions src/devices/colordistancesensor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
106 changes: 104 additions & 2 deletions src/devices/device.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { EventEmitter } from "events";

import { IDeviceInterface } from "../interfaces";
import { IDeviceInterface, IMode } from "../interfaces";

import * as Consts from "../consts";

import { normalize } from "../utils";

/**
* @class Device
* @extends EventEmitter
Expand All @@ -13,10 +15,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;
Expand All @@ -37,7 +41,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) {
Expand All @@ -64,6 +68,11 @@ export class Device extends EventEmitter {
this.hub.on("newListener", eventAttachListener);
this.on("newListener", eventAttachListener);
this.hub.on("detach", deviceDetachListener);

if (!this.autoparse) {
this._ready = true;
this.emit('ready');
}
}

/**
Expand Down Expand Up @@ -126,6 +135,22 @@ export class Device extends EventEmitter {
return this._isVirtualPort;
}

/**
* @readonly
* @property {string[]} events List of availlable events (input modes).
*/
public get events () {
aileo marked this conversation as resolved.
Show resolved Hide resolved
return this._modes.filter(mode => mode.input).map(({ name }) => name);
}

/**
* @readonly
* @property {string[]} writeModes List of availlable write (output modes).
*/
public get writeModes () {
aileo marked this conversation as resolved.
Show resolved Hide resolved
return this._modes.filter(mode => mode.output).map(({ name }) => name);
}

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);
Expand All @@ -134,6 +159,36 @@ export class Device extends EventEmitter {
}
}

public autoparseWriteDirect (mode: string, ...data: number[]) {
aileo marked this conversation as resolved.
Show resolved Hide resolved
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);
Expand All @@ -153,6 +208,38 @@ export class Device extends EventEmitter {

public receive (message: Buffer) {
this.notify("receive", { message });

const mode = this._mode;
if (mode === undefined) {
return;
}
const { name, raw, pct, si, values } = this._modes[mode];
const valueSize = Consts.ValueTypeSize[values.type];
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't checked on all devices, but do all device modes only accept one type of value? Even for multiple values?

Are there any instances where a device could have ie. 3x int32, 1x uint8? I don't know if the UART protocol even supports that.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would say they mix everything as in the boost color and distance sensor:

[
  {
    "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": 0
    }
  },
  {
    "name": "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": 0
    }
  },
  "[...]",
  {
    "name": "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": 0
    }
  },
  "[...]"
]

For SPEC 1, which includes values from COLOR and PROX modes, it use 0-255 as range instead of 0-10 to match other values.

Copy link

@tthiery tthiery Jul 1, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As an example, the tilt sensor of the TechnicMediumHub has different data types for different modes in use (Int8, Int32, Int16) but within one mode, the data type is always the same. At least, that is what the device/spec is self-declaring.

Port: 99
    IOTypeId: TechnicMediumHubTiltSensor
    HardwareRevision: 0.0.0.1
    SoftwareRevision: 0.0.0.1
    OutputCapability: True
    InputCapability: True
    LogicalCombinableCapability: True
    LogicalSynchronizableCapability: True
    ModeCombinations: []
    UsedCombinationIndex: 0
    MultiUpdateEnabled: False
    ConfiguredModeDataSetIndex: []
    Mode: 0
      Name: POS
      IsInput: True
      IsOutput: False
      RawMin: -180
      RawMax: 180
      PctMin: -100
      PctMax: 100
      SIMin: -180
      SIMax: 180
      Symbol: DEG
      InputSupportsNull: False
      InputSupportFunctionalMapping20: True
      InputAbsolute: True
      InputRelative: False
      InputDiscrete: False
      OutputSupportsNull: False
      OutputSupportFunctionalMapping20: False
      OutputAbsolute: False
      OutputRelative: False
      OutputDiscrete: False
      NumberOfDatasets: 3
      DatasetType: Int16
      TotalFigures: 3
      Decimals: 0
      DeltaInterval: 0
      NotificationEnabled: False
    Mode: 1
      Name: IMP
      IsInput: True
      IsOutput: False
      RawMin: 0
      RawMax: 100
      PctMin: 0
      PctMax: 100
      SIMin: 0
      SIMax: 100
      Symbol: CNT
      InputSupportsNull: False
      InputSupportFunctionalMapping20: False
      InputAbsolute: False
      InputRelative: True
      InputDiscrete: False
      OutputSupportsNull: False
      OutputSupportFunctionalMapping20: False
      OutputAbsolute: False
      OutputRelative: False
      OutputDiscrete: False
      NumberOfDatasets: 1
      DatasetType: Int32
      TotalFigures: 3
      Decimals: 0
      DeltaInterval: 0
      NotificationEnabled: False
    Mode: 2
      Name: CFG
      IsInput: False
      IsOutput: True
      RawMin: 0
      RawMax: 255
      PctMin: 0
      PctMax: 100
      SIMin: 0
      SIMax: 255
      Symbol:
      InputSupportsNull: False
      InputSupportFunctionalMapping20: False
      InputAbsolute: False
      InputRelative: False
      InputDiscrete: False
      OutputSupportsNull: False
      OutputSupportFunctionalMapping20: False
      OutputAbsolute: True
      OutputRelative: False
      OutputDiscrete: False
      NumberOfDatasets: 2
      DatasetType: SByte
      TotalFigures: 3
      Decimals: 0
      DeltaInterval: 0
      NotificationEnabled: False

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, {
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) {
Expand Down Expand Up @@ -192,4 +279,19 @@ 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() {
aileo marked this conversation as resolved.
Show resolved Hide resolved
return this.hub.autoParse || this._type === Consts.DeviceType.UNKNOWN;
}
}
4 changes: 4 additions & 0 deletions src/devices/duplotrainbasecolorsensor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
4 changes: 4 additions & 0 deletions src/devices/duplotrainbasespeedometer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ export class DuploTrainBaseSpeedometer extends Device {
}

public receive (message: Buffer) {
if (this.hub.autoParse) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are quite repetitive, and error prone when new devices are added (ie. could forget to do this). Could the check be done at hub level, calling either receive or autoParse?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am thinking about another way to do this:

  1. Define all known modes like auto parsed ones and add them in the constructor.
  2. always use Device.receive to parse incoming messages (including mode information) and emit lego named events
  3. make the specific device classes to listen on their lego events to emit proper ones

No more receive method in specific device classes, just event handlers that could use private methods to handle mode calculation and formating.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I totally forgot about the subscription logic in the comment above. It will need the correct mode map.

aileo marked this conversation as resolved.
Show resolved Hide resolved
return super.receive(message);
}

const mode = this._mode;

switch (mode) {
Expand Down
4 changes: 4 additions & 0 deletions src/devices/motionsensor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
4 changes: 4 additions & 0 deletions src/devices/movehubtiltsensor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
4 changes: 4 additions & 0 deletions src/devices/remotecontrolbutton.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
4 changes: 4 additions & 0 deletions src/devices/tachomotor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
4 changes: 4 additions & 0 deletions src/devices/techniccolorsensor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
4 changes: 4 additions & 0 deletions src/devices/technicdistancesensor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
4 changes: 4 additions & 0 deletions src/devices/technicforcesensor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
4 changes: 4 additions & 0 deletions src/devices/technicmediumhubaccelerometersensor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
4 changes: 4 additions & 0 deletions src/devices/technicmediumhubgyrosensor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
4 changes: 4 additions & 0 deletions src/devices/technicmediumhubtiltsensor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
4 changes: 4 additions & 0 deletions src/devices/tiltsensor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Loading