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/globalstar-network-connectors/CON-272 #79

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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/globalstar/smartone-c/connector.jsonc
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"$schema": "../../../../schema/connector.json",
"name": "Globalstar SmartOne C",
"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/globalstar/smartone-c/description.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Tracks mobile assets, monitors engine run times, sends GPS, battery, and alarm data via satellite
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"$schema": "../../../../../schema/connector_details.json",
"description": "../description.md",
"install_text": "**SmartOne C** is a GPS tracking device for mobile assets such as trailers, cargo containers, heavy construction equipment, generators, and boats/barges. It features two dry contact inputs for monitoring engine run times and alarm inputs, and a serial port for connecting passive and smart sensors. The device processes GPS satellite signals to determine location (longitude and latitude) and transmits data via the Globalstar Satellite Network. It also sends messages including battery status, input alarm status, and diagnostic details. Configuration is done using a computer and USB configuration cable, with messages communicated at specific times or under certain conditions.",
"install_end_text": "",
"device_annotation": "",
"device_parameters": [],
"networks": [
"../../../../network/globalstar/v1.0.0/payload.ts"
]
}
74 changes: 74 additions & 0 deletions decoders/connector/globalstar/smartone-c/v1.0.0/payload.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { describe, test, expect, beforeEach } from "vitest";
import { readFileSync } from "fs";
import { join } from "path";
import { DataToSend } from "@tago-io/sdk/lib/types";
import * as ts from "typescript";

const file = readFileSync(join(__dirname, "./payload.ts"));
const transpiledCode = ts.transpile(file.toString());

let payload: DataToSend[] = [];

describe("SmartOne C Payload Validation", () => {
beforeEach(() => {
payload = [];
});

test("Check all output variables", () => {
payload = [{ variable: "globalstar_payload", value: "002B5372BFF12F0A02", unit: "", metadata: {} }];
const result = eval(transpiledCode);

expect(result).toEqual(
expect.arrayContaining([
expect.objectContaining({ variable: "globalstar_payload", value: "002B5372BFF12F0A02", unit: "", metadata: {} }),
expect.objectContaining({ variable: "battery_state", value: "Good" }),
expect.objectContaining({ variable: "gps_data_valid", value: "Valid" }),
expect.objectContaining({ variable: "missed_input_1", value: 0 }),
expect.objectContaining({ variable: "missed_input_2", value: 0 }),
expect.objectContaining({ variable: "gps_fail_counter", value: 0 }),
expect.objectContaining({
variable: "location",
value: "30.46356439590454,-90.0813889503479",
location: { type: "Point", coordinates: [-90.0813889503479, 30.46356439590454] },
}),
expect.objectContaining({ variable: "message_type", value: "Location Message" }),
])
);
});

test("Check all output variables - location", () => {
payload = [{ variable: "globalstar_payload", value: "C02B5387BFF129090C", unit: "", metadata: {} }];
const result = eval(transpiledCode);

expect(result).toEqual(
expect.arrayContaining([
expect.objectContaining({ variable: "globalstar_payload", value: "C02B5387BFF129090C", unit: "", metadata: {} }),
expect.objectContaining({ variable: "battery_state", value: "Good" }),
expect.objectContaining({ variable: "gps_data_valid", value: "Valid" }),
expect.objectContaining({ variable: "missed_input_1", value: 0 }),
expect.objectContaining({ variable: "missed_input_2", value: 0 }),
expect.objectContaining({ variable: "gps_fail_counter", value: 3 }),
expect.objectContaining({
variable: "location",
value: "30.463789701461792,-90.08151769638062",
location: { type: "Point", coordinates: [-90.08151769638062, 30.463789701461792] },
}),
expect.objectContaining({ variable: "message_type", value: "Location Message" }),
])
);
});

test("Shall not be parsed", () => {
payload = [{ variable: "shallnotpass", value: "invalid_payload" }];
const result = eval(transpiledCode);

expect(result).toEqual(undefined);
});

test("Invalid Payload", () => {
payload = [{ variable: "payload", value: "invalid_payload", unit: "", metadata: {} }];
const result = eval(transpiledCode);

expect(result).toEqual([{ variable: "parse_error", value: "Invalid payload size" }]);
});
});
99 changes: 99 additions & 0 deletions decoders/connector/globalstar/smartone-c/v1.0.0/payload.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { Data, DataToSend } from "@tago-io/sdk/lib/types";

declare let payload: DataToSend[];

function calculateLatitude(lat: number): number {
const degreePerCountLat = 90.0 / Math.pow(2, 23);
let latitude = lat * degreePerCountLat;

if (latitude > 90) {
latitude = 180 - latitude;
}

return latitude;
}

