In the following tutorial, we connect a Dragino LDS02 LoRaWAN Door Sensor to the platform via KPN Things.
You can also find the supporting material to help you kick-start the integration.
typeJsonObject= { [key:string]:Json };constkpnDeviceTypes= { dragino: { lds01:"dragino-lds01", },};constdeviceTypeHashIds= { dragino: { lds01:"TO DO", },};functionhandle(args:Arguments):Result {constbody=JSON.parse(JSON.stringify(args.request.body?.data));let deviceTypeHashId;switch (args.request.headers["device-type"]) {casekpnDeviceTypes.dragino.lds01: deviceTypeHashId =deviceTypeHashIds.dragino.lds01;break;default:thrownewError("Device type hash id is not available "+args.request.headers["device-type"] ); }constdeviceIdentifier= body[0].bn.substring(15,31);if (typeof deviceIdentifier !=="string") {thrownewError("Device identifier not found!"); }return { deviceTypeHashId, deviceIdentifier, };}/** * Validate request. * * @throws Throws an error when the request is invalid. */functionvalidateRequest(request:WebRequest, data:JsonObject) {if (request.body?.type !=="json") {thrownewError("Body must be JSON."); }}
constdoorReportTypeHashId:string="TO DO";interfaceLoraData { payload:string; port:number;}interfaceGenericPayloadResponse { data:LoraData;}/** * Handle is the required function. It has two overloads, one for incoming HTTP requests and one for internal events * When this function is called for an incoming HTTP requests, the function should return information about the response * that should be returned. */functionhandle( args:ArgumentsForIncomingRequestEvent, exec:Exec):IncomingRequestResponse;functionhandle(args:ArgumentsForEventWithoutResponse, exec:Exec):void;functionhandle(args:Arguments, exec:Exec):IncomingRequestResponse|void {if (args.event.type ==="incomingRequest") {constrequest=args.event.webRequest;if (request.method ==="POST"&&request.url.path ==="/") {if (request.body ===undefined||request.body.type !=="json") {return { statusCode:400, body: ["parameter_error","Body is not of type JSON"], }; }// Set receive timestamplet generatedAt =newDate();generatedAt.setMilliseconds(0);constrequestData:GenericPayloadResponse=createGenericPayload(JSON.stringify(request.body.data) );exec.addLog(`Generic payload: ${JSON.stringify(requestData)}`);// pass parsing of the report to the right report type parserif (requestData.data !==undefined) {parseReport( exec, doorReportTypeHashId, generatedAt,JSON.stringify(requestData.data) ); }return { statusCode:204 }; }return { statusCode:404, body: ["not_found_error","Route cannot be found"], }; }}functionparseReport( exec:Exec, reportTypeHashId:string, generatedAt:Date, payload:string) {exec.parseReport({ reportTypeHashId: reportTypeHashId, payload:JSON.stringify({ generatedAt: generatedAt, payload: payload, }), });}functioncreateGenericPayload(body:string):GenericPayloadResponse {let payload:string="", port:number=0, identifier:string; body =JSON.parse(body);// KPN Loraif (Array.isArray(body) && body[0].bn !=undefined) { (payload = body[1].vs), (port =parseInt(body[2].v)); } else {thrownewError(`Failed to parse generic payload`); }return { data: { payload, port, }, };}typeJsonObject= { [key:string]:Json };
/** * Source: http://www.dragino.com/downloads/downloads/LoRa_End_Node/LDS01/LDS01_LoRaWAN_Door_Sensor_UserManual_v1.2.0.pdf */constoperatingVoltageQuantityHashId:string="TO DO";constdoorOpenStatusQuantityHashId:string="TO DO";consttotalOpenDoorEventsQuantityHashId:string="TO DO";constlastDoorOpenDurationQuantityHashId:string="TO DO";constdoorChannelIndex:number=0;constsensorConditionChannelIndex:number=1;functionhandle(args:Arguments, exec:Exec):Result {constdata=JSON.parse(args.payload);if (typeof data !=="object"|| data ===null) {thrownewError("Data is not a JSON-object"); }constgeneratedAtMs=Date.parse(data.generatedAt);if (Number.isNaN(generatedAtMs)) {thrownewError("generatedAt cannot be parsed into a valid Date"); }constgeneratedAt=newDate(generatedAtMs);constparsedLoraData=JSON.parse(data.payload);constpayload=parsedLoraData.payload;constport=parsedLoraData.port;exec.addLog(`Device payload and port as received by report parser: ${payload}, ${port}` );if (typeof payload !="string") {thrownewError("Payload is not a string"); }constdecodedPayload=hexToBytes(payload);exec.addLog(`Bytes: ${decodedPayload}`);constoperatingVoltage= ((decodedPayload[0] <<8) | decodedPayload[1]) &0x3fff;constdoorOpenStatus= decodedPayload[0] &0x80?1:0;consttotalOpenDoorEvents= (decodedPayload[3] <<16) | (decodedPayload[4] <<8) | decodedPayload[5];constlastDoorOpenDuration= (decodedPayload[6] <<16) | (decodedPayload[7] <<8) | decodedPayload[8];constmeasurements:Result["measurements"] = [];pushMeasurement( measurements, sensorConditionChannelIndex, operatingVoltageQuantityHashId, generatedAt,-3, operatingVoltage );pushMeasurement( measurements, doorChannelIndex, doorOpenStatusQuantityHashId, generatedAt,0, doorOpenStatus );pushMeasurement( measurements, doorChannelIndex, totalOpenDoorEventsQuantityHashId, generatedAt,0, totalOpenDoorEvents );pushMeasurement( measurements, doorChannelIndex, lastDoorOpenDurationQuantityHashId, generatedAt,0, lastDoorOpenDuration );return { generatedAt, measurements, fields: {}, };}functionpushMeasurement( measurements:Result["measurements"], channelIndex:number, quantityHashId:string, generatedAt:Date, orderOfMagnitude:number, significand:number) {measurements.push({ channelIndex: channelIndex, quantityHashId: quantityHashId, generatedAt: generatedAt, orderOfMagnitude: orderOfMagnitude, significand: significand, });}functionhexToBytes(hex:string):Uint8Array {for (var bytes = [], c =0; c <hex.length; c +=2)bytes.push(parseInt(hex.substr(c,2),16));returnUint8Array.from(bytes);}tyo
Sending downlinks
To be able to send a downlink message to devices via KPN Things, it is important to correctly access the API. The "KPN Things Developer Manual" and the "KPN Things API's public documentation" provides a detailed information on how this can be done. Here, we conclude the most important steps required to send a raw payload.
Each time, before we want to connect to the device via KPN Things API, we need to request an access token. The access token can be requested with the following code snippet.
The KPN kpnTenantID can be found by completing the following steps, while the steps to obtain kpnClientID (API key ID) and the kpnClientSecret (API key secret key) are explained here.
The last steps are to construct the request body, downlink URL, request header, and to execute the POST request.
constbody='[{"bn":"urn:dev:DEVEUI:'+devEUI+':", "n":"payloadHex", "vs": "'+ payload +'"}]';consturl= kpnDownlinkURL +"?port="+port.toString(10);constbearerToken=kpnGetBearerToken(exec);if (bearerToken ===null||bearerToken.length===0) {thrownewError(`BearerToken is null or length is ${bearerToken.length}`);}constheader= {"Authorization":"Bearer "+ bearerToken,"Accept":"application/vnd.kpnthings.actuator.v1.response+json","Content-Type":"application/json"}constresponse=exec.sendRequest({url, method:'post', body: body, headers: header});
In this last part, the user needs to provide the devEUI of the device of interest, port (e.g. 2), and a raw payload in a hexadecimal format.