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

feat/connectors-globalset #4

Merged
2 changes: 1 addition & 1 deletion biome.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
},
"organizeImports": { "enabled": true },
"linter": {
"ignore": ["node_modules"],
"ignore": ["node_modules", "./decoders/**/v1.0.0/*.ts", "./decoders/**/v1.0.0/payload.js"],
"enabled": true,
"rules": {
"recommended": true,
Expand Down
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/globalsat/kt-520/connector.jsonc
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"$schema": "../../../../schema/connector.json",
"name": "GlobalSat KT-520",
"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/globalsat/kt-520/description.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Satellite Tracker
11 changes: 11 additions & 0 deletions decoders/connector/globalsat/kt-520/v1.0.0/payload-config.jsonc
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"$schema": "../../../../../schema/connector_details.json",
"description": "../description.md",
"install_text": "Features:\n\n* Kineis satellite connectivity\n* Built-in GNSS solution\n* Built-in 3-axis accelerometer for motion detection capability\n* The device is powered by a SAFT 17Ah non-rechargeable battery\n* Expected battery life of 5 years based on 20 messages via Kineis satellite per day\n* The device shall operate in ambient temperature between -20℃ and +55℃.\n* Maintenance-free design\n* The device shall measure or estimate the remaining battery level.\n* Device configuration and firmware update OTA via BLE.\n* Weight : approx. 280g\n* Dimensions : 84.5 x 130.8 x 44 mm\n* Ingress Protection : IP69K\n\nDevice URL: \n\n* https://www.globalsat.com.tw/en/product-283464/Kineis-Satellite-Tracker-KT-520.html",
"install_end_text": "",
"device_annotation": "",
"device_parameters": [],
"networks": [
"../../../../network/kinis/v1.0.0/payload.js"
]
}
95 changes: 95 additions & 0 deletions decoders/connector/globalsat/kt-520/v1.0.0/payload.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { describe, expect, it, test } from "vitest";
import { decoderRun } from "../../../../../src/functions/decoder-run";

const file_path = "decoders/connector/globalsat/kt-520/v1.0.0/payload.ts" as const;

function preparePayload(payloadHex: string) {
let payload = [{ variable: "sensors_raw_data", value: payloadHex, unit: "" } as any];
payload = decoderRun(file_path, { payload });
const period_between_gps_acq = payload.find((item) => item.variable === "period_between_gps_acq");
const location = payload.find((item) => item.variable === "location");
// Error parsing
const parse_error = payload.find((item) => item.variable === "parse_error");
return {
payload,
period_between_gps_acq,
location,
parse_error,
};
}

describe("kt520 Decoder", () => {
it("should decode the payload correctly", () => {
const payloadHex = "EAE2513343D06C80010000000600020001000000000000";
const result = preparePayload(payloadHex);

expect(result.payload[0]).toMatchObject({ variable: "period_between_gps_acq", value: 60, unit: "minutes", time: expect.any(Number) });
expect(result.payload[1]).toMatchObject({
variable: "location",
value: "24.9964,121.4874",
location: { lat: 24.9964, lng: 121.4874 },
time: expect.any(Number),
});
expect(result.payload[2]).toMatchObject({
variable: "location",
value: "24.9964,121.4874",
location: { lat: 24.9964, lng: 121.4874 },
time: expect.any(Number),
});
expect(result.payload[3]).toMatchObject({
variable: "location",
value: "24.9964,121.4873",
location: { lat: 24.9964, lng: 121.4873 },
time: expect.any(Number),
});
expect(result.payload[4]).toMatchObject({
variable: "location",
value: "24.9964,121.4874",
location: { lat: 24.9964, lng: 121.4874 },
time: expect.any(Number),
});
});

it("should decode the payload correctly", () => {
const payloadHex = "EEC4413363D0700009000A0008002C0008000C00000000";
const result = preparePayload(payloadHex);

expect(result.payload[0]).toMatchObject({ variable: "period_between_gps_acq", value: 60, unit: "minutes", time: expect.any(Number) });
expect(result.payload[1]).toMatchObject({
variable: "location",
value: "24.9968,-13.3531",
location: { lat: 24.9968, lng: -13.3531 },
time: expect.any(Number),
});
expect(result.payload[2]).toMatchObject({
variable: "location",
value: "24.9970,-13.3535",
location: { lat: 24.997, lng: -13.3535 },
time: expect.any(Number),
});
expect(result.payload[3]).toMatchObject({
variable: "location",
value: "24.9958,-13.3529",
location: { lat: 24.9958, lng: -13.3529 },
time: expect.any(Number),
});
expect(result.payload[4]).toMatchObject({
variable: "location",
value: "24.9957,-13.3535",
location: { lat: 24.9957, lng: -13.3535 },
time: expect.any(Number),
});
});
});

describe("Shall not be parsed", () => {
let payload = [{ variable: "shallnotpass", value: "04096113950292" }];
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" }]);
});
});
138 changes: 138 additions & 0 deletions decoders/connector/globalsat/kt-520/v1.0.0/payload.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
/* eslint-disable unicorn/number-literal-case */
/* eslint-disable unicorn/numeric-separators-style */
// eslint-disable-next-line unicorn/prefer-set-has
function hexToBinary(hex) {
let binary = "";
for (let i = 0; i < hex.length; i++) {
binary += parseInt(hex[i], 16).toString(2).padStart(4, "0");
}
return binary;
}

