### Example Output: Multiple Sensors of Same Type Source: https://github.com/actility/device-catalog/blob/main/template/sample-vendor/drivers/README.md Illustrates how to handle multiple sensors of the same type, like temperature sensors. Sensor IDs should start from 0. ```json { "temperature:0": { "record": 31.4, "unitId": "Cel" }, "temperature:1": { "record": 31.2, "unitId": "Cel" }, "temperature:2": { "record": 32, "unitId": "Cel" } } ``` -------------------------------- ### Install NPM Dependencies Source: https://github.com/actility/device-catalog/blob/main/vendors/twtg/drivers/neon-vb-v4/README.md Run this command to install the necessary NPM dependencies for the project, including Jest for unit testing. ```bash npm install ``` -------------------------------- ### Install and Use Node.js 20 with NVM Source: https://github.com/actility/device-catalog/blob/main/template/sample-vendor/drivers/README.md Install and switch to Node.js version 20 using NVM. This is a prerequisite for running the tests. ```bash nvm install 20 nvm use 20 ``` -------------------------------- ### Example package.json Source: https://github.com/actility/device-catalog/blob/main/template/sample-vendor/drivers/README.md A sample package.json file generated after running 'npm init'. It outlines the project's metadata and scripts. ```json { "name": "driver", "version": "1.0.0", "description": "My driver", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "author": "", "license": "ISC" } ``` -------------------------------- ### Example Output: Simple Single Measurement Source: https://github.com/actility/device-catalog/blob/main/template/sample-vendor/drivers/README.md A basic example of a single measurement, such as temperature, with its record value and unit ID. ```json { "temperature": { "record": 31.4, "unitId": "Cel" } } ``` -------------------------------- ### Install Jest dependency Source: https://github.com/actility/device-catalog/blob/main/vendors/twtg/drivers/neon-ds-vb-xx-xx-usc/README.md Command to add the Jest testing framework as a development dependency to the project. ```bash npm install --save-dev jest ``` -------------------------------- ### Example Output: Multiple Temperature Readings Source: https://github.com/actility/device-catalog/blob/main/template/sample-vendor/drivers/README.md Demonstrates how to represent multiple temperature readings over time. Each record includes the event time and the measured value. ```json { "temperature": { "unitId": "Cel", "records": [ { "eventTime": "2019-01-01T10:00:00+01:00", "value": 31.4 }, { "eventTime": "2019-01-01T11:00:00+01:00", "value": 31.2 }, { "eventTime": "2019-01-01T12:00:00+01:00", "value": 32 } ] } } ``` -------------------------------- ### Check npm Version Source: https://github.com/actility/device-catalog/blob/main/template/sample-vendor/drivers/README.md Verify that npm is installed and check its version. Ensure npm version is greater than 5. ```sh $ npm -v ``` -------------------------------- ### Example Payload Structure Source: https://github.com/actility/device-catalog/blob/main/template/sample-vendor/drivers/README.md This JSON schema defines the structure for uplink/downlink examples used in the driver. It includes a description, type, payload in hexadecimal, LoRaWAN fPort, and the decoded data. ```json { "description": { "description": "the description of the uplink/downlink example", "type": "string", "required": true }, "type": { "description": "the type of the uplink/downlink example. type 'downlink' is used for both downlink decoding/encoding in case the function exist.", "type": "string", "enum": ["uplink", "downlink"], "required": true }, "bytes": { "description": "the uplink/downlink payload expressed in hexadecimal", "type": "string", "required": true }, "fPort": { "description": "the uplink/downlink message LoRaWAN fPort", "type": "number", "required": true }, "time": { "description": "the uplink/downlink message time", "type": "string", "format": "date-time", "required": false }, "data": { "description": "the decoded uplink/downlink view as an output", "type": "object", "required": true } } ``` -------------------------------- ### BACnet Mapping File Structure Example Source: https://github.com/actility/device-catalog/blob/main/mappings/template/how-to.md Illustrates the nested structure for defining device properties within a BACnet mapping CSV. Use ':' to separate nested paths. ```csv "payload": { "messageType": "EVENT", "onDemand": true, "deviceConfiguration": { "mode": "PERMANENT_TRACKING" }, "eventType": "GEOLOC_START" } ``` -------------------------------- ### Define Payload Test Examples Source: https://context7.com/actility/device-catalog/llms.txt Use this JSON structure in examples.json to define uplink and downlink test cases for automated validation. ```json [ { "type": "uplink", "description": "uplink containing temperature and humidity", "input": { "bytes": [0, 12, 68, 1, 12, 68], "fPort": 1, "recvTime": "2020-08-02T20:00:00.000+05:00" }, "output": { "data": { "temperature": 31.4, "humidity": 31.4 }, "errors": [], "warnings": [] } }, { "type": "downlink-encode", "description": "encode alarm configuration", "input": { "data": { "alarm": true, "pulseCounterThreshold": 10 } }, "output": { "bytes": [0, 10, 1, 1], "fPort": 16, "errors": [], "warnings": [] } }, { "type": "downlink-decode", "description": "decode alarm configuration", "input": { "bytes": [0, 10, 1, 1], "fPort": 16, "recvTime": "2020-08-02T20:00:00.000+05:00" }, "output": { "data": { "pulseCounterThreshold": 10, "alarm": true }, "errors": [], "warnings": [] } } ] ``` -------------------------------- ### Example Output: Single Timestamp Points Source: https://github.com/actility/device-catalog/blob/main/template/sample-vendor/drivers/README.md This JSON structure represents the expected output when all events occur at the same time, adhering to the ontology. It includes various sensor readings with their values and units. ```json { "temperature": { "record": 31.4, "unitId": "Cel" }, "location": { "unitId": "GPS", "records": [ { "value": [48.875158, 2.333822], "eventTime": "2019-01-01T10:00:00+01:00" } ] }, "power:0": { "record": 0.32, "unitId": "GW" }, "power:1": { "record": 0.33, "unitId": "GW" }, "power:2": { "record": 0.4523, "unitId": "GW" }, "power:3": { "record": 0.4456, "unitId": "GW" }, "power:4": { "record": 0.4356, "unitId": "GW" } } ``` -------------------------------- ### Example decoded payload output Source: https://github.com/actility/device-catalog/blob/main/template/sample-vendor/drivers/README.md The expected JSON structure resulting from a decoding process that utilizes context history. ```json { "temperature": 23.3, "temperatureHistory": [ { "temperature": 23.3, "time": "2024-08-12T15:24:24.249Z" }, { "temperature": 23.2, "time": "2024-08-12T15:26:24.249Z" }, { "temperature": 22.9, "time": "2024-08-12T15:28:24.249Z" }, { "temperature": 23.1, "time": "2024-08-12T15:30:24.249Z" } ] } ``` -------------------------------- ### Execute Driver Tests Source: https://github.com/actility/device-catalog/blob/main/template/sample-vendor/drivers/README.md Run tests for your driver using Jest to ensure functionality and get a coverage report. The 'stmts' value indicates statement coverage. ```shell npm run test ``` -------------------------------- ### Uplink/Downlink Decode JSON Schema Source: https://github.com/actility/device-catalog/blob/main/template/sample-vendor/drivers/README.md Defines the structure for uplink and downlink decode examples, including input parameters like payload bytes and fPort, and output fields for decoded data, errors, or warnings. ```json { "description": { "description": "the description of the uplink/downlink example", "type": "string", "required": true }, "type": { "description": "the type of the uplink/downlink example", "type": "string", "enum": ["uplink", "downlink-decode"], "required": true }, "input": { "type": "Object", "items": { "bytes": { "description": "the uplink/downlink payload expressed in hexadecimal", "type": "string", "required": true }, "fPort": { "description": "the uplink/downlink message LoRaWAN fPort", "type": "number", "required": true }, "recvTime": { "description": "the uplink/downlink message time", "type": "string", "format": "date-time", "required": true } } }, "output": { "type": "Object", "items": { "data": { "description": "The open JavaScript object representing the decoded payload when no error is encountered while decoding. required if success.", "type": "Object", "required": false }, "errors": { "description": "A list of error messages while decoding the provided payload. required if failed.", "type": "array", "items": { "type": "string" }, "required": false }, "warnings": { "description": "A list of warning messages that do not prevent the codec from decoding the payload. optional.", "type": "array", "items": { "type": "string" }, "required": false } } } } ``` -------------------------------- ### Downlink Encode JSON Schema Source: https://github.com/actility/device-catalog/blob/main/template/sample-vendor/drivers/README.md Defines the structure for downlink encode examples, including input data for encoding and output fields for the encoded payload bytes, fPort, errors, or warnings. ```json { "description": { "description": "the description of the downlinn encode example", "type": "string", "required": true }, "type": { "description": "the type of the uplink/downlink example", "type": "string", "enum": ["downlink-encode"], "required": true }, "input": { "type": "Object", "items": { "data": { "description": "The open JavaScript object representing the decoded payload when no error is encountered while decoding. required if success.", "type": "Object", "required": false } } }, "output": { "type": "Object", "items": { "bytes": { "description": "the downlink encoded payload expressed in hexadecimal. required if success.", "type": "string", "required": false }, "fPort": { "description": "the downlink message LoRaWAN fPort", "type": "number", "required": true }, "errors": { "description": "A list of error messages while encoding the provided payload. required if failed.", "type": "array", "items": { "type": "string" }, "required": false }, "warnings": { "description": "A list of warning messages that do not prevent the codec from encoding the payload. optional.", "type": "array", "items": { "type": "string" }, "required": false } } } } ``` -------------------------------- ### Initialize npm Project Source: https://github.com/actility/device-catalog/blob/main/template/sample-vendor/drivers/README.md Create a new npm project from an empty directory. This command prompts for project details and generates a package.json file. ```sh $ npm init ``` -------------------------------- ### Execute Driver Tests Source: https://context7.com/actility/device-catalog/llms.txt Commands to initialize the environment, run tests, and package the driver for distribution. ```bash # Install Node.js 20 (required version) nvm install 20 nvm use 20 # Initialize and install dependencies npm init npm install # Run tests with coverage report npm run test # Create distributable package npm pack ``` -------------------------------- ### Configure Device Model Source: https://context7.com/actility/device-catalog/llms.txt Define hardware specifications, sensor ontology, and protocol links in a model.yaml file. ```yaml # vendors/sample-vendor/models/sample-model/model.yaml name: Sample Model description: This a description of the functionalities of the sample model. logo: sample-model.png deviceProfileIds: - id: svendor_RFGroup1_1.0.2revB_classA lorawanDeviceProfileID: '00010001' - id: svendor_RFGroup2_1.0.2revB_classA lorawanDeviceProfileID: '00010002' # Model identification - format: :: modelId: svendor:sample-model:1 # Protocol link to driver - format: :: protocolId: - svendor:sample-model-protocol:1 # Sensor ontology - format: : sensors: - temperature:Cel - humidity:%RH # LoRaWAN Relay settings LoRaWANRelay: canOperateAsARelay: false defaultRelayActivation: false ``` -------------------------------- ### Package Driver Source: https://github.com/actility/device-catalog/blob/main/template/sample-vendor/drivers/README.md Create an npm package file with a .tgz extension containing the complete driver. This command should be run in the project's root directory. ```sh npm pack ``` -------------------------------- ### Execute unit tests Source: https://github.com/actility/device-catalog/blob/main/vendors/twtg/drivers/neon-ds-vb-xx-xx-usc/README.md Command to run the configured test suite for the driver. ```bash npm test ``` -------------------------------- ### Define Vendor Information Source: https://context7.com/actility/device-catalog/llms.txt Create a vendor.yaml file in the vendor root directory to register company details and LoRa Alliance information. ```yaml # vendors/sample-vendor/vendor.yaml id: svendor name: Sample Vendor description: This vendor offers LoRaWAN devices for the Internet of Things. loRaAllianceVendorID: '000E' logo: logo.png websiteURL: https://www.template-vendor.com/ marketplaceURL: https://www.template-vendor.com/products/ contact: support@vendor.com ``` -------------------------------- ### Configure Driver Metadata Source: https://context7.com/actility/device-catalog/llms.txt Link protocols to device models and define codec signature types in driver.yaml. ```yaml # vendors/sample-vendor/drivers/sample-driver/driver.yaml name: Driver Template description: This is a codec that follows the LoRaWAN Code API standard. producerId: actility developerEmail: developer@vendor.com # Protocol link - must match protocolId in model.yaml protocolId: sample-vendor:sample-model-protocol:1 # Signature type: actility, lora-alliance, ttn, chirpstack signature: # Enable context persistence across uplinks (DevEUI-based) useContext: true ``` -------------------------------- ### Enable Context in driver.yaml Source: https://github.com/actility/device-catalog/blob/main/template/sample-vendor/drivers/README.md To enable context persistence for drivers, set 'useContext' to true in the driver.yaml file. Context is based on DevEUI and is limited to 2 days if not updated. ```yaml # When a driver uses a context, this field must be set to true ``` -------------------------------- ### Configure NPM Package Source: https://context7.com/actility/device-catalog/llms.txt Define the package.json file to manage driver dependencies and test scripts. ```json { "name": "sample-model-driver", "version": "1.0.0", "description": "LoRa Alliance sample driver npm packaged", "main": "index.js", "scripts": { "test": "jest --collectCoverage" }, "devDependencies": { "fs-extra": "^11.2.0", "isolated-vm": "^4.7.2", "jest": "^29.7.0", "js-yaml": "^4.1.0", "path": "^0.12.7" } } ``` -------------------------------- ### Store context in a driver Source: https://github.com/actility/device-catalog/blob/main/template/sample-vendor/drivers/README.md Use context.push() to inject data into the driver context during the decoding process. ```javascript function decodeUplink(input){ const raw = Buffer.from(input.bytes); const temperature = raw.readInt16BE(1)/100; context.push({ time: input.recvTime, currentValue: temperature }); ... } ``` -------------------------------- ### Persist Data with Context Storage Source: https://context7.com/actility/device-catalog/llms.txt Utilize the context array to maintain state across uplinks. Ensure `useContext: true` is set in the driver configuration. ```javascript // Store temperature history across multiple uplinks function decodeUplink(input) { let result = { data: {}, errors: [], warnings: [] }; const raw = Buffer.from(input.bytes); const temperature = raw.readInt16BE(1) / 100; // Store current reading in context (persisted per DevEUI) context.push({ temperature: temperature, time: input.recvTime }); // Calculate average from historical context if (context.length > 1) { const latestContext = context.shift(); const previousTemperature = latestContext.temperature; result.data.averageTemperature = (temperature + previousTemperature) / 2; } result.data.temperature = temperature; result.data.temperatureHistory = context; return result; } // Output with history: // { // "temperature": 23.3, // "temperatureHistory": [ // { "temperature": 23.3, "time": "2024-08-12T15:24:24.249Z" }, // { "temperature": 23.2, "time": "2024-08-12T15:26:24.249Z" }, // { "temperature": 22.9, "time": "2024-08-12T15:28:24.249Z" } // ] // } ``` -------------------------------- ### Webpack Configuration for Driver Source: https://github.com/actility/device-catalog/blob/main/template/sample-vendor/drivers/README.md Configuration for Webpack to bundle multiple JavaScript files into a single file for the driver. Ensure 'path' is imported. ```javascript module.exports = { target: "node", mode: "production", entry: "./index.js", output: { filename: "main.js", path: path.resolve(__dirname, "."), library: "driver", }, } ``` -------------------------------- ### Add Dev Dependencies to package.json Source: https://github.com/actility/device-catalog/blob/main/template/sample-vendor/drivers/README.md Adds necessary development dependencies for testing and building the driver, including `fs-extra`, `isolated-vm`, `jest`, `js-yaml`, and `path`. ```json "devDependencies": { "fs-extra": "^11.2.0", "isolated-vm": "^4.7.2", "jest": "^29.7.0", "js-yaml": "^4.1.0", "path": "^0.12.7" } ``` -------------------------------- ### Helper Function for Reading Signed Short Source: https://github.com/actility/device-catalog/blob/main/template/sample-vendor/drivers/README.md A utility function to read a 16-bit signed integer from a buffer. This function should be added to index.js. ```javascript function readShort(bin) { var result = bin & 0xffff; if (0x8000 & result) { result = -(0x010000 - result); } return result; } ``` -------------------------------- ### Configure BACnet Mapping Source: https://context7.com/actility/device-catalog/llms.txt Define CSV mapping files to integrate LoRaWAN drivers with BACnet/Modbus systems. ```csv version,1.0 max_downlink_commands,5 #models name,selector all_models,{mId: tempo},{mId: tempo-v2} tempo_only,{mId: tempo} #objects id,name,value,type,access_mode,value_type,precision,units,cov_increment,min_value,max_value,models,description temperature,Temperature,0,number,R,FLOAT,2,Cel,0.1,,-40,85,all_models,Current temperature reading setpoint,Temperature Setpoint,20,number,RW,FLOAT,1,Cel,0.5,10,30,all_models,Target temperature alarm,Alarm State,false,boolean,R,BOOL,,,,,,all_models,Alarm active indicator deviceConfiguration:mode,Operating Mode,STANDARD,string,W,STRING,,,,,,tempo_only,Device mode configuration ``` -------------------------------- ### Reset Device Source: https://github.com/actility/device-catalog/blob/main/vendors/ellenex/drivers/pls2-l/README.md Use this JSON script to trigger an immediate reset of the Ellenex PLS2-L device. ```json { "command": 3, "data": { "reset": true } } ``` -------------------------------- ### Change Sampling Rate to Minutes Source: https://github.com/actility/device-catalog/blob/main/vendors/ellenex/drivers/pls2-l/README.md Use this JSON script to set the device's sampling rate in minutes. Modify the 'time' field to adjust the interval. The minimum supported rate is 60 seconds. ```json { "command": 1, "data": { "unit": "minute", "time": 180 } } ``` -------------------------------- ### Change Sampling Rate to Seconds Source: https://github.com/actility/device-catalog/blob/main/vendors/ellenex/drivers/pls2-l/README.md Use this JSON script to set the device's sampling rate in seconds. Modify the 'time' field to adjust the interval. The minimum supported rate is 60 seconds. ```json { "command": 1, "data": { "unit": "second", "time": 180 } } ``` -------------------------------- ### Reload context in a driver Source: https://github.com/actility/device-catalog/blob/main/template/sample-vendor/drivers/README.md Retrieve previously saved data using context.shift() for the latest entry or index-based access for specific entries. ```javascript function decodeUplink(input){ const latestContext = context.shift(); const latestTemperature = latestContext["currentValue"]; const raw = Buffer.from(input.bytes); const temperature = raw.readInt16BE(1)/100; const averageTwoMeasures = (temperature + latestTemperature) / 2; ... } ``` -------------------------------- ### Enable Device Confirmation Source: https://github.com/actility/device-catalog/blob/main/vendors/ellenex/drivers/pls2-l/README.md This JSON script enables the confirmation feature on the device. This setting controls whether the device sends acknowledgments for received messages. ```json { "command": 2, "data": { "confirmation": true } } ``` -------------------------------- ### Set Auto-Reset Interval Source: https://github.com/actility/device-catalog/blob/main/vendors/ellenex/drivers/pls2-l/README.md Configure the device to reset itself after sending a specified number of samples. Change the 'count' value to set the interval. Setting 'count' to 0 disables auto-reset. Note potential behavior changes for OTA and ABP devices. ```json { "command": 4, "data": { "count": 3000 } } ``` -------------------------------- ### Export Driver Functions Source: https://github.com/actility/device-catalog/blob/main/template/sample-vendor/drivers/README.md Exports the `decodeUplink`, `decodeDownlink`, and `encodeDownlink` functions from the `index.js` file, making them available for testing or external use. ```javascript exports.decodeUplink = decodeUplink; exports.decodeDownlink = decodeDownlink; exports.encodeDownlink = encodeDownlink; ``` -------------------------------- ### Transform Data with Points Extraction Source: https://context7.com/actility/device-catalog/llms.txt Map decoded uplink data into a standardized ontology format using the extractPoints function. ```javascript // Extract points with ontology units from decoded uplink function extractPoints(input) { return { // Single measurement point temperature: { record: input.message.temperature, unitId: "Cel" }, humidity: { record: input.message.humidity, unitId: "%RH" }, // GPS location point location: { unitId: "GPS", records: [ { value: [48.875158, 2.333822], eventTime: input.time } ] }, // Multiple sensors of same type (indexed from 0) "power:0": { record: 0.32, unitId: "W" }, "power:1": { record: 0.33, unitId: "W" }, // Time-series measurements temperature_history: { unitId: "Cel", records: [ { eventTime: "2019-01-01T10:00:00+01:00", value: 31.4 }, { eventTime: "2019-01-01T11:00:00+01:00", value: 31.2 }, { eventTime: "2019-01-01T12:00:00+01:00", value: 32 } ] } }; } ``` -------------------------------- ### Define LoRaWAN Device Profile Source: https://context7.com/actility/device-catalog/llms.txt Specify network characteristics, MAC version, and regional parameters in a profile YAML file. ```yaml # vendors/sample-vendor/profiles/vendor_RFGroup1_1.0.2b_classA.yaml id: svendor_RFGroup1_1.0.2b_classA loRaWANClass: A ISMbands: eu868 motionIndicator: RANDOM mac: activationByPersonalization: true overTheAirActivation: false loRaWANMacVersion: 1.0.2 regionalParameterVersion: 1.0.2revB devicesTxPowerCapabilities: minTxPower: 2 maxTxPower: 18 minTxEIRP: 2 maxTxEIRP: 18 bootSettings: rx1Delay: 1000 downlinkDwellTime: 1 uplinkDwellTime: 1 supportedMACCommands: linkADRReqAns: true devStatusReqAns: true joinRequestAccept: true dutyCycleReqAns: true linkCheckReqAns: true rxParamSetupReqAns: true rxTimingSetupReqAns: true newChannelReqAns: true dlChannelReqAns: true txParamSetupReqAns: true deviceTimeReqAns: true ``` -------------------------------- ### Add Test Script to package.json Source: https://github.com/actility/device-catalog/blob/main/template/sample-vendor/drivers/README.md Configures the `test` script in `package.json` to run Jest tests and collect code coverage. This script is used to automate the testing process for the driver. ```json "scripts": { "test": "jest --collectCoverage" } ``` -------------------------------- ### Output Schema: Multiple Timestamps Source: https://github.com/actility/device-catalog/blob/main/template/sample-vendor/drivers/README.md This JSON schema defines the structure for points with multiple values recorded at different timestamps. It uses an array of objects, each containing an event time and a value. ```json { "type": "object", "additionalProperties": { "type": "array", "items": { "type": "object", "properties": { "eventTime": { "type": "string", "format": "date-time", "required": true }, "value": { "type": [ "string", "number", "boolean" ], "required": true } } } } } ``` -------------------------------- ### Update and reload context Source: https://github.com/actility/device-catalog/blob/main/template/sample-vendor/drivers/README.md Update the context object and assign it to the result object to persist history across multiple decode/encode cycles. ```javascript function decodeUplink(input){ const raw = Buffer.from(input.bytes); const temperature = raw.readInt16BE(1)/100; context.push({ temperature: temperature, time: input.recvTime }); result.temperatureHistory = context; ... } ``` -------------------------------- ### Disable Auto-Reset Source: https://github.com/actility/device-catalog/blob/main/vendors/ellenex/drivers/pls2-l/README.md This JSON script disables the periodic auto-reset functionality of the device by setting the sample count to 0. ```json { "command": 4, "data": { "count": 0 } } ``` -------------------------------- ### Implement Downlink Decode Function Source: https://context7.com/actility/device-catalog/llms.txt Use this function to decode downlink payloads for monitoring. The function must return an object containing data, errors, and warnings arrays. ```javascript // vendors/sample-vendor/drivers/sample-driver/index.js function decodeDownlink(input) { let result = { data: {}, errors: [], warnings: [] }; const raw = Buffer.from(input.bytes); if (raw.byteLength > 4) { result.errors.push("Invalid downlink payload: length exceeds 4 bytes"); delete result.data; return result; } for (let i = 0; i < raw.byteLength; i += 2) { switch (raw[i]) { // Pulse counter threshold (ID: 0x00) case 0x00: if (raw.byteLength < i + 2) { result.errors.push("Invalid downlink payload: index out of bounds"); delete result.data; return result; } result.data.pulseCounterThreshold = raw.readUInt8(i + 1); break; // Alarm flag (ID: 0x01) case 0x01: if (raw.byteLength < i + 2) { result.errors.push("Invalid downlink payload: index out of bounds"); delete result.data; return result; } result.data.alarm = raw.readUInt8(i + 1) === 1; break; default: result.errors.push("Invalid downlink payload: unknown id '" + raw[i] + "'"); delete result.data; return result; } } return result; } // Input example: // { bytes: [0, 10, 1, 1], fPort: 16, recvTime: "2020-08-02T20:00:00.000Z" } // Output: // { data: { pulseCounterThreshold: 10, alarm: true }, errors: [], warnings: [] } exports.decodeDownlink = decodeDownlink; ``` -------------------------------- ### Encode Downlink Command Source: https://context7.com/actility/device-catalog/llms.txt Implement `encodeDownlink(input)` to convert JSON commands to binary payloads for device configuration. Returns bytes array, fPort, and optional errors. Supports encoding pulse counter threshold and alarm flag. ```javascript // vendors/sample-vendor/drivers/sample-driver/index.js function encodeDownlink(input) { let result = { errors: [], warnings: [] }; let raw = new Buffer(4); let index = 0; // Encode pulse counter threshold (ID: 0x00, 1 byte value) if (typeof input.data.pulseCounterThreshold !== "undefined") { if (input.data.pulseCounterThreshold > 255) { result.errors.push("Invalid downlink: pulseCounterThreshold cannot exceed 255"); delete result.data; return result; } raw.writeUInt8(0, index); index += 1; raw.writeUInt8(input.data.pulseCounterThreshold, index); index += 1; } // Encode alarm flag (ID: 0x01, 1 byte boolean) if (typeof input.data.alarm !== "undefined") { raw.writeUInt8(1, index); index += 1; raw.writeUInt8(input.data.alarm === true ? 1 : 0, index); index += 1; } result.bytes = Array.from(raw).slice(0, index); result.fPort = 16; return result; } // Input example: // { data: { pulseCounterThreshold: 10, alarm: true } } // Output: // { bytes: [0, 10, 1, 1], fPort: 16, errors: [], warnings: [] } exports.encodeDownlink = encodeDownlink; ``` -------------------------------- ### Define JSON Validation Schema Source: https://context7.com/actility/device-catalog/llms.txt Create uplink and downlink schemas using JSON Schema draft-07 to validate device data structures. ```json { "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", "properties": { "temperature": { "type": "number" }, "humidity": { "type": "number" }, "pulseCounter": { "type": "integer" }, "volumes": { "type": "array", "items": { "type": "object", "properties": { "time": { "type": "string" }, "volume": { "type": "integer" } }, "required": ["time", "volume"] } } }, "additionalProperties": false } ``` -------------------------------- ### Output Schema for decodeUplink Source: https://github.com/actility/device-catalog/blob/main/template/sample-vendor/drivers/README.md Defines the structure of the output object returned by the `decodeUplink` function, including decoded data, errors, and warnings. ```json { "data": { "description": "The open JavaScript object representing the decoded payload when no error is encountered while decoding. required if success.", "type": "Object", "required": false }, "errors": { "description": "A list of error messages while decoding the provided payload. required if failed.", "type": "array", "items": { "type": "string" }, "required": false }, "warnings": { "description": "A list of warning messages that do not prevent the codec from decoding the payload. optional.", "type": "array", "items": { "type": "string" }, "required": false } } ``` -------------------------------- ### Input Schema for decodeUplink Source: https://github.com/actility/device-catalog/blob/main/template/sample-vendor/drivers/README.md Defines the structure of the input object for the `decodeUplink` function, including payload bytes, fPort, and reception time. ```json { "bytes": { "description": "The uplink payload byte array, where each byte is represented by an integer between 0 and 255", "type": "array", "items": { "type": "integer" }, "required": true }, "fPort": { "description": "The uplink message LoRaWAN fPort", "type": "integer", "required": true }, "recvTime": { "description": "The uplink message datetime recorded by the LoRaWAN network server as a JavaScript Date object", "type": "string", "format": "date-time", "required": true } } ``` -------------------------------- ### Input Schema for extractPoints Source: https://github.com/actility/device-catalog/blob/main/template/sample-vendor/drivers/README.md The input object for the extractPoints function is defined by this JSON schema. It includes the decoded message and the uplink's timestamp. ```json { "message": { "description": "the object message as returned by the decodeUplink function", "type": "object", "required": true }, "time": { "description": "the datetime of the uplink message, it is a real javascript Date object", "type": "string", "format": "date-time", "required": true } } ``` -------------------------------- ### Decode Downlink Function Source: https://github.com/actility/device-catalog/blob/main/template/sample-vendor/drivers/README.md This optional function can be implemented to ease monitoring and logs of sent downlinks. It takes an input object and returns an output object with data, errors, or warnings. ```javascript function decodeDownlink(input) { ... return output; } ``` -------------------------------- ### Uplink Data JSON Schema Source: https://github.com/actility/device-catalog/blob/main/template/sample-vendor/drivers/README.md Define the structure of the decoded uplink data using this JSON schema. It specifies expected properties like temperature, humidity, pulseCounter, and volumes. ```json { "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", "properties": { "temperature": { "type": "number" }, "humidity": { "type": "number" }, "pulseCounter": { "type": "number" }, "volumes": { "type": "array", "items": [ { "type": "object", "properties": { "time": { "type": "string" }, "volume": { "type": "integer" } }, "required": [ "time", "volume" ] } ] } }, "additionalProperties": false } ``` -------------------------------- ### Encode Downlink Function Source: https://github.com/actility/device-catalog/blob/main/template/sample-vendor/drivers/README.md Implement this function when a device supports downlinks. It takes an input object and returns an output object containing fPort, bytes, errors, or warnings. ```javascript function encodeDownlink(input) { ... return output; } ``` -------------------------------- ### Encode Downlink Function for BACnet Adapter Source: https://github.com/actility/device-catalog/blob/main/mappings/template/how-to.md This JavaScript function encodes downlink data for BACnet adapters. It handles different input keys to construct a byte array and specifies the FPort for LoRaWAN transmission. Ensure the input object has a 'data' property or uses the input directly. ```javascript function encodeDownlink(input) { var bytes = []; var key, i; if(!input.hasOwnProperty('data')){ input.data = input } for (key in input.data) { if (input.data.hasOwnProperty(key)) { switch (key) { case "setKeepAlive": bytes.push(0x02); bytes.push(input.data.setKeepAlive); break; ... return { bytes: bytes, fPort: 1, warnings: [], errors: [] } } exports.encodeDownlink = encodeDownlink; ``` -------------------------------- ### Define extractPoints Function Source: https://github.com/actility/device-catalog/blob/main/template/sample-vendor/drivers/README.md Implement this JavaScript function to extract data points from decoded uplink messages. The function receives an input object containing the message and its timestamp. ```javascript function extractPoints(input) {...} ``` -------------------------------- ### decodeUplink Function Source: https://github.com/actility/device-catalog/blob/main/template/sample-vendor/drivers/README.md The mandatory function used to decode uplink payloads from devices within the Actility framework. ```APIDOC ## decodeUplink(input) ### Description This function is mandatory for all device drivers. It processes the raw uplink payload and returns a decoded object or error messages. ### Parameters #### Request Body (input object) - **bytes** (array) - Required - The uplink payload byte array, where each byte is represented by an integer between 0 and 255. - **fPort** (integer) - Required - The uplink message LoRaWAN fPort. - **recvTime** (string) - Required - The uplink message datetime recorded by the LoRaWAN network server as a JavaScript Date object. ### Response #### Success Response (200) - **data** (Object) - Optional - The open JavaScript object representing the decoded payload when no error is encountered. - **errors** (array) - Optional - A list of error messages while decoding the provided payload. - **warnings** (array) - Optional - A list of warning messages that do not prevent the codec from decoding the payload. ``` -------------------------------- ### Downlink Data JSON Schema Source: https://github.com/actility/device-catalog/blob/main/template/sample-vendor/drivers/README.md Define the structure of the decoded downlink data using this JSON schema. It specifies expected properties like pulseCounterThreshold and alarm. ```json { "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", "properties": { "pulseCounterThreshold": { "type": "integer" }, "alarm": { "type": "boolean" } }, "additionalProperties": false } ``` -------------------------------- ### Disable Device Confirmation Source: https://github.com/actility/device-catalog/blob/main/vendors/ellenex/drivers/pls2-l/README.md This JSON script disables the confirmation feature on the device. This setting controls whether the device sends acknowledgments for received messages. ```json { "command": 2, "data": { "confirmation": false } } ``` -------------------------------- ### Decode Uplink Payload Source: https://context7.com/actility/device-catalog/llms.txt Implement the mandatory `decodeUplink(input)` function to convert binary payloads to JSON. The function receives bytes, fPort, and recvTime, returning decoded data with optional errors and warnings. Handles temperature, humidity, and pulse counter data types. ```javascript // vendors/sample-vendor/drivers/sample-driver/index.js function decodeUplink(input) { let result = { data: {}, errors: [], warnings: [] }; const raw = Buffer.from(input.bytes); // Validate payload length if (raw.byteLength > 8) { result.errors.push("Invalid uplink payload: length exceeds 8 bytes"); delete result.data; return result; } for (let i = 0; i < raw.byteLength; i++) { switch (raw[i]) { // Temperature - 2 bytes (ID: 0x00) case 0x00: if (raw.byteLength < i + 3) { result.errors.push("Invalid uplink payload: index out of bounds when reading temperature"); delete result.data; return result; } result.data.temperature = raw.readInt16BE(i + 1) / 100; i += 2; break; // Humidity - 2 bytes (ID: 0x01) case 0x01: if (raw.byteLength < i + 3) { result.errors.push("Invalid uplink payload: index out of bounds when reading humidity"); delete result.data; return result; } result.data.humidity = raw.readInt16BE(i + 1) / 100; i += 2; break; // Pulse counter - 1 byte (ID: 0x02) case 0x02: result.data.pulseCounter = raw.readInt8(i + 1); i += 1; break; default: result.errors.push("Invalid uplink payload: unknown id '" + raw[i] + "'ப்புகளில்"); delete result.data; return result; } } return result; } // Input example: // { bytes: [0, 12, 68, 1, 12, 68, 2, 12], fPort: 1, recvTime: "2020-08-02T20:00:00.000Z" } // Output: // { data: { temperature: 31.4, humidity: 31.4, pulseCounter: 12 }, errors: [], warnings: [] } exports.decodeUplink = decodeUplink; ``` -------------------------------- ### Uplink Decode Function Source: https://github.com/actility/device-catalog/blob/main/template/sample-vendor/drivers/README.md Implements the uplink decoding logic for parsing sensor data from a byte payload. Handles temperature, humidity, and pulse counter values. ```javascript function decodeUplink(input) { let result = { data: {}, errors: [], warnings: [] }; const raw = Buffer.from(input.bytes); if (raw.byteLength > 8) { result.errors.push("Invalid uplink payload: length exceeds 8 bytes"); delete result.data; return result; } for (i = 0; i < raw.byteLength; i++) { switch (raw[i]) { // Temperature - 2 bytes case 0x00: if (raw.byteLength < i + 3) { result.errors.push("Invalid uplink payload: index out of bounds when reading temperature"); delete result.data; return result; } result.data.temperature = raw.readInt16BE(i+1)/100; if(result.data.temperature > 40){ result.warnings = ["temperature exceeded the threshold of 40 degrees."]; } i += 2; break; // Humidity - 2 bytes case 0x01: if (raw.byteLength < i + 3) { result.errors.push("Invalid uplink payload: index out of bounds when reading humidity"); delete result.data; return result; } result.data.humidity = raw.readInt16BE(i+1)/100; i += 2; break; // Pulse counter - 1 byte case 0x02: result.data.pulseCounter = raw.readInt8(i+1); i += 1; break; default: result.errors.push("Invalid uplink payload: unknown id '" + raw[i] + "'"); delete result.data; return result; } } return result; } ``` -------------------------------- ### Downlink Decode Input Schema Source: https://github.com/actility/device-catalog/blob/main/template/sample-vendor/drivers/README.md The input for the decodeDownlink function is an object provided by the LoRaWAN framework, containing bytes, fPort, and recvTime. ```json { "bytes": { "description": "The downlink payload byte array, where each byte is represented by an integer between 0 and 255", "type": "array", "items": { "type": "integer" }, "required": true }, "fPort": { "description": "The downlink message LoRaWAN fPort", "type": "integer", "required": true }, "recvTime": { "description": "The downlink message datetime computed by the LoRaWAN platform as a JavaScript Date object", "type": "string", "format": "date-time", "required": true } } ``` -------------------------------- ### Encode Downlink Data Source: https://github.com/actility/device-catalog/blob/main/template/sample-vendor/drivers/README.md Encodes specific data fields like `pulseCounterThreshold` and `alarm` into a byte array for downlink transmission. Ensure `pulseCounterThreshold` does not exceed 255. Sets the `fPort` to 16. ```javascript function encodeDownlink(input) { let result = { errors: [], warnings: [] }; let raw = new Buffer(4); let index = 0; if (typeof input.data.pulseCounterThreshold !== "undefined") { if (input.data.pulseCounterThreshold > 255) { result.errors.push("Invalid downlink: pulseCounterThreshold cannot exceed 255"); delete result.data; return result; } raw.writeUInt8(0,index); index+=1; raw.writeUInt8(input.data.pulseCounterThreshold, index); index+=1; } if (typeof input.data.alarm !== "undefined") { raw.writeUInt8(1, index); index+=1; raw.writeUInt8(input.data.alarm === true? 1 : 0, index); index+=1; } result.bytes = Array.from(raw).slice(0,index); result.fPort = 16; return result; } ```