Skip to content

Commit

Permalink
Merge pull request #58 from tago-io/feat/connector-dragino-cs01lb-3
Browse files Browse the repository at this point in the history
Created the dragino cs01lb decoder and tests
  • Loading branch information
FabianoEger authored Jul 12, 2024
2 parents 657f5e3 + dbc2724 commit fa56524
Show file tree
Hide file tree
Showing 6 changed files with 381 additions and 0 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
13 changes: 13 additions & 0 deletions decoders/connector/dragino/cs01lb/connector.jsonc
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"$schema": "../../../../schema/connector.json",
"name": "Dragino CS01-LB",
"images": {
"logo": "./assets/logo.png"
},
"versions": {
"v1.0.0": {
"src": "./v1.0.0/payload.ts",
"manifest": "./v1.0.0/payload-config.jsonc"
}
}
}
1 change: 1 addition & 0 deletions decoders/connector/dragino/cs01lb/description.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
4 Channel Current Sensor Converter over LoRaWAN™
25 changes: 25 additions & 0 deletions decoders/connector/dragino/cs01lb/v1.0.0/payload-config.jsonc
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"$schema": "../../../../../schema/connector_details.json",
"description": "../description.md",
"install_text": "The Dragino CS01-LB is a 4 Channels Current Sensor Converter. It can convert the reading from current sensors and upload to IoT server via LoRaWAN Network.\n* LoRaWAN 1.0.3 Class A.\n* Bands:CN470/EU433/KR920/US915/EU868/AS923/AU915/IN865.\n* Ultra-low power consumption.\n* Supports maximum 4 current sensors.\n* Support various current sensor Ratio: 50A, 100A etc..\n* Monitor the machine running status.\n* Analyze power consumption trends.\n* Current Alarm.\n* Uplink on periodically.\n* Downlink to change configure.\n* Support BLE and LoRaWAN remote configure.\n* Support wireless OTA update firmware.\n* 8500mAh Li/SOCl2 Battery.",
"install_end_text": "",
"device_annotation": "",
"device_parameters": [],
"networks": [
"../../../../network/lorawan-actility/v1.0.0/payload.js",
"../../../../network/lorawan-chirpstack/v1.0.0/payload.js",
"../../../../network/lorawan-citykinect/v1.0.0/payload.js",
"../../../../network/lorawan-everynet/v1.0.0/payload.js",
"../../../../network/lorawan-helium/v1.0.0/payload.js",
"../../../../network/lorawan-kerlink/v1.0.0/payload.js",
"../../../../network/lorawan-tektelic/v1.0.0/payload.js",
"../../../../network/lorawan-swisscom/v1.0.0/payload.js",
"../../../../network/lorawan-senra/v1.0.0/payload.js",
"../../../../network/lorawan-senet/v1.0.0/payload.js",
"../../../../network/lorawan-orbiwise/v1.0.0/payload.js",
"../../../../network/lorawan-machineq/v1.0.0/payload.js",
"../../../../network/lorawan-loriot/v1.0.0/payload.js",
"../../../../network/lorawan-ttittn-v3/v1.0.0/payload.js",
"../../../../network/lorawan-brdot/v1.0.0/payload.js"
]
}
133 changes: 133 additions & 0 deletions decoders/connector/dragino/cs01lb/v1.0.0/payload.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
/* eslint-disable unicorn/numeric-separators-style */
import { describe, expect, test } from "vitest";

import { decoderRun } from "../../../../../src/functions/decoder-run";

const file_path =
"decoders/connector/dragino/cs01lb/v1.0.0/payload.ts" as const;