function calculateNewLongitude(originalLongitude: number, metersToAdd: number, latitude: number) {
const latitudeInRadians = (latitude * Math.PI) / 180;
const changeInLongitude = metersToAdd / (111320 * Math.cos(latitudeInRadians));
const newLongitude = originalLongitude + changeInLongitude;
return newLongitude;
}

function calculateNewLatitude(originalLatitude: number, metersToAdd: number) {
const changeInLatitude = metersToAdd / 111132;
const newLatitude = originalLatitude + changeInLatitude;
return newLatitude;
}

function kt520decoder(bytes) {
const decoded: any = [];
const binaryPayload = hexToBinary(bytes);
const time_period = binaryPayload.slice(0, 2) === "10" ? 30 : 60;
decoded.push({ variable: "period_between_gps_acq", value: time_period, unit: "minutes" }); // time period between GPS acquisitions
// modify the time to be the hours and minutes of the first GPS acquisition
const hours = parseInt(binaryPayload.slice(2, 7), 2);
const minutes = parseInt(binaryPayload.slice(7, 13), 2);

const aux = Date.now();
const date = new Date(aux);
date.setHours(hours);
date.setMinutes(minutes);
let time = date.getTime();
decoded[0].time = time;

const fistlng = parseInt(binaryPayload.slice(14, 35), 2);
const fistlat = parseInt(binaryPayload.slice(36, 56), 2);

const lng1sign = binaryPayload.slice(13, 14) === "0" ? 1 : -1;
const lat1sign = binaryPayload.slice(35, 36) === "0" ? 1 : -1;

const lng1 = fistlng * lng1sign * 0.0001;
const lat1 = fistlat * lat1sign * 0.0001;

decoded.push({
variable: "location",
value: `${lat1.toFixed(4)},${lng1.toFixed(4)}`,
location: { lat: Number(lat1.toFixed(4)), lng: Number(lng1.toFixed(4)) },
time,
group: String(time),
});

const lng2Sign = binaryPayload.slice(56, 57) === "0" ? -1 : 1;
const lat2Sign = binaryPayload.slice(71, 72) === "0" ? -1 : 1;
let lng2 = parseInt(binaryPayload.slice(57, 71), 2);
let lat2 = parseInt(binaryPayload.slice(72, 86), 2);

lng2 = lng2Sign * lng2 * 10;
lat2 = lat2Sign * lat2 * 10;

const lng2Final = calculateNewLongitude(Number(lng1), lng2, Number(lat1));
const lat2Final = calculateNewLatitude(Number(lat1), lat2);

// add time_period, which is in minutes, to the time of the first GPS acquisition
time += time_period * 60 * 1000;

decoded.push({
variable: "location",
value: `${lat2Final.toFixed(4)},${lng2Final.toFixed(4)}`,
location: { lat: Number(lat2Final.toFixed(4)), lng: Number(lng2Final.toFixed(4)) },
time,
group: String(time),
});

const lng3Sign = binaryPayload.slice(86, 87) === "0" ? -1 : 1;
const lat3Sign = binaryPayload.slice(102, 103) === "0" ? -1 : 1;
let lng3 = parseInt(binaryPayload.slice(87, 102), 2);
let lat3 = parseInt(binaryPayload.slice(103, 118), 2);

lng3 = lng3Sign * lng3 * 10;
lat3 = lat3Sign * lat3 * 10;

const lng3Final = calculateNewLongitude(Number(lng1), lng3, Number(lat1));
const lat3Final = calculateNewLatitude(Number(lat1), lat3);

// add time_period, which is in minutes, to the time of the first GPS acquisition
time += time_period * 60 * 1000;

decoded.push({
variable: "location",
value: `${lat3Final.toFixed(4)},${lng3Final.toFixed(4)}`,
location: { lat: Number(lat3Final.toFixed(4)), lng: Number(lng3Final.toFixed(4)) },
time,
group: String(time),
});

const lng4Sign = binaryPayload.slice(118, 119) === "0" ? -1 : 1;
const lat4Sign = binaryPayload.slice(135, 136) === "0" ? -1 : 1;
let lng4 = parseInt(binaryPayload.slice(120, 135), 2);
let lat4 = parseInt(binaryPayload.slice(136, 152), 2);

lng4 = lng4Sign * lng4 * 10;
lat4 = lat4Sign * lat4 * 10;

const lng4Final = calculateNewLongitude(Number(lng1), lng4, Number(lat1));
const lat4Final = calculateNewLatitude(Number(lat1), lat4);

// add time_period, which is in minutes, to the time of the third GPS acquisition
time += time_period * 60 * 1000;

decoded.push({
variable: "location",
value: `${lat4Final.toFixed(4)},${lng4Final.toFixed(4)}`,
location: { lat: Number(lat4Final.toFixed(4)), lng: Number(lng4Final.toFixed(4)) },
time,
group: String(time),
});
return decoded;
}

