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-242 Refactor and Test Network Everynet Plugin for Monorepo #62

Merged
merged 6 commits into from
Oct 17, 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
415 changes: 288 additions & 127 deletions package-lock.json

Large diffs are not rendered by default.

6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@
"zod": "3.23.8",
"systeminformation": "5.23.5",
"express": "4.21.1",
"qs": "6.10.1"
"qs": "6.10.1",
"async": "3.2.6",
"body-parser": "2.0.1"
},
"devDependencies": {
"@biomejs/biome": "1.9.3",
Expand All @@ -52,6 +54,8 @@
"@types/uuid": "10.0.0",
"@types/express": "5.0.0",
"@types/axios": "0.14.0",
"@types/async": "3.2.24",
"@types/ms": "0.7.34",
"concurrently": "9.0.1",
"esbuild": "0.24.0",
"jest": "29.7.0",
Expand Down
3 changes: 0 additions & 3 deletions plugins/mqtt/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,8 @@
"permissions": ["device", "action", "device-data"]
},
"dependencies": {
"async": "3.2.6",
"mqtt-connection": "4.1.0"
},
"devDependencies": {
"@types/async": "3.2.24",
"@types/ms": "0.7.34"
}
}
1 change: 1 addition & 0 deletions plugins/mysql/src/Database/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ function setupConnections(config: Config) {
port: config.main_read_port,
user: config.main_user,
password: config.main_password,
timezone: "local",
};

const mainOptions: IConnectionOptions = {
Expand Down
2 changes: 1 addition & 1 deletion plugins/mysql/src/Providers/Device/getDeviceInfo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ async function getDeviceInfo(deviceID: TGenericID): Promise<IDevice | null> {
response.created_at = new Date(response.created_at);
}
if (response.encoder_stack) {
response.encoder_stack = JSON.parse(JSON.stringify(response.encoder_stack));
response.encoder_stack = JSON.parse(response.encoder_stack);
}
}