function calculateLongitude(long: number): number {
const degreePerCountLong = 180.0 / Math.pow(2, 23);
let longitude = long * degreePerCountLong;

if (longitude > 180) {
longitude = longitude - 360;
}

return longitude;
}

interface DecodedData extends Pick<Data, "variable" | "value" | "time" | "unit" | "group" | "location"> {}

function ParsePayload(payload: string, group: string | undefined, receivedTime: Date | string | undefined): DecodedData[] {
const buffer = Buffer.from(payload, "hex");
// const buffer = payload as unknown as Buffer;

if (buffer.length < 2) {
throw new Error("Invalid payload size");
}

const data: DecodedData[] = [];
const checkTime = receivedTime || new Date().toISOString();
const time = new Date(checkTime);

const batteryState = (buffer.readUInt8(0) & 0x04) >> 2;
const gpsDataValid = (buffer.readUInt8(0) & 0x08) >> 3;
const missedInput1 = (buffer.readUInt8(0) & 0x10) >> 4;
const missedInput2 = (buffer.readUInt8(0) & 0x20) >> 5;
const gpsFailCounter = (buffer.readUInt8(0) & 0xc0) >> 6;

const latitude = calculateLatitude(buffer.readIntBE(1, 3));
const longitude = calculateLongitude(buffer.readIntBE(4, 3));

data.push({ variable: "battery_state", value: batteryState ? "Replace" : "Good", group, time });
data.push({ variable: "gps_data_valid", value: gpsDataValid ? "Invalid" : "Valid", group, time });
data.push({ variable: "missed_input_1", value: missedInput1, group, time });
data.push({ variable: "missed_input_2", value: missedInput2, group, time });
data.push({ variable: "gps_fail_counter", value: gpsFailCounter, group, time });
data.push({ variable: "location", value: `${latitude},${longitude}`, location: { type: "Point", coordinates: [longitude, latitude] }, group, time });

const messageSubtype = (buffer.readUInt8(7) & 0xf0) >> 4;

switch (messageSubtype) {
case 0:
data.push({ variable: "message_type", value: "Location Message", group, time });
break;
case 1:
data.push({ variable: "message_type", value: "Device Turned On Message", group, time });
break;
case 2:
data.push({ variable: "message_type", value: "Change of Location Area Alert Message", group, time });
break;
case 3:
data.push({ variable: "message_type", value: "Input Status Changed Message", group, time });
break;
case 4:
data.push({ variable: "message_type", value: "Undesired Input State Message", group, time });
break;
case 5:
data.push({ variable: "message_type", value: "Re-Center Message", group, time });
break;
default:
data.push({ variable: "message_type", value: "Unknown", group, time });
break;
}

return data;
}

// Handle Received Data
const payload_raw = payload.find((x) => ["payload_raw", "payload", "data", "globalstar_payload"].includes(x.variable));

if (payload_raw) {
try {
const parsedData = ParsePayload(payload_raw.value as string, payload_raw.group, payload_raw.time);
payload = payload.concat(parsedData);
} 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 }];
}
}
Empty file.
Empty file.
15 changes: 15 additions & 0 deletions decoders/network/globalstar/network.jsonc
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"$schema": "../../../schema/network.json",
"name": "KPN Things",
"images": {
"logo": "",
"icon": "",
"banner": ""
},
"versions": {
"v1.0.0": {
"src": "./v1.0.0/payload.ts",
"manifest": "./v1.0.0/payload-config.jsonc"
}
}
}
12 changes: 12 additions & 0 deletions decoders/network/globalstar/v1.0.0/payload-config.jsonc
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"$schema": "../../../../schema/network_details.json",
"description": "../description.md",
"serial_number_config": {
"required": true,
"label": "EUI / IMEI / UUID",
"case": "upper"
},
"device_parameters": [],
"middleware_endpoint": "kpn.middleware.tago.io",
"documentation_url": "https://help.tago.io/portal/en/community/topic/how-to-integrate-with-kpn-things"
}
39 changes: 39 additions & 0 deletions decoders/network/globalstar/v1.0.0/payload.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { describe, test, expect, beforeEach } from "vitest";
import { readFileSync } from "fs";
import { join } from "path";
import { DataToSend } from "@tago-io/sdk/lib/types";
import * as ts from "typescript";

const file = readFileSync(join(__dirname, "./payload.ts"));
const transpiledCode = ts.transpile(file.toString());

let payload: DataToSend[] = [];