const kt520PayloadData = payload.find((x) => x.variable === "sensors_raw_data");

if (kt520PayloadData) {
try {
const decodedkt520Payload = kt520decoder(kt520PayloadData.value);
payload = decodedkt520Payload.map((x) => ({ ...x }));
} catch (error: any) {
// Print the error to the Live Inspector.
console.error(error);
// Return the variable parse_error for debugging.
payload = [{ variable: "parse_error", value: error.message }];
}
}
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/globalsat/ls-111p/connector.jsonc
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"$schema": "../../../../schema/connector.json",
"name": "GlobalSat LS-111P",
"images": {
"logo": "./assets/logo.png"
},
"versions": {
"v1.0.0": {
"src": "./v1.0.0/payload.js",
"manifest": "./v1.0.0/payload-config.jsonc"
}
}
}
1 change: 1 addition & 0 deletions decoders/connector/globalsat/ls-111p/description.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Carbon Dioxide CO2 and Temperature & Humidity Detector with LoRaWAN™
23 changes: 23 additions & 0 deletions decoders/connector/globalsat/ls-111p/v1.0.0/payload-config.jsonc
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"$schema": "../../../../../schema/connector_details.json",
"description": "../description.md",
"install_text": "##\nLS-111P is designed to measure Carbon Dioxide (CO2), Temperature and Humidity by LoRa® long-range and low-power wireless connectivity. It is integrated LoRa® wireless technology, CO2 sensor knowhow and high-performance MCU solution for various IoT markets usage. With calibrated CO2 sensor module and compensated Temperature/ Humidity sensor integration, the data is ready for use. It is perfect for monitoring air quality in schools, office buildings, greenhouses, factories, hotels, hospitals, transportation lines and anywhere high levels of carbon dioxide are generated.\n##\n* LoRaWAN™ compliant\n* High receiver sensitivity, long range solution\n* Integrated with calibrated CO2 sensor\n* Accuracy: ±50 ppm, ±3% of reading\n* Range: 0 ~ 10,000 ppm\n* Integrated with compensated Temp/ RH sensor\n* Wide range DC power-in, 8~24V /or Micro-USB DC power-in, 5V\n* Display CO2 concentration, Temp/ RH\n* Alarm LED indicator\n##\nMore information about this device can be found [here.](https://www.globalsat.com.tw/en/product-225265/CO2-and-Temperature-Humidity-Detector-with-LoRaWAN%E2%84%A2-Certified-Module-LS-111P.html) ",
"install_end_text": "",
"device_annotation": "",
"device_parameters": [],
"networks": [
"../../../../network/lorawan-everynet/v1.0.0/payload.js",
"../../../../network/lorawan-loriot-/v1.0.0/payload.js",
"../../../../network/lorawan-machineq/v1.0.0/payload.js",
"../../../../network/lorawan-orbiwise/v1.0.0/payload.js",
"../../../../network/lorawan-kerlink/v1.0.0/payload.js",
"../../../../network/lorawan-citykinect/v1.0.0/payload.js",
"../../../../network/lorawan-tektelic/v1.0.0/payload.js",
"../../../../network/lorawan-actility/v1.0.0/payload.js",
"../../../../network/lorawan-ttittn-v3/v1.0.0/payload.js",
"../../../../network/lorawan-swisscom/v1.0.0/payload.js",
"../../../../network/lorawan-chirpstack/v1.0.0/payload.js",
"../../../../network/lorawan-helium/v1.0.0/payload.js",
"../../../../network/lorawan-brdot-/v1.0.0/payload.js"
]
}
80 changes: 80 additions & 0 deletions decoders/connector/globalsat/ls-111p/v1.0.0/payload.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/* This code finds the variable 'everynet_payload' sent by Everynet Middleware inside the payload posted by the device.
** Then convert all fields to TagoIO format.
**
** The ignore_vars variable in this code should be used to ignore variables
** from the device that you don't want.
*/
// Add ignorable variables in this array.
const ignore_vars = ['device_addr', 'port', 'duplicate', 'network', 'packet_hash', 'application', 'device', 'packet_id'];

