Skip to content

Commit

Permalink
Added more features to decode
Browse files Browse the repository at this point in the history
Decode can now do lookups for sensor types, reported during node
presentation, and use that to enrich the emitted mysensors node message

Also added more tests on controller class
  • Loading branch information
Thomas Mørch committed Jul 13, 2018
1 parent 987dff7 commit 513ee20
Show file tree
Hide file tree
Showing 18 changed files with 740 additions and 57 deletions.
511 changes: 498 additions & 13 deletions package-lock.json

Large diffs are not rendered by default.

10 changes: 6 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"author": {
"name": "Thomas Bowman Mørch"
},
"version": "3.1.2",
"version": "3.2.0",
"scripts": {
"build": "mkdir -p dist/nodes/ && cp -a src/nodes/*.html dist/nodes/ && cp -R src/migrations dist && tsc ",
"pretest": "tsc",
Expand Down Expand Up @@ -44,16 +44,18 @@
},
"dependencies": {
"@types/moment-timezone": "^0.5.6",
"moment-timezone": "^0.5.17",
"@types/sinon": "^5.0.1",
"moment-timezone": "^0.5.20",
"sqlite": "^2.9.2"
},
"devDependencies": {
"@types/chai": "^4.1.3",
"@types/mocha": "^5.2.0",
"@types/chai": "^4.1.4",
"@types/mocha": "^5.2.3",
"@types/node": "^9.6.18",
"@types/node-red": "^0.17.3",
"chai": "^4.1.2",
"mocha": "^5.2.0",
"sinon": "^6.0.0",
"tslint": "^5.10.0",
"typescript": "^2.8.3"
}
Expand Down
45 changes: 31 additions & 14 deletions src/lib/database-sqlite.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import * as path from 'path';
import { open } from 'sqlite';
import { IDatabase, INodeData } from './database.interface';
import { IDatabase, INodeData, ISensorData } from './database.interface';
import { NullCheck } from './nullcheck';
export class Database implements IDatabase {
export class DatabaseSqlite implements IDatabase {
private dbPromise: any;

/**
Expand All @@ -11,27 +11,28 @@ export class Database implements IDatabase {
*/
constructor(private file: string) {
if (NullCheck.isUndefinedNullOrEmpty(this.file)) {
throw new Error('No dbname set');
// throw new Error('No dbname set');
} else {
this.dbPromise = Promise.resolve()
.then(() => open(this.file))
.then((db) => db.migrate({migrationsPath: path.dirname(__dirname) + '/migrations'}));
this.checkDb();
}
this.dbPromise = Promise.resolve()
.then(() => open(this.file))
.then((db) => db.migrate({migrationsPath: path.dirname(__dirname) + '/migrations'}));
this.checkDb();
}

public async nodeHeard(nodeId: number): Promise<void> {
const db = await this.dbPromise;
await db.run(`update node set lastHeard=CURRENT_TIMESTAMP, used=1 where id=${nodeId}`);
await db.run(`update node set lastHeard=CURRENT_TIMESTAMP, used=1 where nodeId=${nodeId}`);
}

public async sketchName(nodeId: number, name: string): Promise<void> {
const db = await this.dbPromise;
await db.run(`update node set sketchName='${name}' where id=${nodeId}`);
await db.run(`update node set sketchName='${name}' where nodeId=${nodeId}`);
}

public async sketchVersion(nodeId: number, version: string): Promise<void> {
const db = await this.dbPromise;
await db.run( `update node set sketchVersion='${version}', lastRestart=CURRENT_TIMESTAMP where id=${nodeId}`);
await db.run( `update node set sketchVersion='${version}', lastRestart=CURRENT_TIMESTAMP where nodeId=${nodeId}`);
}

public async getNodeList(): Promise<INodeData[]> {
Expand All @@ -42,7 +43,7 @@ export class Database implements IDatabase {

public async getFreeNodeId(): Promise<number> {
const db = await this.dbPromise;
const res = await db.get('select min(id) id from node where used=0');
const res = await db.get('select min(nodeId) id from node where used=0');
return res.id;
}

Expand All @@ -53,16 +54,32 @@ export class Database implements IDatabase {

public async setParent(node: string, last: string): Promise<void> {
const db = await this.dbPromise;
await db.run(`update node set parentId=${last} where id=${node}`);
await db.run(`update node set parentId=${last} where nodeId=${node}`);
}

public async child(nodeId: number, childId: number, type: number, description: string): Promise<void> {
const db = await this.dbPromise;
db.run(`insert or replace into child (childId, nodeId, sType, description) values(${childId}, ${nodeId}, ${type}, '${description}')`);
}

public async childHeard(nodeId: number, childId: number): Promise<void> {
const db = await this.dbPromise;
await db.run(`update child set lastHeard=CURRENT_TIMESTAMP where childId=${childId} and nodeId=${nodeId}`);
}

public async getChild(nodeId: number, childId: number): Promise<ISensorData> {
const db = await this.dbPromise;
const result = (await db.get(`select * from child where nodeId=${nodeId} and childId=${childId}`)) as ISensorData;
return result;
}

private async checkDb(): Promise<void> {
const db = await this.dbPromise;

const x = await db.get('select count(id) cnt from node');
const x = await db.get('select count(nodeId) cnt from node');
if (x.cnt === 0) {
for (let i = 1; i <= 254; i++) {
db.run(`insert into node (id, used) values (${i}, 0)`);
db.run(`insert into node (nodeId, used) values (${i}, 0)`);
}
}
}
Expand Down
7 changes: 6 additions & 1 deletion src/lib/database.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@ export interface INodeData {
}

export interface ISensorData {
id: number;
nodeId: number;
childId: number;
description: string;
sType: mysensor_sensor;
lastHeard: Date;
}

export interface IDatabase {
Expand All @@ -26,4 +28,7 @@ export interface IDatabase {
getFreeNodeId(): Promise<number>;
close(): Promise<void>;
setParent(node: string, last: string): Promise<void>;
child(nodeId: number, childId: number, type: number, description: string): Promise<void>;
childHeard(nodeId: number, childId: number): Promise<void>;
getChild(nodeId: number, childId: number): Promise<ISensorData>;
}
6 changes: 3 additions & 3 deletions src/lib/decoder/auto-decode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ import { NullCheck } from '../nullcheck';
import { MysensorsMqtt } from './mysensors-mqtt';
import { MysensorsSerial } from './mysensors-serial';

export function AutoDecode(msg: IMysensorsMsg): IMysensorsMsg {
export async function AutoDecode(msg: IMysensorsMsg): Promise<IMysensorsMsg> {
if (NullCheck.isUndefinedOrNull(msg.nodeId)) {
let msgTmp: IMysensorsMsg | undefined;
if (NullCheck.isUndefinedNullOrEmpty(msg.topic)) {
msgTmp = new MysensorsSerial().decode(msg);
msgTmp = await new MysensorsSerial().decode(msg);
} else {
msgTmp = new MysensorsMqtt().decode(msg);
msgTmp = await new MysensorsMqtt().decode(msg);
}
if (NullCheck.isDefinedOrNonNull(msgTmp)) {
msg = msgTmp;
Expand Down
2 changes: 1 addition & 1 deletion src/lib/decoder/decoder.interface.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { IMysensorsMsg, INodeMessage } from '../mysensors-msg';

export interface IDecoder {
decode(msg: INodeMessage): IMysensorsMsg| undefined;
decode(msg: INodeMessage): Promise<IMysensorsMsg| undefined>;
encode(msg: IMysensorsMsg): INodeMessage| undefined;
}
18 changes: 17 additions & 1 deletion src/lib/decoder/mysensors-decoder.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { IDatabase } from 'node-red-contrib-mysensors/src/lib/database.interface';
import { IMysensorsMsg } from '../mysensors-msg';
import {
mysensor_command,
Expand All @@ -9,7 +10,13 @@ import {
import { NullCheck } from '../nullcheck';

export abstract class MysensorsDecoder {
protected enrich(msg: IMysensorsMsg): IMysensorsMsg {
protected enrichWithDb: boolean;

constructor(enrich?: boolean, private database?: IDatabase) {
this.enrichWithDb = enrich || false;
}

protected async enrich(msg: IMysensorsMsg): Promise<IMysensorsMsg> {
if (NullCheck.isDefinedOrNonNull(msg.messageType)) {
msg.messageTypeStr = mysensor_command[msg.messageType];
}
Expand All @@ -29,6 +36,15 @@ export abstract class MysensorsDecoder {
msg.subTypeStr = mysensor_stream[msg.subType];
}
}
if (this.enrichWithDb &&
NullCheck.isDefinedOrNonNull(msg.nodeId) &&
NullCheck.isDefinedOrNonNull(msg.childSensorId) &&
NullCheck.isDefinedOrNonNull(this.database)) {
const res = await this.database.getChild(msg.nodeId, msg.childSensorId);
if (NullCheck.isDefinedOrNonNull(res)) {
msg.sensorTypeStr = mysensor_sensor[res.sType];
}
}
return msg;
}
}
4 changes: 2 additions & 2 deletions src/lib/decoder/mysensors-mqtt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { MysensorsDecoder } from './mysensors-decoder';

export class MysensorsMqtt extends MysensorsDecoder implements IDecoder {

public decode(msg: INodeMessage): IMysensorsMsg| undefined {
public async decode(msg: INodeMessage): Promise<IMysensorsMsg| undefined> {
if (NullCheck.isDefinedNonNullAndNotEmpty(msg.topic)) {
const msgOut = msg as IMysensorsMsg;
const split = msg.topic.toString().split('/');
Expand All @@ -17,7 +17,7 @@ export class MysensorsMqtt extends MysensorsDecoder implements IDecoder {
msgOut.ack = (split[split.length - 2] === '1') ? 1 : 0;
msgOut.subType = parseInt( split[split.length - 1], 10 );
msgOut.origin = MsgOrigin.mqtt;
return this.enrich(msgOut);
return await this.enrich(msgOut);
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/lib/decoder/mysensors-serial.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { IDecoder } from './decoder.interface';
import { MysensorsDecoder } from './mysensors-decoder';

export class MysensorsSerial extends MysensorsDecoder implements IDecoder {
public decode(msg: INodeMessage): IMysensorsMsg| undefined {
public async decode(msg: INodeMessage): Promise<IMysensorsMsg| undefined> {
let message = msg.payload.toString();
message = message.replace(/(\r\n|\n|\r)/gm, '');
const tokens = message.split(';');
Expand All @@ -17,7 +17,7 @@ export class MysensorsSerial extends MysensorsDecoder implements IDecoder {
msgOut.subType = parseInt(tokens[4], 10);
msgOut.payload = tokens[5];
msgOut.origin = MsgOrigin.serial;
return this.enrich(msgOut);
return await this.enrich(msgOut);
}
}

Expand Down
62 changes: 62 additions & 0 deletions src/lib/mysensors-controller.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { expect } from 'chai';
import { IDatabase } from 'node-red-contrib-mysensors/src/lib/database.interface';
import * as sinon from 'sinon';
import { DatabaseSqlite } from './database-sqlite';
import { MysensorsController } from './mysensors-controller';
import { IMysensorsMsg } from './mysensors-msg';
import { mysensor_command, mysensor_internal } from './mysensors-types';

describe('Controller test', () => {
let db: IDatabase;
let controller: MysensorsController;
sinon.stub(DatabaseSqlite.prototype, 'getFreeNodeId').callsFake(() => '777');
db = new DatabaseSqlite('dummy');
controller = new MysensorsController(db, true, true, 'CET', 'M', 'mys-out');

it('MQTT ID Request', async () => {
const input: IMysensorsMsg = {
payload: '',
topic: 'mys-in/255/255/3/0/3',
};
const expected: IMysensorsMsg = {
payload: '777',
subType: mysensor_internal.I_ID_RESPONSE,
topicRoot: 'mys-out',
};
expect(await controller.messageHandler(input))
.to.include(expected);
});

it('Serial config request', async () => {
const expected: IMysensorsMsg = {
payload: '255;255;3;0;6;M',
};
const request: IMysensorsMsg = {payload: '255;255;3;0;6;0'};

expect(await controller.messageHandler(request)).to.include(expected);
});

it('Decoded time request', async () => {
const request: IMysensorsMsg = {
ack: 0,
childSensorId: 255,
messageType: mysensor_command.C_INTERNAL,
nodeId: 10,
payload: '',
subType: mysensor_internal.I_TIME,
};

const expected: IMysensorsMsg = {
payload: '',
subType: mysensor_internal.I_TIME,
};

expect(await controller.messageHandler(request)).to.include.keys(expected);
});

it('updates database uppon reception of package', async () => {
const spy = sinon.spy(DatabaseSqlite.prototype, 'nodeHeard');
await controller.messageHandler({payload: '10;255;3;0;6;0'});
expect(spy.called).to.eq(true);
});
});
12 changes: 11 additions & 1 deletion src/lib/mysensors-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,19 @@ export class MysensorsController {
) { }

public async messageHandler(msg: IMysensorsMsg): Promise<IMysensorsMsg | undefined> {
msg = AutoDecode(msg);
msg = await AutoDecode(msg);
if (NullCheck.isDefinedOrNonNull(msg.nodeId)) {
await this.database.nodeHeard(msg.nodeId);
if (NullCheck.isDefinedOrNonNull(msg.childSensorId)) {
await this.database.childHeard(msg.nodeId, msg.childSensorId);
}
}

if (msg.messageType === mysensor_command.C_PRESENTATION &&
NullCheck.isDefinedOrNonNull(msg.childSensorId) &&
NullCheck.isDefinedOrNonNull(msg.nodeId) &&
NullCheck.isDefinedOrNonNull(msg.subType)) {
await this.database.child(msg.nodeId, msg.childSensorId, msg.subType, msg.payload);
}

if (msg.messageType === mysensor_command.C_INTERNAL) {
Expand Down
3 changes: 2 additions & 1 deletion src/lib/mysensors-msg.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export interface INodeMessage {
topic?: string;
}

export interface IMysensorsMsg extends INodeMessage {
export interface IMysensorsMsg extends INodeMessage {
topicRoot?: string;
nodeId?: number;
childSensorId?: number;
Expand All @@ -18,6 +18,7 @@ export interface IMysensorsMsg extends INodeMessage {
ack?: 0|1;
subType?: mysensor_data| mysensor_internal| mysensor_sensor| mysensor_stream;
subTypeStr?: string;
sensorTypeStr?: string;
origin?: MsgOrigin;
}

Expand Down
34 changes: 34 additions & 0 deletions src/migrations/003-update-columns.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
-- UP

ALTER TABLE node RENAME TO tmp_node;

CREATE TABLE IF NOT EXISTS node (
nodeId integer PRIMARY KEY AUTOINCREMENT,
label varchar,
sketchName varchar,
sketchVersion varchar,
lastHeard timestamp,
parentId integer,
lastRestart timestamp,
used boolean
);

INSERT INTO node(nodeId, label, sketchName, sketchVersion, lastHeard, parentId, lastRestart, used)
SELECT id, label, sketchName, sketchVersion, lastHeard, parentId, lastRestart, used
FROM tmp_node;

drop table tmp_node;

drop table child;

CREATE TABLE IF NOT EXISTS child (
nodeId integer,
childId integer,
sType integer,
description varchar,
lastHeard timestamp,
PRIMARY KEY(nodeId, childId),
FOREIGN KEY(nodeId) REFERENCES node(nodeId));


-- DOWN
4 changes: 4 additions & 0 deletions src/nodes/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,13 @@ export interface IEncodeProperties extends NodeProperties {
/* Decode */
export interface IDecodeProperties extends NodeProperties {
mqtt: boolean;
enrich: boolean;
database?: NodeId;
}
export interface IDecodeEncodeConf extends Node {
decoder: IDecoder;
database?: IDbConfigNode;
enrich: boolean;
}

/* DB */
Expand Down
Loading

0 comments on commit 513ee20

Please sign in to comment.