describe("Globalstar Payload Validation", () => {
beforeEach(() => {
payload = [
{
variable: "globalstar_payload",
value:
'[{"bn":"urn:dev:DVNUUID:737b588b-547e-45d3-96d0-f79723291bf1:","bt":1713193637,"n":"battery","u":"%","vs":"32.0"},{"n":"accelerationX","u":"m/s2","v":0.39},{"n":"accelerationY","u":"m/s2","v":3.64},{"n":"accelerationZ","u":"m/s2","v":9.46},{"n":"latitude","u":"lat","v":51.90725},{"n":"longitude","u":"lon","v":4.48934}]',
unit: "",
metadata: {},
},
];
});

test("Check all output variables for acceleration", () => {
const result = eval(transpiledCode);

expect(result).toEqual(

Check failure on line 28 in decoders/network/globalstar/v1.0.0/payload.test.ts

View workflow job for this annotation

GitHub Actions / test

decoders/network/globalstar/v1.0.0/payload.test.ts > Globalstar Payload Validation > Check all output variables for acceleration

AssertionError: expected undefined to deeply equal ArrayContaining{…} - Expected: ArrayContaining [ ObjectContaining { "time": 2024-04-15T15:07:17.000Z, "unit": "%", "value": "32.0", "variable": "battery", }, ObjectContaining { "time": 2024-04-15T15:07:17.000Z, "unit": "m/s2", "value": 0.39, "variable": "accelerationX", }, ObjectContaining { "time": 2024-04-15T15:07:17.000Z, "unit": "m/s2", "value": 3.64, "variable": "accelerationY", }, ObjectContaining { "time": 2024-04-15T15:07:17.000Z, "unit": "m/s2", "value": 9.46, "variable": "accelerationZ", }, ObjectContaining { "time": 2024-04-15T15:07:17.000Z, "unit": "lat", "value": 51.90725, "variable": "latitude", }, ObjectContaining { "time": 2024-04-15T15:07:17.000Z, "unit": "lon", "value": 4.48934, "variable": "longitude", }, ] + Received: undefined ❯ decoders/network/globalstar/v1.0.0/payload.test.ts:28:20

Check failure on line 28 in decoders/network/globalstar/v1.0.0/payload.test.ts

View workflow job for this annotation

GitHub Actions / test

decoders/network/globalstar/v1.0.0/payload.test.ts > Globalstar Payload Validation > Check all output variables for acceleration

AssertionError: expected undefined to deeply equal ArrayContaining{…} - Expected: ArrayContaining [ ObjectContaining { "time": 2024-04-15T15:07:17.000Z, "unit": "%", "value": "32.0", "variable": "battery", }, ObjectContaining { "time": 2024-04-15T15:07:17.000Z, "unit": "m/s2", "value": 0.39, "variable": "accelerationX", }, ObjectContaining { "time": 2024-04-15T15:07:17.000Z, "unit": "m/s2", "value": 3.64, "variable": "accelerationY", }, ObjectContaining { "time": 2024-04-15T15:07:17.000Z, "unit": "m/s2", "value": 9.46, "variable": "accelerationZ", }, ObjectContaining { "time": 2024-04-15T15:07:17.000Z, "unit": "lat", "value": 51.90725, "variable": "latitude", }, ObjectContaining { "time": 2024-04-15T15:07:17.000Z, "unit": "lon", "value": 4.48934, "variable": "longitude", }, ] + Received: undefined ❯ decoders/network/globalstar/v1.0.0/payload.test.ts:28:20
expect.arrayContaining([
expect.objectContaining({ variable: "battery", value: "32.0", unit: "%", time: new Date(1713193637 * 1000) }),
expect.objectContaining({ variable: "accelerationX", value: 0.39, unit: "m/s2", time: new Date(1713193637 * 1000) }),
expect.objectContaining({ variable: "accelerationY", value: 3.64, unit: "m/s2", time: new Date(1713193637 * 1000) }),
expect.objectContaining({ variable: "accelerationZ", value: 9.46, unit: "m/s2", time: new Date(1713193637 * 1000) }),
expect.objectContaining({ variable: "latitude", value: 51.90725, unit: "lat", time: new Date(1713193637 * 1000) }),
expect.objectContaining({ variable: "longitude", value: 4.48934, unit: "lon", time: new Date(1713193637 * 1000) }),
])
);
});
});
27 changes: 27 additions & 0 deletions decoders/network/globalstar/v1.0.0/payload.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { DataToSend } from "@tago-io/sdk/lib/types";

declare let payload: DataToSend[];

/**
* @description Decodes the SenML payload
* @param {SenML[]} senMLObj - SenML object
*/
function decoder(senMLObj: any[]) {
// * to be implemented
}

// Handle Received Data
const kpnPayload = payload.find((x) => x.variable === "globalstar_payload");

if (kpnPayload) {
try {
const contentJSON = JSON.parse(kpnPayload.value as string);
const parsedData = decoder(contentJSON);
// payload = parsedData;
} catch (error) {
// 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