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

TCORE-244 Refactor and Test Network Tektelic Plugin for Monorepo #64

Merged
merged 4 commits into from
Oct 18, 2024
Merged
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
9 changes: 9 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 18 additions & 0 deletions plugins/network-tektelic/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"name": "@tago-io/tcore-plugin-network-tektelic",
"version": "0.7.0",
"private": false,
"main": "./src/index.ts",
"type": "module",
"tcore": {
"name": "Tektelic LoRaWAN",
"short_description": "Adds support for Tektelic LoRaWAN",
"full_description": "./README.md",
"icon": "./assets/icon.png",
"cluster": true,
"types": ["service", "encoder", "action-type"],
"permissions": ["device", "action", "device-data"]
},
"dependencies": {},
"devDependencies": {}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { core } from "@tago-io/tcore-sdk";
import axios, { AxiosRequestConfig } from "axios";
import { Request, Response } from "express";
import sendResponse from "../lib/sendResponse";
import { IConfigParam } from "../types";
import { getDevice } from "./uplink";
import axios, { type AxiosRequestConfig } from "axios";
import type { Request, Response } from "express";
import sendResponse from "../lib/sendResponse.ts";
import type { IConfigParam } from "../types.ts";
import { getDevice } from "./uplink.ts";

interface IDownlinkBuild {
deviceEUI: string;
Expand All @@ -23,7 +23,13 @@ interface IDownlinkBuild {
* @param body.url - network server url
* @param body.confirmed - confirmed true/false
*/
async function sendDownlink({ deviceEUI, port, payload, url, confirmed }: IDownlinkBuild) {
async function sendDownlink({
deviceEUI,
port,
payload,
url,
confirmed,
}: IDownlinkBuild) {
const options: AxiosRequestConfig = {
url,
method: "POST",
Expand Down Expand Up @@ -57,25 +63,42 @@ interface IClassAConfig {
* @param res - request response
* @returns {void}
*/
async function downlinkService(config: IConfigParam, req: Request, res: Response) {
async function downlinkService(
config: IConfigParam,
req: Request,
res: Response,
) {
const authorization =
req.headers["authorization-code"] || req.headers["Authorization"] || req.headers["authorization"];
req.headers["authorization-code"] ||
req.headers.Authorization ||
req.headers.authorization;
if (!authorization || authorization !== config.authorization_code) {
console.error(`[Network Server] Request refused, authentication is invalid: ${authorization}`);
return sendResponse(res, { body: "Invalid authorization header", status: 401 });
console.error(
`[Network Server] Request refused, authentication is invalid: ${authorization}`,
);
return sendResponse(res, {
body: "Invalid authorization header",
status: 401,
});
}

const body = <IDownlinkParams>req.body;
const port = Number(body.port || 1);

if (!body.device) {
return sendResponse(res, { body: "Missing device paramater with device eui as value", status: 400 });
return sendResponse(res, {
body: "Missing device paramater with device eui as value",
status: 400,
});
}
if (!body.port) {
return sendResponse(res, { body: "Missing port paramater", status: 400 });
}
if (!body.payload) {
return sendResponse(res, { body: "Missing payload paramater with hexadecimal value", status: 400 });
return sendResponse(res, {
body: "Missing payload paramater with hexadecimal value",
status: 400,
});
}

const device = await getDevice(body.device);
Expand All @@ -95,13 +118,21 @@ async function downlinkService(config: IConfigParam, req: Request, res: Response

return sendDownlink(downlinkBuild)
.then(() => {
return sendResponse(res, { body: { status: true, message: "Downlink successfully sent" }, status: 201 });
return sendResponse(res, {
body: { status: true, message: "Downlink successfully sent" },
status: 201,
});
})
.catch((e) => {
console.log(`[Tektelic Dn-error ${e.response.status}] ${JSON.stringify(e.response.data)}`);
return sendResponse(res, { body: JSON.stringify(e.response.data), status: e.response.status });
console.log(
`[Tektelic Dn-error ${e.response.status}] ${JSON.stringify(e.response.data)}`,
);
return sendResponse(res, {
body: JSON.stringify(e.response.data),
status: e.response.status,
});
});
}

export default downlinkService;
export { IClassAConfig, IDownlinkParams };
export type { IClassAConfig, IDownlinkParams };
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { core } from "@tago-io/tcore-sdk";
import { IDeviceData } from "@tago-io/tcore-sdk/build/Types";
import { IConfigParam } from "../types";
import downlinkService, { IDownlinkParams } from "./downlink";
import type { IDeviceData } from "@tago-io/tcore-sdk/Types";
import type { IConfigParam } from "../types.ts";
import downlinkService, { type IDownlinkParams } from "./downlink.ts";

class ResMockup {
_status = 200;
json(body: {}) {
json(body: any) {
if (this._status >= 400) {
console.error(body);
}
Expand All @@ -32,18 +32,29 @@ async function downlinkAction(
pluginConfig: IConfigParam,
actionID: string,
actionSettings: IDownlinkParams,
dataList: IDeviceData
dataList: IDeviceData,
) {
const action = await core.getActionInfo(actionID);

if (Array.isArray(dataList) && dataList[0].variable) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const variables = action.trigger.conditions.map((condition: any) => condition.variable);
const variables = action.trigger.conditions.map(
(condition: any) => condition.variable,
);
const data = dataList.find((item) => variables.includes(item.variable));

actionSettings.payload = actionSettings.payload.replace(/\$VALUE\$/g, data.value);
actionSettings.payload = actionSettings.payload.replace(/\$VARIABLE\$/g, data.variable);
actionSettings.payload = actionSettings.payload.replace(/\$SERIE\$/g, data.serie);
actionSettings.payload = actionSettings.payload.replace(
/\$VALUE\$/g,
data.value,
);
actionSettings.payload = actionSettings.payload.replace(
/\$VARIABLE\$/g,
data.variable,
);
actionSettings.payload = actionSettings.payload.replace(
/\$SERIE\$/g,
data.serie,
);

if (actionSettings.device.toLowerCase() === "$device_id$") {
const deviceInfo = await core.getDeviceInfo(data.origin);
Expand Down
78 changes: 78 additions & 0 deletions plugins/network-tektelic/src/Services/tektelicParser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import inspectFormat from "../lib/inspectFormat.ts";
import toTagoFormat, { type IDeviceDataLatLng } from "../lib/toTagoFormat.ts";

/**
* Decode data from Tektelic
*
* @param payload - any payload sent by the device
* @returns {IDeviceDataLatLng[]} data to be stored
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export default async function parser(payload: any) {
if (Array.isArray(payload)) {
return payload;
}

if (!payload.payloadMetaData) {
return payload;
}

const serie = String(new Date().getTime());
let toTago: IDeviceDataLatLng[] = [];

if (payload.payloadMetaData) {
payload.payloadMetaData.deviceMetaData = undefined;
payload.payloadMetaData.applicationMetaData = undefined;
payload.payloadMetaData.gatewayMetaDataList[0].id = undefined;
payload.payloadMetaData.gatewayMetaDataList[0].name = undefined;
payload.payloadMetaData.gatewayMetaDataList[0].gtw_id =
payload.payloadMetaData.gatewayMetaDataList[0].mac;
payload.payloadMetaData.gatewayMetaDataList[0].mac = undefined;

payload.payloadMetaData.gatewayMetaDataList[0].gtw = {
altitude: payload.payloadMetaData.gatewayMetaDataList[0].altitude,
latitude: payload.payloadMetaData.gatewayMetaDataList[0].latitude,
longitude: payload.payloadMetaData.gatewayMetaDataList[0].longitude,
};

payload.payloadMetaData.gatewayMetaDataList[0].altitude = undefined;
payload.payloadMetaData.gatewayMetaDataList[0].latitude = undefined;
payload.payloadMetaData.gatewayMetaDataList[0].longitude = undefined;

toTago = toTago.concat(
inspectFormat(payload.payloadMetaData.gatewayMetaDataList[0], serie),
);
payload.payloadMetaData.gatewayMetaDataList[0] = undefined;
toTago = toTago.concat(inspectFormat(payload.payloadMetaData, serie));
payload.payloadMetaData = undefined;
}
// Parse the payload_fields. Go to inspectFormat function if you need to change something.
if (payload.payload) {
if (payload.payload.data) {
payload.payload.payload = payload.payload.data;
payload.payload.data = undefined;
}
if (payload.payload.payload_hex) {
payload.payload.payload = payload.payload.payload_hex;
payload.payload.payload_hex = undefined;
}

if (payload.payload.bytes) {
payload.payload.payload = payload.payload.bytes;
payload.payload.bytes = undefined;
}

if (payload.payload?.payload?.includes("[")) {
payload.payload.payload = Buffer.from(
JSON.parse(payload.payload.payload),
).toString("hex");
}

toTago = toTago.concat(toTagoFormat(payload.payload, serie));
}
toTago = toTago.filter(
(x) => !x.location || (x.location && x.location.lat !== 0 && x.location.lng !== 0 && !Number.isNaN(x.location.lat) && !Number.isNaN(x.location.lng)),
);

return toTago;
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { core } from "@tago-io/tcore-sdk";
import { Request, Response } from "express";
import { IDevice } from "@tago-io/tcore-sdk/build/Types";
import { IConfigParam } from "../types";
import sendResponse from "../lib/sendResponse";
import type { Request, Response } from "express";
import sendResponse from "../lib/sendResponse.ts";
import type { IConfigParam } from "../types.ts";

interface IPayloadParamsTektelic {
payloadMetaData: {
Expand Down Expand Up @@ -44,7 +43,9 @@ async function getDevice(devEui: string) {
if (!deviceList || !deviceList.length) {
throw "Authorization Denied: Device EUI doesn't match any serial tag";
}
const device = deviceList.find((x) => x.tags.find((tag) => tag.key === "serial" && tag.value === devEui));
const device = deviceList.find((x) =>
x.tags.find((tag) => tag.key === "serial" && tag.value === devEui),
);
if (!device) {
throw "Authorization Denied: Device EUI doesn't match any serial tag";
}
Expand All @@ -60,21 +61,34 @@ async function getDevice(devEui: string) {
* @param res - express res param
* @returns {void}
*/
async function uplinkService(config: IConfigParam, req: Request, res: Response) {
async function uplinkService(
config: IConfigParam,
req: Request,
res: Response,
) {
const authorization =
req.headers["authorization-code"] || req.headers["Authorization"] || req.headers["authorization"];
req.headers["authorization-code"] ||
req.headers.Authorization ||
req.headers.authorization;
if (!authorization || authorization !== config.authorization_code) {
console.error(`[Network Server] Request refused, authentication is invalid: ${authorization}`);
return sendResponse(res, { body: "Invalid authorization header", status: 401 });
console.error(
`[Network Server] Request refused, authentication is invalid: ${authorization}`,
);
return sendResponse(res, {
body: "Invalid authorization header",
status: 401,
});
}

const data: IPayloadParamsTektelic = req.body;
if (!data.payloadMetaData) {
console.error(`[Network Server] Request refused, body is invalid`);
console.error("[Network Server] Request refused, body is invalid");
return sendResponse(res, { body: "Invalid body received", status: 401 });
}

const deviceEUI = (req.headers["device"] as string) || data.payloadMetaData.deviceMetaData?.deviceEUI;
const deviceEUI =
(req.headers.device as string) ||
data.payloadMetaData.deviceMetaData?.deviceEUI;
const device = await getDevice(deviceEUI).catch((e) => {
return sendResponse(res, { body: e.message || e, status: 400 });
});
Expand Down
Loading
Loading