Expand Down
2 changes: 1 addition & 1 deletion plugins/mysql/src/Providers/Device/getDeviceList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ async function getDeviceList(
item.tags = JSON.parse(JSON.stringify(item.tags));
}
if (item.encoder_stack) {
item.encoder_stack = JSON.parse(JSON.stringify(item.encoder_stack));
item.encoder_stack = JSON.parse(item.encoder_stack);
}
if (item.last_input) {
item.last_input = new Date(item.last_input);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ In order for the integration to properly identify the device, you must add a tag
* **Tag key**: serial
* **Tag value**: copy the Device EUI in the value of the tag.

For TagoCore to interpret the payload sent by Actility, you need to go to the device and select the Encoder Stack as Actility LoRaWAN.

---
# Action type: Send downlink to the device
This integration also add a new action which you can use the send downlink to the device. Such as select as type of the action when setting up new actions.
Expand Down
20 changes: 20 additions & 0 deletions plugins/network-actility/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"name": "@tago-io/tcore-plugin-network-actility",
"version": "0.7.0",
"private": false,
"main": "./src/index.ts",
"type": "module",
"tcore": {
"name": "Actility LoRaWAN",
"short_description": "Adds support for Actility LoRaWAN",
"full_description": "./README.md",
"icon": "./assets/icon.png",
"cluster": true,
"types": ["service", "encoder", "action-type"],
"permissions": ["device", "action", "device-data"]
},
"dependencies": {
"lora-packet": "0.9.2"
},
"devDependencies": {}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import crypto from "crypto";
import crypto from "node:crypto";
import { core } from "@tago-io/tcore-sdk";
import axios, { AxiosRequestConfig } from "axios";
import { Request, Response } from "express";
import axios, { type AxiosRequestConfig } from "axios";
import type { Request, Response } from "express";
import { DateTime } from "luxon";
import sendResponse from "../lib/sendResponse";
import { IConfigParam } from "../types";
import { getDevice } from "./uplink";
import sendResponse from "../lib/sendResponse.ts";
import type { IConfigParam } from "../types.ts";
import { getDevice } from "./uplink.ts";

interface IDownlinkBuild {
url: string;
Expand Down Expand Up @@ -56,24 +56,40 @@ interface IClassAConfig {
* @param classAConfig - optinal parameter sent by Actlity for class A devices
* @returns {void}
*/
async function downlinkService(config: IConfigParam, req: Request, res: Response, classAConfig: IClassAConfig = {}) {
const authorization = req.headers["Authorization"] || req.headers["authorization"];
async function downlinkService(
config: IConfigParam,
req: Request,
res: Response,
classAConfig: IClassAConfig = {},
) {
const authorization = 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 device = await getDevice(body.device);
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,
});
}

if (!classAConfig?.fcntdn || !classAConfig?.as_id) {
Expand All @@ -83,15 +99,21 @@ async function downlinkService(config: IConfigParam, req: Request, res: Response
const fctdnParam = deviceParams.find((param) => param.key === "fctdn");

if (!asIDParam || !fctdnParam) {
return sendResponse(res, { body: "Invalid authorization header", status: 401 });
return sendResponse(res, {
body: "Invalid authorization header",
status: 401,
});
}

classAConfig.fcntdn = Number(fctdnParam.value as string);
classAConfig.as_id = asIDParam.value as string;
}

const deveui = String(device).toUpperCase();
const time = DateTime.utc().setZone("America/Sao_Paulo").plus({ seconds: 5 }).toFormat("YYYY-MM-DDTHH:mm:ss.SZ");
const time = DateTime.utc()
.setZone("America/Sao_Paulo")
.plus({ seconds: 5 })
.toFormat("YYYY-MM-DDTHH:mm:ss.SZ");
const tokenString = `DevEUI=${deveui}&FPort=${body.port}&FCntDn=${classAConfig.fcntdn}&Payload=${
body.payload
}&AS_ID=${classAConfig.as_id}&Time=${time}${config.tunnel_key.toLowerCase()}`;
Expand All @@ -117,12 +139,18 @@ async function downlinkService(config: IConfigParam, req: Request, res: Response

await 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) => {
return sendResponse(res, { body: JSON.stringify(e.response.data), status: e.response.status });
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
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import toTagoFormat, { IDeviceDataLatLng } from "../lib/toTagoFormat";
import { IPayloadParamsActility } from "./uplink";
import toTagoFormat, { type IDeviceDataLatLng } from "../lib/toTagoFormat.ts";
import type { IPayloadParamsActility } from "./uplink.ts";

/**
* Decode data from Actility
Expand All @@ -9,7 +9,9 @@ import { IPayloadParamsActility } from "./uplink";
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export default async function parser(
payload: Partial<IPayloadParamsActility["DevEUI_uplink"]> & { payload?: string }
payload: Partial<IPayloadParamsActility["DevEUI_uplink"]> & {
payload?: string;
},
): Promise<IDeviceDataLatLng[]> {
if (Array.isArray(payload)) {
return payload;
Expand All @@ -23,19 +25,21 @@ export default async function parser(
const serie = String(new Date().getTime());
if (payload.payload_hex) {
payload.payload = payload.payload_hex;
delete payload.payload_hex;
payload.payload_hex = undefined;
}
delete payload.CustomerData;
delete payload.CustomerID;
delete payload.Lrrs;
delete payload.DevAddr;
delete payload.AppSKey;
delete payload.DynamicClass;
delete payload.InstantPER;
delete payload.MeanPER;
payload.CustomerData = undefined;
payload.CustomerID = undefined;
payload.Lrrs = undefined;
payload.DevAddr = undefined;
payload.AppSKey = undefined;
payload.DynamicClass = undefined;
payload.InstantPER = undefined;
payload.MeanPER = undefined;

let result = toTagoFormat(payload, serie);
result = result.filter((x) => !x.location || (x.location.lat !== 0 && x.location.lng !== 0));
result = result.filter(
(x) => !x.location || (x.location.lat !== 0 && x.location.lng !== 0),
);

return result;
}
Loading
Loading