function preparePayload(payloadHex: Buffer, port: number) {
let payload = [
{ variable: "payload", value: payloadHex },
{ variable: "fport", value: port },
];
payload = decoderRun(file_path, { payload });

const DATALOG = payload.find((item) => item.variable === "datalog");
const EXTI_Trigger = payload.find((item) => item.variable === "exti_trigger");
const BAT = payload.find((item) => item.variable === "bat");
const KEEP_STATUS = payload.find((item) => item.variable === "keep_status");
const Current1_A = payload.find((item) => item.variable === "current1_a");
const Current2_A = payload.find((item) => item.variable === "current2_a");
const Current3_A = payload.find((item) => item.variable === "current3_a");
const Current4_A = payload.find((item) => item.variable === "current4_a");
const Cur1L_status = payload.find((item) => item.variable === "cur1l_status");
const Cur1H_status = payload.find((item) => item.variable === "cur1h_status");
const Cur2L_status = payload.find((item) => item.variable === "cur2l_status");
const Cur2H_status = payload.find((item) => item.variable === "cur2h_status");
const Cur3L_status = payload.find((item) => item.variable === "cur3l_status");
const Cur3H_status = payload.find((item) => item.variable === "cur3h_status");
const Cur4L_status = payload.find((item) => item.variable === "cur4l_status");
const Cur4H_status = payload.find((item) => item.variable === "cur4h_status");
const SENSOR_MODEL = payload.find((item) => item.variable === "sensor_model");
const FIRMWARE_VERSION = payload.find(
(item) => item.variable === "firmware_version"
);
const FREQUENCY_BAND = payload.find(
(item) => item.variable === "frequency_band"
);
const SUB_BAND = payload.find((item) => item.variable === "sub_band");

const parse_error = payload.find((item) => item.variable === "parse_error");
return {
payload,
DATALOG,
EXTI_Trigger,
BAT,
KEEP_STATUS,
Current1_A,
Current2_A,
Current3_A,
Current4_A,
Cur1L_status,
Cur1H_status,
Cur2L_status,
Cur2H_status,
Cur3L_status,
Cur3H_status,
Cur4L_status,
Cur4H_status,
SENSOR_MODEL,
FIRMWARE_VERSION,
FREQUENCY_BAND,
SUB_BAND,
parse_error,
};
}

describe("Port 2 unit tests", () => {
const payloadHex = "220020002000020001";
const port = 2;
const result = preparePayload(payloadHex, port);

test("Port 2 variable values", () => {
expect(result.EXTI_Trigger?.value).toBe("FALSE");
expect(result.BAT?.value).toBe(0.002);
expect(result.KEEP_STATUS?.value).toBe("LOW");
expect(result.Current1_A?.value).toBe(81.92);
expect(result.Current2_A?.value).toBe(81.92);
expect(result.Current3_A?.value).toBe(5.12);
expect(result.Current4_A?.value).toBe(2.56);
expect(result.Cur1L_status?.value).toBe("False");
expect(result.Cur1H_status?.value).toBe("False");
expect(result.Cur2L_status?.value).toBe("False");
expect(result.Cur2H_status?.value).toBe("False");
expect(result.Cur3L_status?.value).toBe("False");
expect(result.Cur3H_status?.value).toBe("False");
expect(result.Cur4L_status?.value).toBe("False");
expect(result.Cur4H_status?.value).toBe("False");
});
});

// removed because of time constraints
// describe("Port 3 unit tests", () => {
// const payloadHex = "";
// const port = 3;
// const result = preparePayload(payloadHex, port);

// test("Port 3 variable values", () => {
// expect(result.datalog?.value).toBe("");
// });
// });

describe("Port 5 unit tests", () => {
const payloadHex = "33010001000B45";
const port = 5;
const result = preparePayload(payloadHex, port);

test("Port 5 variable values", () => {
expect(result.SENSOR_MODEL?.value).toBe("CS01-LB");
expect(result.FIRMWARE_VERSION?.value).toBe("1.0.0");
expect(result.FREQUENCY_BAND?.value).toBe("EU868");
expect(result.SUB_BAND?.value).toBe(0);
expect(result.BAT?.value).toBe(2.885);
});
});

describe("Shall not be parsed", () => {
let payload = [
{ variable: "shallnotpass", value: "04096113950292" },
{ variable: "fport", value: 9 },
];
payload = decoderRun(file_path, { payload });
test("Output Result", () => {
expect(Array.isArray(payload)).toBe(true);
});

test("Not parsed Result", () => {
expect(payload).toEqual([
{ variable: "shallnotpass", value: "04096113950292" },
{ variable: "fport", value: 9 },
]);
});
});
209 changes: 209 additions & 0 deletions decoders/connector/dragino/cs01lb/v1.0.0/payload.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
/* eslint-disable default-case */
/* eslint-disable radix */
/* eslint-disable no-else-return */
/* eslint-disable prefer-destructuring */
/* eslint-disable prettier/prettier */
/* eslint-disable no-bitwise */
/*
* cs01lb
*/
// Add ignorable variables in this array.
const ignore_vars = [] as any;

function toTagoFormat(object_item: {}, group: string, prefix = "") {
const result = [] as any;
for (const key in object_item) {
if (ignore_vars.includes(key)) continue;

if (typeof object_item[key] === "object") {
result.push({
variable: object_item[key].variable || `${prefix}${key}`.toLowerCase(),
value: object_item[key].value,
group: object_item[key].group || group,
metadata: object_item[key].metadata,
location: object_item[key].location,
unit: object_item[key].unit,
});
} else {
result.push({
variable: `${prefix}${key}`.toLowerCase(),
value: object_item[key],
group,
});
}
}

return result;
}

function getzf(c_num: string) {
if (parseInt(c_num) < 10) c_num = `0${c_num}`;

return c_num;
}

function getMyDate(str: string) {
let c_Date;
if (Number(str) > 9999999999) c_Date = new Date(parseInt(str));
else c_Date = new Date(parseInt(str) * 1000);

const c_Year = c_Date.getFullYear();
const c_Month = c_Date.getMonth() + 1;
const c_Day = c_Date.getDate();
const c_Hour = c_Date.getHours();
const c_Min = c_Date.getMinutes();
const c_Sen = c_Date.getSeconds();
const c_Time = `${c_Year}-${getzf(c_Month)}-${getzf(c_Day)} ${getzf(
c_Hour
)}:${getzf(c_Min)}:${getzf(c_Sen)}`;

return c_Time;
}

function datalog(i: number, bytes: Buffer) {
const aa = parseFloat(((bytes[1 + i] << 8) | bytes[2 + i]) / 100).toFixed(2);
const bb = parseFloat(((bytes[3 + i] << 8) | bytes[4 + i]) / 100).toFixed(2);
const cc = parseFloat(((bytes[5 + i] << 8) | bytes[6 + i]) / 100).toFixed(2);
const dd = bytes[0 + i] & 0x02 ? "High" : "Low";
const ee = bytes[0 + i] & 0x01 ? "True" : "FALSE";
const ff = getMyDate(
(
(bytes[7 + i] << 24) |
(bytes[8 + i] << 16) |
(bytes[9 + i] << 8) |
bytes[10 + i]
).toString(10)
);
let string = `[${aa},${bb},${cc},${ff},${dd},${ee}]`;
string = string.concat(",");

return string;
}

function datalog2(i: number, bytes: Buffer) {
var aa = parseFloat(((bytes[i] << 8) | bytes[i + 1]) / 100).toFixed(2);
var string = "[" + aa + "]" + ",";
return string;
}

function Decoder(bytes: Buffer, port: number) {
if (port === 0x03) {
let data_sum;
for (let i = 0; i < bytes.length; i += 11) {
const data = datalog(i, bytes);
if (i == 0) data_sum = data;
else data_sum += data;
}
return {
DATALOG: data_sum,
};
} else if (port === 0x07) {
const bat = ((bytes[5] << 8) | bytes[6]) / 1000;
let data_sum;
for (var k = 2; k < bytes.length; k = k + 2) {
const data = datalog2(k, bytes);
if (k == 2) data_sum = data;
else data_sum += data;
}
return {
BAT: bat,
DATALOG: data_sum,
};
} else if (port === 0x02) {
const bat = ((bytes[5] << 8) | bytes[6]) / 1000;
const exti_Trigger = bytes[0] & 0x40 ? "TRUE" : "FALSE";
const exti_Level = bytes[0] & 0x80 ? "HIGH" : "LOW";
const current1_A = ((bytes[2] << 8) | bytes[3]) / 100;
const current2_A = ((bytes[4] << 8) | bytes[5]) / 100;
const current3_A = ((bytes[6] << 8) | bytes[7]) / 100;
const current4_A = ((bytes[8] << 8) | bytes[9]) / 100;
const cur1L_status = bytes[10] & 0x80 ? "True" : "False";
const cur1H_status = bytes[10] & 0x40 ? "True" : "False";
const cur2L_status = bytes[10] & 0x20 ? "True" : "False";
const cur2H_status = bytes[10] & 0x10 ? "True" : "False";
const cur3L_status = bytes[10] & 0x08 ? "True" : "False";
const cur3H_status = bytes[10] & 0x04 ? "True" : "False";
const cur4L_status = bytes[10] & 0x02 ? "True" : "False";
const cur4H_status = bytes[10] & 0x01 ? "True" : "False";

return {
EXTI_Trigger: exti_Trigger,
BAT: bat,
KEEP_STATUS: exti_Level,
Current1_A: current1_A,
Current2_A: current2_A,
Current3_A: current3_A,
Current4_A: current4_A,
Cur1L_status: cur1L_status,
Cur1H_status: cur1H_status,
Cur2L_status: cur2L_status,
Cur2H_status: cur2H_status,
Cur3L_status: cur3L_status,
Cur3H_status: cur3H_status,
Cur4L_status: cur4L_status,
Cur4H_status: cur4H_status,
};
} else if (port === 0x05) {
let sub_band;
let freq_band;
let sensor;
if (bytes[0] === 0x33) sensor = "CS01-LB";

if (bytes[4] === 0xff) sub_band = "NULL";
else sub_band = bytes[4];

if (bytes[3] === 0x01) freq_band = "EU868";
else if (bytes[3] === 0x02) freq_band = "US915";
else if (bytes[3] === 0x03) freq_band = "IN865";
else if (bytes[3] === 0x04) freq_band = "AU915";
else if (bytes[3] === 0x05) freq_band = "KZ865";
else if (bytes[3] === 0x06) freq_band = "RU864";
else if (bytes[3] === 0x07) freq_band = "AS923";
else if (bytes[3] === 0x08) freq_band = "AS923_1";
else if (bytes[3] === 0x09) freq_band = "AS923_2";
else if (bytes[3] === 0x0a) freq_band = "AS923_3";
else if (bytes[3] === 0x0b) freq_band = "CN470";
else if (bytes[3] === 0x0c) freq_band = "EU433";
else if (bytes[3] === 0x0d) freq_band = "KR920";
else if (bytes[3] === 0x0e) freq_band = "MA869";

const firm_ver = `${bytes[1] & 0x0f}.${(bytes[2] >> 4) & 0x0f}.${
bytes[2] & 0x0f
}`;
const bat = ((bytes[5] << 8) | bytes[6]) / 1000;

return {
SENSOR_MODEL: sensor,
FIRMWARE_VERSION: firm_ver,
FREQUENCY_BAND: freq_band,
SUB_BAND: sub_band,
BAT: bat,
};
}
}

const cs01lb_payload = payload.find(
(x) =>
x.variable === "payload_raw" ||
x.variable === "payload" ||
x.variable === "data"
);

const port = payload.find(
(x) => x.variable === "port" || x.variable === "fport"
);

if (cs01lb_payload) {
try {
// Convert the data from Hex to Javascript Buffer.
const buffer = Buffer.from(cs01lb_payload.value, "hex");
const group = new Date().getTime();
const payload_aux = Decoder(buffer, Number(port.value));
payload = payload.concat(toTagoFormat(payload_aux, group));
} catch (e) {
// Print the error to the Live Inspector.
console.error(e);
// Return the variable parse_error for debugging.
payload = [{ variable: "parse_error", value: e.message }];
}
}

0 comments on commit fa56524

Please sign in to comment.