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 handle
const setIntervalCommandHashId = 'TO DO';
const resetDeviceCommandHashId = 'TO DO';
// define the hashId of the reportType
// will be known at step 8
const reportTypeHashId = 'pd72ry';
// IoT Creators connection settings
const iotCreatorsBaseUrl = '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
const iotCreatorsToken = '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.
*/
function handle(args: ArgumentsForIncomingRequestEvent, exec: Exec): IncomingRequestResponse;
function handle(args: ArgumentsForEventWithoutResponse, exec: Exec): void;
function handle(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') {
const request = 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 report
if (request.body === undefined || request.body.type !== 'json') {
return {
statusCode: 400,
body: ['parameter_error', 'Body is not of type JSON'],
};
}
// Set receive timestamp
let generatedAt = new Date();
generatedAt.setMilliseconds(0);
// pass parsing of the report to the right report type parser
exec.parseReport({
reportTypeHashId,
payload: JSON.stringify({
generatedAt,
payload: request.body.data,
}),
});
return { statusCode: 204 };
}
return {
statusCode: 404,
body: ['not_found_error', 'Route cannot be found'],
};
}
}
function filterCommands(exec: Exec, args: Arguments): Command[] {
const scheduledCommands = args.scheduledCommands.filter((sC) => {
let endOfCommand: Date | undefined;
if (sC.endAt !== null) {
endOfCommand = sC.endAt;
} else if (sC.startAt !== null) {
endOfCommand = sC.startAt;
} else if (sC.sentAt !== null) {
endOfCommand = new Date(sC.sentAt.valueOf() + sC.delay * 1000);
}
if (endOfCommand !== undefined && endOfCommand.valueOf() <= Date.now()) {
// drop and do not send commands scheduled in the past
exec.addLog(sC.hashId);
exec.dropCommand(sC.hashId);
return false;
}
if (sC.sentAt === null && sC.deletedAt !== null) {
// drop and do not send commands that were deleted before they were sent.
exec.dropCommand(sC.hashId);
return false;
}
return true;
});
return scheduledCommands;
}
function sendCommands(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 command
if (typeof sC.fields.interval !== 'number') {
throw new Error(`interval is a ${typeof sC.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";
} else if (sC.commandTypeHashId === resetDeviceCommandHashId) {
// Reset device command
payload = "04ff";
payload = exec.uInt8ArrayToBase64(hexToBytes(payload));
requestbody = '{"resourceValue" : "' + payload + '"}';
url = iotCreatorsBaseUrl + deviceId + "/downlinkMsgBase64/0/data";
} else {
throw new Error(`Cannot parse commandTypeHashId ${sC.commandTypeHashId}`);
}
const header = {
"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) {
throw new Error(`Schedule downlink failed with code ${response?.statusCode}`);
}
exec.markCommandAsSent(sC.hashId);
});
}
function createHexString(value: any, width: number): string {
if (value === null || width === null) {
throw new Error("Create Hex String failed!");
}
value = value.toString(16);
width -= value.length;
if (width > 0) {
return new Array(width + (/\./.test(value) ? 2 : 1)).join('0') + value;
}
return value;
}
function hexToBytes(hex: string): Uint8Array {
for (var bytes = [], c = 0; c < hex.length; c += 2)
bytes.push(parseInt(hex.substr(c, 2), 16));
return Uint8Array.from(bytes);
}
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 . In our case, the following request will return { "msg": "Success", "code": 1000 }, which means the request was successful.