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

Feature/devices: parsing sensor #65

Closed
wants to merge 12 commits into from
33 changes: 33 additions & 0 deletions src/consts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -189,3 +189,36 @@ export enum BLECharacteristic {
WEDO2_NAME_ID = "00001524-1212-efde-1523-785feabcd123", // "1524"
LPF2_ALL = "00001624-1212-efde-1623-785feabcd123"
}

export enum ValueType {
UInt8 = "UInt8",
Int8 = "Int8",
UInt16 = "UInt16",
Int16 = "Int16",
UInt32 = "UInt32",
Int32 = "Int32",
Float = "Float"
}

// tslint:disable-next-line
export const ValueBits = {
[ValueType.UInt8]: 1,
[ValueType.Int8]: 1,
[ValueType.UInt16]: 2,
[ValueType.Int16]: 2,
[ValueType.UInt32]: 3,
[ValueType.Int32]: 3,
[ValueType.Float]: 3
};

export interface IDeviceMode {
input: boolean;
event?: string;
values?: {
type: ValueType,
count: number,
min?: number,
max?: number
};
num: { [type in HubType]?: number };
}
173 changes: 97 additions & 76 deletions src/devices/colordistancesensor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,90 +2,111 @@ import { Device } from "./device";

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

import * as Consts from "../consts";
import { DeviceType, HubType, IDeviceMode, ValueType } from "../consts";

export class ColorDistanceSensor extends Device {

private static modes: { [name: string]: IDeviceMode } = {
COLOR: {
/**
* Emits when a color sensor is activated.
* @event ColorDistanceSensor#color
* @param {string} port
* @param {Color} color
*/
input: true,
event: "color",
values: { type: ValueType.UInt8, count: 1, min: 0, max: 255 },
num: {
[HubType.BOOST_MOVE_HUB]: 0x00,
[HubType.CONTROL_PLUS_HUB]: 0x00,
[HubType.POWERED_UP_HUB]: 0x00,
[HubType.WEDO2_SMART_HUB]: 0x00
}
},
PROX: {
input: true,
event: "distance",
values: { type: ValueType.UInt8, count: 1, min: 0, max: 10 },
num: {
[HubType.BOOST_MOVE_HUB]: 0x01,
[HubType.CONTROL_PLUS_HUB]: 0x01,
[HubType.POWERED_UP_HUB]: 0x01
}
},
COUNT: {
input: true,
event: "count",
values: { type: ValueType.UInt8, count: 1, min: 0 },
num: {
[HubType.BOOST_MOVE_HUB]: 0x02,
[HubType.CONTROL_PLUS_HUB]: 0x02,
[HubType.POWERED_UP_HUB]: 0x02
}
},
REFLT: {
input: true,
event: "reflectivity",
values: { type: ValueType.UInt8, count: 1, min: 0, max: 100 },
num: {
[HubType.BOOST_MOVE_HUB]: 0x03,
[HubType.CONTROL_PLUS_HUB]: 0x03,
[HubType.POWERED_UP_HUB]: 0x03
}
},
AMBI: {
input: true,
event: "luminosity",
values: { type: ValueType.UInt8, count: 1, min: 0, max: 100 },
num: {
[HubType.BOOST_MOVE_HUB]: 0x04,
[HubType.CONTROL_PLUS_HUB]: 0x04,
[HubType.POWERED_UP_HUB]: 0x04
}
},
SOME_OUTPUT_MODE: {
input: false,
num: {}
},
RGB_I: {
input: true,
event: "rgb",
values: { type: ValueType.UInt8, count: 3, min: 0, max: 255 },
num: {
[HubType.BOOST_MOVE_HUB]: 0x06,
[HubType.CONTROL_PLUS_HUB]: 0x06,
[HubType.POWERED_UP_HUB]: 0x06
}
},
OTHER_OUTPUT_MODE: {
input: false,
num: {}
},
SPEC_1: {
input: true,
event: "colorAndDistance",
values: { type: ValueType.UInt8, count: 4, min: 0, max: 255 },
num: {
[HubType.BOOST_MOVE_HUB]: 0x08,
[HubType.CONTROL_PLUS_HUB]: 0x08,
[HubType.POWERED_UP_HUB]: 0x08
}
}
};
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This makes devices objects very declarative.

It handle compatibility matrix, value parsing description and event relation to mode.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We could add here some transformation function that get data array in parameters and return transformed array. It could do some calculation for distance or simply remove some values (color and distance).


constructor (hub: IDeviceInterface, portId: number) {
super(hub, portId, ColorDistanceSensor.ModeMap, Consts.DeviceType.COLOR_DISTANCE_SENSOR);
super(hub, portId, ColorDistanceSensor.modes, DeviceType.COLOR_DISTANCE_SENSOR);
}

public receive (message: Buffer) {
const mode = this._mode;

switch (mode) {
case ColorDistanceSensor.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
* @param {Color} color
*/
this.emit("color", color);
}
break;

case ColorDistanceSensor.Mode.DISTANCE:
if (this.isWeDo2SmartHub) {
break;
}
if (message[4] <= 10) {
const distance = Math.floor(message[4] * 25.4) - 20;

/**
* Emits when a distance sensor is activated.
* @event ColorDistanceSensor#distance
* @param {number} distance Distance, in millimeters.
*/
this.emit("distance", distance);
}
break;

case ColorDistanceSensor.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
* @param {Color} color
* @param {number} distance Distance, in millimeters.
*/
if (message[4] <= 10) {
const color = message[4];
this.emit("colorAndDistance", color, distance);
}
break;
const data = super.receive(message) || [];

if (this._mode === "SPEC_1") {
this.emit("color", data[0]);
this.emit("distance", data[1]);
this.emit("reflectivity", data[3]);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The purpose of this is to defer the generic parsing to the device class then get back the data to handle only edge cases.

However, this is not the same as the current state of the lib in term of events and data but I think that if we decide to break some things, it is now.

}
}

}