// Function to convert an object to TagoIO data format.
function toTagoFormat(object_item, serie, prefix = '') {
const result = [];
for (const key in object_item) {
if (ignore_vars.includes(key)) continue; // ignore chosen vars

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

return result;
}

function TransformSolutionParam(solutions, serie) {
let to_tago = [];
for (const s of solutions) {
let convert_json = {};
if (!ignore_vars.includes('location')) {
convert_json.location = { value: `${s.lat}, ${s.lng}`, location: { lat: s.lat, lng: s.lng } };
}
delete s.lat;
delete s.lng;

convert_json = { ...convert_json, ...s };
to_tago = to_tago.concat(toTagoFormat(convert_json, serie));
}

return to_tago;
}


// Find the Everynet payload. So we don't try to parse random variables.
let body = payload.find(item => item.variable === 'everynet_payload');

if (body) {
// Copy the same serie created by Everynet middleware, or create a new one based on timestamp.
const serie = body.serie || new Date().getTime();

// The data is string formated, so we convert it to JSON.
body = JSON.parse(body.value);
let vars_to_tago = [];
if (body.params.solutions) {
vars_to_tago = vars_to_tago.concat(TransformSolutionParam(body.params.solutions));
delete body.params.solutions;
}

// Parse the fields. Go to toTagoFormat function if you need to change something.
vars_to_tago = vars_to_tago.concat(toTagoFormat(body.params, serie));
vars_to_tago = vars_to_tago.concat(toTagoFormat(body.meta, serie));

// Here's an example of how to add color to a location pin. You can replicate the following code to do other changes
// Ex:
// const location_var = vars_to_tago.find(x => x.variable == 'location');
// if (location_var) { location_var.metadata = { color: 'lightgreen' }; }

// Change the payload to the new formated variables.
payload = vars_to_tago;
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading