-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #58 from tago-io/feat/connector-dragino-cs01lb-3
Created the dragino cs01lb decoder and tests
- Loading branch information
Showing
6 changed files
with
381 additions
and
0 deletions.
There are no files selected for viewing
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
25
decoders/connector/dragino/cs01lb/v1.0.0/payload-config.jsonc
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
133
decoders/connector/dragino/cs01lb/v1.0.0/payload.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 }, | ||
]); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 }]; | ||
} | ||
} |