Skip to content

Commit

Permalink
Merge pull request #108 from tago-io/feat/networks/actility/locationdata
Browse files Browse the repository at this point in the history
Feat/networks/actility/locationdata
  • Loading branch information
FabianoEger authored Jan 15, 2025
2 parents ecf281e + 6a695a4 commit bd137fa
Show file tree
Hide file tree
Showing 2 changed files with 153 additions and 28 deletions.
94 changes: 66 additions & 28 deletions decoders/network/lorawan-actility/v1.0.0/payload.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,32 @@
/* This is an example code for TTN Parser.
** TTN send several parameters to TagoIO. The job of this parse is to convert all these parameters into a TagoIO format.
** One of these parameters is the payload of your device. We find it too and apply the appropriate sensor parse.
**
** IMPORTANT: In most case, you will only need to edit the parsePayload function.
**
** Testing:
** You can do manual tests to this parse by using the Device Emulator. Copy and Paste the following code:
** [{ "variable": "ttn_payload", "value": "{ \"payload_raw\": \"0109611395\" }" }]
**
** The ignore_vars variable in this code should be used to ignore variables
** from the device that you don't want.
*/
** TTN send several parameters to TagoIO. The job of this parse is to convert all these parameters into a TagoIO format.
** One of these parameters is the payload of your device. We find it too and apply the appropriate sensor parse.
**
** IMPORTANT: In most case, you will only need to edit the parsePayload function.
**
** Testing:
** You can do manual tests to this parse by using the Device Emulator. Copy and Paste the following code:
** [{ "variable": "ttn_payload", "value": "{ \"payload_raw\": \"0109611395\" }" }]
**
** 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 = ['rf_chain', 'channel', 'modulation', 'app_id', 'time', 'gtw_trusted'];
const ignore_vars = [
"rf_chain",
"channel",
"modulation",
"app_id",
"time",
"gtw_trusted",
"processedFeed",
"rawPosition",
"coordinates",
"resolvedTracker",
"resolvedTrackerParameters",
"uplinkPayload",
"customerData",
];

/**
* Convert an object to TagoIO object format.
Expand All @@ -24,12 +38,12 @@ const ignore_vars = ['rf_chain', 'channel', 'modulation', 'app_id', 'time', 'gtw
* @param {String} serie Serie for the variables
* @param {String} prefix Add a prefix to the variables name
*/
function toTagoFormat(object_item, serie, prefix = '') {
function toTagoFormat(object_item, serie, prefix = "") {
const result = [];
for (const key in object_item) {
if (ignore_vars.includes(key) || object_item[key] === null) continue;

if (typeof object_item[key] === 'object') {
if (typeof object_item[key] === "object") {
result.push({
variable: object_item[key].variable || `${prefix}${key}`,
value: object_item[key].value,
Expand All @@ -51,7 +65,7 @@ function toTagoFormat(object_item, serie, prefix = '') {
}

// Just convert lat and lng, or latitude and longitude to TagoIO format.
function transformLatLngToLocation(fields, serie, prefix = '') {
function transformLatLngToLocation(fields, serie, prefix = "") {
if ((fields.latitude && fields.longitude) || (fields.lat && fields.lng)) {
const lat = fields.lat || fields.latitude;
const lng = fields.lng || fields.longitude;
Expand All @@ -77,20 +91,22 @@ function transformLatLngToLocation(fields, serie, prefix = '') {
function inspectFormat(object_item, serie, old_key) {
let result = [];
for (const key in object_item) {
if (key === 'lng'.toLowerCase() || key.toLowerCase() === 'longitude') continue;
else if (key === 'lat'.toLowerCase() || key.toLowerCase() === 'latitude') {
const lng = object_item.lng || object_item.longitude || object_item.Longitude;
if (key === "lng".toLowerCase() || key.toLowerCase() === "longitude")
continue;
else if (key === "lat".toLowerCase() || key.toLowerCase() === "latitude") {
const lng =
object_item.lng || object_item.longitude || object_item.Longitude;
result.push({
variable: old_key ? `${old_key}_location` : 'location',
variable: old_key ? `${old_key}_location` : "location",
value: `${object_item[key]}, ${lng}`,
location: { lat: Number(object_item[key]), lng: Number(lng) },
serie,
});
} else if (typeof object_item[key] === 'object') {
} else if (typeof object_item[key] === "object") {
result = result.concat(inspectFormat(object_item[key], serie, key));
} else {
result.push({
variable: old_key ? `${old_key}_${key}` :`${key}`,
variable: old_key ? `${old_key}_${key}` : `${key}`,
value: object_item[key],
serie,
});
Expand All @@ -103,7 +119,7 @@ function inspectFormat(object_item, serie, old_key) {
// Check if what is being stored is the ttn_payload.
// Payload is an environment variable. Is where what is being inserted to your device comes in.
// Payload always is an array of objects. [ { variable, value...}, {variable, value...} ...]
let ttn_payload = payload.find(x => x.variable === 'actility_payload');
let ttn_payload = payload.find((x) => x.variable === "actility_payload");
if (ttn_payload) {
// Get a unique serie for the incoming data.
const serie = String(ttn_payload.serie || new Date().getTime());
Expand All @@ -116,11 +132,32 @@ if (ttn_payload) {
delete ttn_payload.payload_hex;
}

if (ttn_payload.rawPosition?.floor_number) {
ttn_payload.floor_number = {
variable: "floor_number",
value: ttn_payload.rawPosition.floor_number,
};
}

if (ttn_payload.rawPosition?.room_name) {
ttn_payload.room_name = {
variable: "room_name",
value: ttn_payload.rawPosition.room_name,
};

// delete ttn_payload.resolvedTrackerParameters;
// delete ttn_payload.rawPosition;
// delete ttn_payload.uplinkPayload;
// delete ttn_payload.processedFeed;
// delete ttn_payload.coordinates;
// delete ttn_payload.resolvedTracker;
}

if (ttn_payload.DevLAT && ttn_payload.DevLON) {
ttn_payload.location = {
variable: "location",
ttn_payload.location = {
variable: "location",
value: `${ttn_payload.DevLAT},${ttn_payload.DevLON}`,
location: { lat: ttn_payload.DevLAT, lng: ttn_payload.DevLON }
location: { lat: ttn_payload.DevLAT, lng: ttn_payload.DevLON },
};
delete ttn_payload.DevLAT;
delete ttn_payload.DevLON;
Expand All @@ -134,8 +171,9 @@ if (ttn_payload) {
delete ttn_payload.DynamicClass;
delete ttn_payload.InstantPER;
delete ttn_payload.MeanPER;

payload = toTagoFormat(ttn_payload, serie);
if (ttn_payload.Time) {
payload = payload.map(x => ({ ...x, time: ttn_payload.Time }));
payload = payload.map((x) => ({ ...x, time: ttn_payload.Time }));
}
}
}
87 changes: 87 additions & 0 deletions decoders/network/lorawan-actility/v1.0.0/payload.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { readFileSync } from "fs";
import { join } from "path";
import * as ts from "typescript";
import { beforeEach, describe, expect, test } from "vitest";

import { DataToSend } from "@tago-io/sdk/lib/types";

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

let payload: DataToSend[] = [];

describe("Uplink 2 Actility Payload Validation", () => {
beforeEach(() => {
payload = [
{
variable: "actility_payload",
value:
'{"deviceEUI":"20635f039100011e","dxProfileId":"tpe-au1-api","customerId":"1100012666","modelCfg":"1:TPX_226d5d46-f3ae-44c9-8c37-5a8b9b3f21ff","userAccountId":"1100012666","realmId":"tpw-users-actility-tpe-ope","time":"2025-01-13T12:46:30.201000+00:00","coordinates":[103.84844229788555,1.3791139358924838,0],"overwriteCoordinates":true,"age":-1,"validityState":"NEW","horizontalAccuracy":2.618808467085686,"incomingSubscriberId":"1100012666","trackerType":"Micro Tracker V3.1","processedFeed":{"deviceProfileId":"ABEEWAY/MICRO","payloadEncoded":"0e48548daa0011000000004384bf000000004385be000000004388bc0000000051e8bb","sequenceNumber":10448,"receptionTime":"2025-01-13T12:46:30.201000+00:00","SF":7,"dynamicMotionState":"STATIC","temperatureMeasure":27.32941176470588,"processedPacket":{"baseStationId":"10002313","antennaCoordinates":[103.848991,1.378461],"SNR":8,"RSSI":-73.600006},"port":18},"rawPosition":{"rawPositionType":"RawPositionByBleSolver","coordinates":[103.84844149722993,1.379114189651386],"age":17,"horizontalAccuracy":8.333181988320284,"bssidCount":4,"beaconIdData":[{"rssi":-65,"beaconId":"00-00-00-00-43-84"},{"rssi":-66,"beaconId":"00-00-00-00-43-85"},{"rssi":-68,"beaconId":"00-00-00-00-43-88"},{"rssi":-69,"beaconId":"00-00-00-00-51-E8"}],"beaconFormat":"BEACON_ID","floor_number":4,"room_name":"S.439"},"resolvedTracker":{"firmwareVersion":"2.6.0","bleFirmwareVersion":"3.3.5","messageType":"EXTENDED_POSITION_MESSAGE","eventType":"UNKNOWN","shutdownCause":"UNKNOWN","trackingMode":"PERMANENT_TRACKING","dynamicMotionState":"STATIC","temperatureMeasure":27.32941176470588,"gpsScanMode":"UNKNOWN","sensorMode":"UNKNOWN","periodicPositionInterval":-1,"batteryLevel":84,"batteryStatus":"OPERATING","sosFlag":false,"activityCount":-1,"trackingUlPeriod":-1,"loralivePeriod":-1,"energyStatusPeriod":-1,"geolocSensorProfile":"UNKNOWN","oneshotGeolocMethod":"UNKNOWN","extAntennaProfile":"UNKNOWN","motionStartEndNbTx":-1,"gpsTimeout":-1,"xgpsTimeout":-1,"gpsEHPE":-1,"gpsConvergence":-1,"transmitStrat":"UNKNOWN","bleBeaconCount":-1,"bleBeaconTimeout":-1,"gpsStandbyTimeout":-1,"confirmedUlBitmap":-1,"confirmedUlRetry":-1,"motionSensitivity":-1,"shockDetection":-1,"periodicActivityPeriod":-1,"motionDuration":-1,"bleRssiFilter":-1,"temperatureHigh":-1,"temperatureLow":-1,"temperatureAction":"UNKNOWN","bleBond":"UNKNOWN","batteryCapacity":-1,"reedSwitchConfiguration":"UNKNOWN","collectionScanType":"UNKNOWN","collectionBLEFilterType":"UNKNOWN","collectionBLEFilterMain1":-1,"collectionBLEFilterMain2":-1,"collectionBLEFilterSecValue":-1,"collectionBLEFilterSecMask":-1,"collectionNbEntry":-1,"networkTimeoutCheck":-1,"networkTimeoutReset":-1},"uplinkPayload":{"messageType":"EXTENDED_POSITION_MESSAGE","age":17,"trackingMode":"PERMANENT_TRACKING","batteryLevel":84,"batteryStatus":"OPERATING","ackToken":10,"rawPositionType":"BLE_BEACON_SCAN_SHORT_ID","periodicPosition":false,"temperatureMeasure":27.3,"sosFlag":0,"appState":1,"dynamicMotionState":"STATIC","onDemand":false,"payload":"0e48548daa0011000000004384bf000000004385be000000004388bc0000000051e8bb","deviceConfiguration":{"mode":"PERMANENT_TRACKING"},"bleBeaconIds":[{"beaconId":"00-00-00-00-43-84","rssi":-65},{"beaconId":"00-00-00-00-43-85","rssi":-66},{"beaconId":"00-00-00-00-43-88","rssi":-68},{"beaconId":"00-00-00-00-51-e8","rssi":-69}]},"resolvedTrackerParameters":{"mode":"PERMANENT_TRACKING","firmwareVersion":"2.6.0","bleFirmwareVersion":"3.3.5","defaultProfile":"UNKNOWN","dynamicProfile":"Unknown"},"messageSource":"LORA","customerData":{"alr":{"pro":"ABEE/MICR","ver":"1"},"name":"MT 20635F039100011E"},"downlinkUrl":"https://thingparkenterprise.au.actility.com/iot-flow/downlinkMessages/da4466d6-083b-411a-89b8-5b5dde3b0d40"}',
group: "1736772392364",
},
];
});

test("Check all output variables for location tracking", () => {
eval(transpiledCode); // This runs the code and sets the payload variable

expect(payload).toEqual(
expect.arrayContaining([expect.objectContaining({ variable: "floor_number", value: 4 }), expect.objectContaining({ variable: "room_name", value: "S.439" })])
);
});
});

describe("Uplink 1 Actility Payload Validation", () => {
beforeEach(() => {
payload = [
{
variable: "actility_payload",
value:
'{"DevEUI":"20635F039100011E","FCntUp":10448,"FPort":18,"DevAddr":"004880D6","payload_hex":"0e48548daa0011000000004384bf000000004385be000000004388bc0000000051e8bb","FCntDn":209,"Time":"2025-01-13T12:46:30.201+00:00","LostUplinksAS":0,"ADRbit":1,"MType":2,"mic_hex":"294ae1fd","Lrcid":"00000233","LrrRSSI":-73.600006,"LrrSNR":8,"LrrESP":-74.23893,"SpFact":7,"SubBand":"G0","Channel":"LC5","Lrrid":"10002313","Late":0,"LrrLAT":1.378461,"LrrLON":103.848991,"Lrrs":{"Lrr":[{"Lrrid":"10002313","Chain":0,"LrrRSSI":-73.600006,"LrrSNR":8,"LrrESP":-74.23893}]},"DevLrrCnt":1,"CustomerID":"1100012666","CustomerData":{"loc":null,"alr":{"pro":"ABEE/MICR","ver":"1"},"tags":[],"doms":[],"name":"MT 20635F039100011E"},"BaseStationData":{"doms":[],"name":"7076ff-7076FF050E2D"},"ModelCfg":"0:2329,1:TPX_226d5d46-f3ae-44c9-8c37-5a8b9b3f21ff","DriverCfg":{"mod":{"pId":"abeeway","mId":"micro-tracker","ver":"2"},"app":{"pId":"abeeway","mId":"asset-tracker","ver":"2"},"id":"abeeway:asset-tracker:3"},"InstantPER":0,"MeanPER":0,"TxPower":4,"NbTrans":1,"Frequency":922.4,"DynamicClass":"A","payload":{"messageType":"EXTENDED_POSITION_MESSAGE","age":17,"trackingMode":"PERMANENT_TRACKING","batteryLevel":84,"batteryStatus":"OPERATING","ackToken":10,"rawPositionType":"BLE_BEACON_SCAN_SHORT_ID","periodicPosition":false,"temperatureMeasure":27.3,"sosFlag":0,"appState":1,"dynamicMotionState":"STATIC","onDemand":false,"payload":"0e48548daa0011000000004384bf000000004385be000000004388bc0000000051e8bb","deviceConfiguration":{"mode":"PERMANENT_TRACKING"},"bleBeaconIds":[{"beaconId":"00-00-00-00-43-84","rssi":-65},{"beaconId":"00-00-00-00-43-85","rssi":-66},{"beaconId":"00-00-00-00-43-88","rssi":-68},{"beaconId":"00-00-00-00-51-e8","rssi":-69}]},"points":{"batteryLevel":{"unitId":"%","record":84},"temperature":{"unitId":"Cel","record":27.3},"age":{"unitId":"s","record":17}},"downlinkUrl":"https://thingparkenterprise.au.actility.com/iot-flow/downlinkMessages/da4466d6-083b-411a-89b8-5b5dde3b0d40"}',
group: "1736772391524",
time: "2025-01-13T12:46:30.201+00:00",
},
];
});

test("Check all output variables for location tracking", () => {
eval(transpiledCode); // This runs the code and sets the payload variable

expect(payload).toEqual(
expect.arrayContaining([
expect.objectContaining({ variable: "fcntup", value: 10448 }),
expect.objectContaining({ variable: "fport", value: 18 }),
expect.objectContaining({ variable: "fcntdn", value: 209 }),
expect.objectContaining({ variable: "time", value: "2025-01-13T12:46:30.201+00:00" }),
expect.objectContaining({ variable: "lostuplinksas", value: 0 }),
expect.objectContaining({ variable: "adrbit", value: 1 }),
expect.objectContaining({ variable: "mtype", value: 2 }),
expect.objectContaining({ variable: "mic_hex", value: "294ae1fd" }),
expect.objectContaining({ variable: "lrcid", value: "00000233" }),
expect.objectContaining({ variable: "lrrrssi", value: -73.600006 }),
expect.objectContaining({ variable: "lrrsnr", value: 8 }),
expect.objectContaining({ variable: "lrresp", value: -74.23893 }),
expect.objectContaining({ variable: "spfact", value: 7 }),
expect.objectContaining({ variable: "subband", value: "G0" }),
expect.objectContaining({ variable: "channel", value: "LC5" }),
expect.objectContaining({ variable: "lrrid", value: "10002313" }),
expect.objectContaining({ variable: "late", value: 0 }),
expect.objectContaining({ variable: "lrrlat", value: 1.378461 }),
expect.objectContaining({ variable: "lrrlon", value: 103.848991 }),
expect.objectContaining({ variable: "devlrrcnt", value: 1 }),
expect.objectContaining({ variable: "BaseStationData" }),
expect.objectContaining({ variable: "modelcfg", value: "0:2329,1:TPX_226d5d46-f3ae-44c9-8c37-5a8b9b3f21ff" }),
expect.objectContaining({ variable: "DriverCfg" }),
expect.objectContaining({ variable: "txpower", value: 4 }),
expect.objectContaining({ variable: "nbtrans", value: 1 }),
expect.objectContaining({ variable: "frequency", value: 922.4 }),
expect.objectContaining({ variable: "payload", value: "0e48548daa0011000000004384bf000000004385be000000004388bc0000000051e8bb" }),
expect.objectContaining({ variable: "points" }),
expect.objectContaining({
variable: "downlinkurl",
value: "https://thingparkenterprise.au.actility.com/iot-flow/downlinkMessages/da4466d6-083b-411a-89b8-5b5dde3b0d40",
}),
])
);
});
});

0 comments on commit bd137fa

Please sign in to comment.