export namespace ColorDistanceSensor {

export enum Mode {
COLOR = 0x00,
DISTANCE = 0x01,
COLOR_AND_DISTANCE = 0x08
return data;
}

export const ModeMap: {[event: string]: number} = {
"color": ColorDistanceSensor.Mode.COLOR,
"distance": ColorDistanceSensor.Mode.DISTANCE,
"colorAndDistance": ColorDistanceSensor.Mode.COLOR_AND_DISTANCE
}

}
}
94 changes: 83 additions & 11 deletions src/devices/device.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,26 +8,38 @@ export class Device extends EventEmitter {

public autoSubscribe: boolean = true;

protected _mode: number | undefined;
protected _mode: string | undefined;
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 use mode names instead of number because number is not reliable over different hubs.

protected _busy: boolean = false;
protected _finished: (() => void) | undefined;

private _hub: IDeviceInterface;
private _portId: number;
private _connected: boolean = true;
private _type: Consts.DeviceType;
private _modeMap: {[event: string]: number} = {};
private _modes: {[name: string]: Consts.IDeviceMode} = {};
private _eventMap: {[event: string]: string};

private _isWeDo2SmartHub: boolean;

constructor (hub: IDeviceInterface, portId: number, modeMap: {[event: string]: number} = {}, type: Consts.DeviceType = Consts.DeviceType.UNKNOWN) {
constructor (hub: IDeviceInterface, portId: number, modes: {[name: string]: Consts.IDeviceMode} = {}, 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._eventMap = Object.keys(modes).reduce(
(map: {[event: string]: string}, name) => {
const mode = modes[name];
if (mode.num[hub.type] !== undefined && mode.event) {
map[mode.event] = name;
}
return map;
},
{}
);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Create an event to mode name map to subscribe from event


const detachListener = (device: Device) => {
if (device.portId === this.portId) {
this._connected = false;
Expand All @@ -42,9 +54,12 @@ export class Device extends EventEmitter {
return;
}
if (this.autoSubscribe) {
if (this._modeMap[event] !== undefined) {
this.subscribe(this._modeMap[event]);
if (!this._eventMap[event]) {
// TODO : error handling -> no mode for event
return;
}

this.subscribe(this._eventMap[event]);
}
});
}
Expand Down Expand Up @@ -78,16 +93,73 @@ export class Device extends EventEmitter {
this.hub.send(data, characteristic, callback);
}

public subscribe (mode: number) {
public subscribe (modeName: string) {
this._ensureConnected();
if (mode !== this._mode) {
this._mode = mode;
this.hub.subscribe(this.portId, this.type, mode);
if (modeName !== this._mode) {
this._mode = modeName;

const modeNum = this._modes[modeName].num[this.hub.type];
if (modeNum === undefined) {
// TODO : error handling -> unsupported mode
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Allow to handle hub related unsupported mode error

return;
}
this.hub.subscribe(this.portId, this.type, modeNum);
}
}

public receive (message: Buffer) {
this.emit("receive", message);
if (this._mode === undefined) {
// TODO : error handling -> no mode defined
return;
}

const mode = this._modes[this._mode];

if (!mode.values) {
// TODO : error handling -> no parsing informations
return;
}

const data = [];

for (let index = 0; index <= message.length; index += Consts.ValueBits[mode.values.type]) {
switch (mode.values.type) {
case Consts.ValueType.UInt8: {
data.push(message.readUInt8(index));
Copy link
Owner

Choose a reason for hiding this comment

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

Should the raw value be changed to take into account min/max value at this point?

break;
}
case Consts.ValueType.Int8: {
data.push(message.readInt8(index));
break;
}
case Consts.ValueType.UInt16: {
data.push(message.readUInt16LE(index));
break;
}
case Consts.ValueType.Int16: {
data.push(message.readInt16LE(index));
break;
}
case Consts.ValueType.UInt32: {
data.push(message.readUInt32LE(index));
break;
}
case Consts.ValueType.Int32: {
data.push(message.readInt32LE(index));
break;
}
case Consts.ValueType.Float: {
data.push(message.readFloatLE(index));
break;
}
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Because "We All Love Switches (TM)" :)

Copy link
Owner

@nathankellenicki nathankellenicki Dec 19, 2019

Choose a reason for hiding this comment

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

"This is the way."

}

if (mode.event) {
this.emit(mode.event, ...data);
}
Copy link
Contributor Author

@aileo aileo Dec 18, 2019

Choose a reason for hiding this comment

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

emit related event if any.

This breaks the distance one as it does not apply calculation on the value and also the colorAndDistance which will receive color, distance (0-10), led color and reflectivity instead of only color and distance.


return data;
}

public finish () {
Expand Down
3 changes: 2 additions & 1 deletion src/hubs/lpf2hub.ts
Original file line number Diff line number Diff line change
Expand Up @@ -410,7 +410,8 @@ export class LPF2Hub extends Hub {
const device = this._getDeviceByPortId(portId);

if (device) {
device.receive(message);
// Forward message to device (without size, hub and port)
device.receive(message.slice(3));
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Device does not need to get the full message, data is sufficient.
I did not test and I wonder if it should be 4 instead of 3.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Also, it seems the offset is not the same for WEDO2, it would remove specifique parsing from devices.

}

// if (data[3] === this._voltagePort) {
Expand Down