diff --git a/lib/test/utils.js b/lib/test/utils.js index 1d333880..c384af27 100644 --- a/lib/test/utils.js +++ b/lib/test/utils.js @@ -2,7 +2,12 @@ const fs = require("fs"); const chai = require("chai"); const request = require("sync-request"); const { validate } = require("jsonschema"); -require('dotenv').config() + +try { + require("dotenv").config(); +} catch (ex) { + console.log("No dotenv file"); +} const { assert } = chai; const bits = require("bits"); diff --git a/types/laiier/severnWLD/default.schema.json b/types/laiier/severnWLD/default.schema.json new file mode 100644 index 00000000..198ccd66 --- /dev/null +++ b/types/laiier/severnWLD/default.schema.json @@ -0,0 +1,151 @@ +{ + "$id": "https://akenza.io/laiier/severn/default.schema.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "processingType": "uplink_decoder", + "topic": "default", + "title": "Default", + "properties": { + "messageType": { + "title": "Message type", + "type": "string", + "description": "Message type", + "enum": ["REGULAR_MESSAGE", "EMERGENCY_MESSAGE", "SELF_TEST_MESSAGE"] + }, + "leakElectrode1": { + "title": "Leak electrode 1", + "description": "Leak electrode. wet = true, dry = false", + "type": "boolean" + }, + "leakElectrode2": { + "title": "Leak electrode 2", + "description": "Leak electrode. wet = true, dry = false", + "type": "boolean" + }, + "leakElectrode3": { + "title": "Leak electrode 3", + "description": "Leak electrode. wet = true, dry = false", + "type": "boolean" + }, + "leakElectrode4": { + "title": "Leak electrode 4", + "description": "Leak electrode. wet = true, dry = false", + "type": "boolean" + }, + "leakElectrode5": { + "title": "Leak electrode 5", + "description": "Leak electrode. wet = true, dry = false", + "type": "boolean" + }, + "leakElectrode6": { + "title": "Leak electrode 6", + "description": "Leak electrode. wet = true, dry = false", + "type": "boolean" + }, + "leakElectrode7": { + "title": "Leak electrode 7", + "description": "Leak electrode. wet = true, dry = false", + "type": "boolean" + }, + "leakElectrode8": { + "title": "Leak electrode 8", + "description": "Leak electrode. wet = true, dry = false", + "type": "boolean" + }, + "leakElectrode9": { + "title": "Leak electrode 9", + "description": "Leak electrode. wet = true, dry = false", + "type": "boolean" + }, + "leakElectrode10": { + "title": "Leak electrode 10", + "description": "Leak electrode. wet = true, dry = false", + "type": "boolean" + }, + "leakElectrode11": { + "title": "Leak electrode 11", + "description": "Leak electrode. wet = true, dry = false", + "type": "boolean" + }, + "leakElectrode12": { + "title": "Leak electrode 12", + "description": "Leak electrode. wet = true, dry = false", + "type": "boolean" + }, + "selfTestFailed": { + "title": "Self test failed", + "description": "Self test failed", + "type": "boolean" + }, + "criticalWetFlag": { + "title": "Critical wet flag", + "description": "Critical wet flag", + "type": "boolean" + }, + "accX": { + "title": "Accelerometer X Axis", + "unit": "mg", + "description": "Acceleration on the X-Axis is mg", + "type": "integer", + "minimum": -32767, + "maximum": 32767 + }, + "accY": { + "title": "Accelerometer Y Axis", + "unit": "mg", + "description": "Acceleration on the Y-Axis is mg", + "type": "integer", + "minimum": -32767, + "maximum": 32767 + }, + "accZ": { + "title": "Accelerometer Z Axis", + "unit": "mg", + "description": "Acceleration on the Z-Axis is mg", + "type": "integer", + "minimum": -32767, + "maximum": 32767 + }, + "temperature": { + "title": "Temperature", + "unit": "°C", + "type": "number", + "description": "The temperature in °C.", + "minimum": -3276.5, + "maximum": 3276.5 + }, + "wetnessThreshold": { + "title": "Wetness threshold", + "type": "number", + "description": "Wetness threshold" + }, + "interval": { + "title": "Message interval", + "type": "number", + "description": "Message interval" + } + }, + "required": [ + "messageType", + "selfTestFailed", + "criticalWetFlag", + "leakElectrode1", + "leakElectrode2", + "leakElectrode3", + "leakElectrode4", + "leakElectrode5", + "leakElectrode6", + "leakElectrode7", + "leakElectrode8", + "leakElectrode9", + "leakElectrode10", + "leakElectrode11", + "leakElectrode12", + "accX", + "accY", + "accZ", + "temperature", + "wetnessThreshold", + "interval" + ] +} diff --git a/types/laiier/severnWLD/laiier.jpg b/types/laiier/severnWLD/laiier.jpg new file mode 100644 index 00000000..edf0c882 Binary files /dev/null and b/types/laiier/severnWLD/laiier.jpg differ diff --git a/types/laiier/severnWLD/meta.json b/types/laiier/severnWLD/meta.json new file mode 100644 index 00000000..f236e48f --- /dev/null +++ b/types/laiier/severnWLD/meta.json @@ -0,0 +1,14 @@ +{ + "name": "Severn WLD", + "version": "1.0.0", + "manufacturer": "Laiier", + "url": "https://www.laiier.io/severn-wld", + "description": "Sever WLD is a water leak detector that installs like tape. You can use it to monitor for water leaks where you couldn't monitor before.", + "author": "Akenza AG", + "firmwareVersion": "V1.0.0", + "loraDeviceClass": "A", + "availableSensors": ["Leak"], + "outputTopics": ["default", "start_up"], + "encoding": "HEX", + "connectivity": "LORA" +} diff --git a/types/laiier/severnWLD/start_up.schema.json b/types/laiier/severnWLD/start_up.schema.json new file mode 100644 index 00000000..c11285b8 --- /dev/null +++ b/types/laiier/severnWLD/start_up.schema.json @@ -0,0 +1,23 @@ +{ + "$id": "https://akenza.io/laiier/severn/start_up.schema.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "processingType": "uplink_decoder", + "topic": "start_up", + "title": "Start up", + "properties": { + "serialNumber": { + "title": "Serial number", + "type": "number", + "description": "Serial number", + "hideFromKpis": true + }, + "firmwareVersion": { + "title": "Firmware version", + "type": "string", + "description": "Firmware version", + "hideFromKpis": true + } + }, + "required": ["serialNumber", "firmwareVersion"] +} diff --git a/types/laiier/severnWLD/uplink.js b/types/laiier/severnWLD/uplink.js new file mode 100644 index 00000000..01392921 --- /dev/null +++ b/types/laiier/severnWLD/uplink.js @@ -0,0 +1,67 @@ +function consume(event) { + const payload = event.data.payloadHex; + const bits = Bits.hexToBits(payload); + const { port } = event.data; + const data = {}; + let topic = "default"; + + switch (port) { + case 1: + case 99: + case 102: + switch (port) { + case 1: + data.messageType = "REGULAR_MESSAGE"; + break; + case 99: + data.messageType = "EMERGENCY_MESSAGE"; + break; + case 102: + data.messageType = "SELF_TEST_MESSAGE"; + break; + default: + break; + } + // 00 00 + data.leakElectrode12 = !!Bits.bitsToUnsigned(bits.substr(0, 1)); + data.leakElectrode11 = !!Bits.bitsToUnsigned(bits.substr(1, 1)); + data.leakElectrode10 = !!Bits.bitsToUnsigned(bits.substr(2, 1)); + data.leakElectrode9 = !!Bits.bitsToUnsigned(bits.substr(3, 1)); + + data.selfTestFailed = !!Bits.bitsToUnsigned(bits.substr(6, 1)); + data.criticalWetFlag = !!Bits.bitsToUnsigned(bits.substr(7, 1)); + + data.leakElectrode8 = !!Bits.bitsToUnsigned(bits.substr(8, 1)); + data.leakElectrode7 = !!Bits.bitsToUnsigned(bits.substr(9, 1)); + data.leakElectrode6 = !!Bits.bitsToUnsigned(bits.substr(10, 1)); + data.leakElectrode5 = !!Bits.bitsToUnsigned(bits.substr(11, 1)); + data.leakElectrode4 = !!Bits.bitsToUnsigned(bits.substr(12, 1)); + data.leakElectrode3 = !!Bits.bitsToUnsigned(bits.substr(13, 1)); + data.leakElectrode2 = !!Bits.bitsToUnsigned(bits.substr(14, 1)); + data.leakElectrode1 = !!Bits.bitsToUnsigned(bits.substr(15, 1)); + + // fd 02 3f + data.accX = Bits.bitsToUnsigned(bits.substr(16, 8)); // 1/63 g + data.accY = Bits.bitsToUnsigned(bits.substr(24, 8)); // 1/63 g + data.accZ = Bits.bitsToUnsigned(bits.substr(32, 8)); // 1/63 g + + data.temperature = Bits.bitsToSigned(bits.substr(40, 8)); // 18 + data.wetnessThreshold = Bits.bitsToUnsigned(bits.substr(48, 8)); // 03 + data.interval = Bits.bitsToUnsigned(bits.substr(56, 16)); // 0168 + + break; + case 100: + topic = "start_up"; + data.serialNumber = Bits.bitsToUnsigned(bits.substr(0, 64)); + data.firmwareVersion = `${Bits.bitsToUnsigned( + bits.substr(64, 8), + )}.${Bits.bitsToUnsigned(bits.substr(72, 8))}.${Bits.bitsToUnsigned( + bits.substr(80, 8), + )}`; + break; + default: + break; + } + + emit("sample", { data, topic }); +} diff --git a/types/laiier/severnWLD/uplink.spec.js b/types/laiier/severnWLD/uplink.spec.js new file mode 100644 index 00000000..afcf6057 --- /dev/null +++ b/types/laiier/severnWLD/uplink.spec.js @@ -0,0 +1,100 @@ +const chai = require("chai"); + +const rewire = require("rewire"); +const utils = require("test-utils"); + +const { assert } = chai; + +describe("Laiier severnWLD Uplink", () => { + let defaultSchema = null; + let consume = null; + before((done) => { + const script = rewire("./uplink.js"); + consume = utils.init(script); + utils + .loadSchema(`${__dirname}/default.schema.json`) + .then((parsedSchema) => { + defaultSchema = parsedSchema; + done(); + }); + }); + + let startUpSchema = null; + before((done) => { + utils + .loadSchema(`${__dirname}/start_up.schema.json`) + .then((parsedSchema) => { + startUpSchema = parsedSchema; + done(); + }); + }); + + describe("consume()", () => { + it("should decode Laiier severnWLD startup payload", () => { + const data = { + data: { + port: 100, + payloadHex: "2319010600000272010000", + }, + }; + + utils.expectEmits((type, value) => { + assert.equal(type, "sample"); + assert.isNotNull(value); + assert.typeOf(value.data, "object"); + + assert.equal(value.topic, "start_up"); + + assert.equal(value.data.firmwareVersion, "1.0.0"); + assert.equal(value.data.serialNumber, 2529053791026676000); + + utils.validateSchema(value.data, startUpSchema, { + throwError: true, + }); + }); + + consume(data); + }); + + it("should decode Laiier severnWLD self test payload", () => { + const data = { + data: { + port: 102, + payloadHex: "0000fd023f18030168", + }, + }; + + utils.expectEmits((type, value) => { + assert.equal(type, "sample"); + assert.isNotNull(value); + assert.typeOf(value.data, "object"); + + assert.equal(value.topic, "default"); + assert.equal(value.data.criticalWetFlag, false); + assert.equal(value.data.interval, 360); + assert.equal(value.data.leakElectrode1, false); + assert.equal(value.data.leakElectrode2, false); + assert.equal(value.data.leakElectrode3, false); + assert.equal(value.data.leakElectrode4, false); + assert.equal(value.data.leakElectrode5, false); + assert.equal(value.data.leakElectrode6, false); + assert.equal(value.data.leakElectrode7, false); + assert.equal(value.data.leakElectrode8, false); + assert.equal(value.data.leakElectrode9, false); + assert.equal(value.data.leakElectrode10, false); + assert.equal(value.data.leakElectrode11, false); + assert.equal(value.data.leakElectrode12, false); + assert.equal(value.data.messageType, "SELF_TEST_MESSAGE"); + assert.equal(value.data.selfTestFailed, false); + assert.equal(value.data.temperature, 24); + assert.equal(value.data.wetnessThreshold, 3); + + utils.validateSchema(value.data, defaultSchema, { + throwError: true, + }); + }); + + consume(data); + }); + }); +});