T-Mobile IoT Creators

Learn how to integrate Withthegrid platform with IoT Creators.

Connecting a device

In the following tutorial, temperature & humidity sensor Dragino - N95S31B will be connected to the platform.

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.

curl -X PUT 
--header 'Content-Type: application/json' 
--header 'Accept: application/json'  
--header 'Authorization: Basic <your Base64 encoded username:password>' 
-d '{
  "headers": {"authorization":"Basic <your Base64 encoded username:password>},
  "url": "<your webhook URL>"
}' http://api.scs.iot.telekom.com/m2m/applications/registration

Create new commands

For more information refer to Command types.

Update the device type

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);
}

Last updated