In the following tutorial, temperature & humidity sensor Dragino - N95S31B will be connected to the platform.
Sending downlinks
Downlinks are way to send messages to the device. This could be an instruction to reset the device or change its reporting interval.
Register application URL
In order to be able to send commands to devices via IoT Creators API, we first need to register the application URL from previous tutorial as described in the IoT Creators documentation. In our case, the following request will return { "msg": "Success", "code": 1000 }, which means the request was successful.
In order to process a command, we need to extend the code we previously implemented. The way commands are processed and sent can be implemented in the corresponding device type. The following code will in case of a newCommand:
Optional: Verify a command is valid
Create a command-specific payload as defined by the Dragino - N95S31B documentation
Send downlink request with required payload to IoT Creators
Mark command as sent
Optional: update a device field with additional information (e.g. new reporting interval)
// define the hashIds of commands this device type is expected to handleconstsetIntervalCommandHashId='TO DO';constresetDeviceCommandHashId='TO DO';// define the hashId of the reportType// will be known at step 8constreportTypeHashId='pd72ry';// IoT Creators connection settingsconstiotCreatorsBaseUrl='https://api.scs.iot.telekom.com/m2m/endpoints/';// credential from https://portal.iotcreators.com/projects/<yourp_roject>/credentials as username:password to base64 with prefix Basic
constiotCreatorsToken='Basic <your Base64 encoded username:password>';/*** 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' || args.event.type === 'newCommand' || args.event.type === 'deletedCommand' || args.event.type === 'commandDue') {
/** * Filter commands and send commands */sendCommands(exec, args,filterCommands(exec, args)); }if (args.event.type ==='incomingRequest') {constrequest=args.event.webRequest;if (request.method ==='POST'&&request.url.path ==='/') {if (args.device.supplierDeviceIdentifier ==='iot-creators-registration') {return { statusCode:204 }; }// the device is submitting a condition reportif (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);// pass parsing of the report to the right report type parserexec.parseReport({ reportTypeHashId, payload:JSON.stringify({ generatedAt, payload:request.body.data, }), });return { statusCode:204 }; }return { statusCode:404, body: ['not_found_error','Route cannot be found'], }; }}functionfilterCommands(exec:Exec, args:Arguments):Command[] {constscheduledCommands=args.scheduledCommands.filter((sC) => {let endOfCommand:Date|undefined;if (sC.endAt !==null) { endOfCommand =sC.endAt; } elseif (sC.startAt !==null) { endOfCommand =sC.startAt; } elseif (sC.sentAt !==null) { endOfCommand =newDate(sC.sentAt.valueOf() +sC.delay *1000); }if (endOfCommand !==undefined&&endOfCommand.valueOf() <=Date.now()) {// drop and do not send commands scheduled in the pastexec.addLog(sC.hashId);exec.dropCommand(sC.hashId);returnfalse; }if (sC.sentAt ===null&&sC.deletedAt !==null) {// drop and do not send commands that were deleted before they were sent.exec.dropCommand(sC.hashId);returnfalse; }returntrue; });return scheduledCommands;}functionsendCommands(exec:Exec, args:Arguments, scheduledCommands:Command[]) {scheduledCommands.forEach((sC) => {let payload:string;let requestbody:string;let url:string;// complete serialnumber of the device, including eventual leading "IMEI:" prefix.let deviceId:string='IMEI:'+args.device.supplierDeviceIdentifier;exec.addLog("Commande Type hash id = "+sC.commandTypeHashId);if (sC.commandTypeHashId === setIntervalCommandHashId &&sC.fields !==null) {// Set Reporting Interval commandif (typeofsC.fields.interval !=='number') {thrownewError(`interval is a ${typeofsC.fields.interval}`); }// exec.setDeviceFields([{ key: 'reportInterval', value: <FieldToServerFull>sC.fields.interval }]); payload =createHexString(1,2) +createHexString(sC.fields.interval,6); payload =exec.uInt8ArrayToBase64(hexToBytes(payload)); requestbody ='{"resourceValue" : "'+ payload +'"}'; url = iotCreatorsBaseUrl + deviceId +"/downlinkMsgBase64/0/data"; } elseif (sC.commandTypeHashId === resetDeviceCommandHashId) {// Reset device command payload ="04ff"; payload =exec.uInt8ArrayToBase64(hexToBytes(payload)); requestbody ='{"resourceValue" : "'+ payload +'"}'; url = iotCreatorsBaseUrl + deviceId +"/downlinkMsgBase64/0/data"; } else {thrownewError(`Cannot parse commandTypeHashId ${sC.commandTypeHashId}`); }constheader= {"Authorization": iotCreatorsToken,"Content-Type":"application/json","Content-Length":""+requestbody.length,"Host":"api.scs.iot.telekom.com" }let response =exec.sendRequest({ url: url, method:"put", body: requestbody, headers: header });if (response ===null||response.statusCode <200||response.statusCode >299) {thrownewError(`Schedule downlink failed with code ${response?.statusCode}`); }exec.markCommandAsSent(sC.hashId); });}functioncreateHexString(value:any, width:number):string {if (value ===null|| width ===null) {thrownewError("Create Hex String failed!"); } value =value.toString(16); width -=value.length;if (width >0) {returnnewArray(width + (/\./.test(value) ?2:1)).join('0') + value; }return value;}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);}