Developer Hub

IoT Connector Connect Proxy CoAP OAuth

Connector Proxy

Connect is a proxy service from IoT SIM that can receive CoAP messages and forward them as HTTPS messages and return the response.

June 20, 2024

Overview

Connect is a proxy service from IoT SIM that can receive CoAP messages and forward them as HTTPS messages and return the response. This proxy service ensures that the CoAP messages sent by the IoT device can be transmitted in a format that the HTTPS server can understand. This allows IoT devices to use the lighter CoAP protocol within the secure network without having to worry about TLS encryption as well as authorization. This type of proxy server is particularly useful when integrating IoT devices with an existing HTTPS infrastructure without requiring any changes to the existing servers or services.

iot-sim-connector

Basic proxy functionality

As described earlier in the introduction, the proxy acts as an interface between the IoT devices and any HTTP backend. CoAP messages sent to the proxy are first converted to an HTTP message. This step also incorporates information such as the method and content type. In this process, the message content (payload) is taken without any other processing. The header information, on the other hand, is not natively included in CoAP but can be set if required. In addition to the self-defined header information, the proxy automatically sets the headers described in the the section Header fields.

The last step of the conversion involves the translation of the target address. In simple terms, only the host part of the proxy is replaced by the host in the target URL. If the device uses a path and query parameters, these are retained. Suppose that the host of the proxy is coap://proxy.connect-internal.iot-sim.tech:5683 and the target URL is https://xyz.ee/iot/. When a device addresses the CoAP message to coap://proxy.connect-internal.iot-sim.tech:5683/sensor_data?type=temperature, the HTTP message ends up at https://xyz.ee/iot/sensor_data?type=temperatur.

Once the outgoing HTTP message is composed, it is sent to the newly defined destination. Within the defined timeout, a response is waited for. The timeout is 3 seconds by default but can be defined in a range from 10 milliseconds to 5 seconds.

As soon as the HTTP response comes back, it is decided whether the response is converted into a CoAP response and sent back or not. This is defined by the "Confirmable" option of the initial CoAP message. If a response is to be returned, the status code and the content type are translated accordingly. The header information is discarded.

Authentication

Normally, access to the HTTP backend is and should be secured. There are two ways to realize authentication.

The simplest variant is a shared key, or bearer, which is set in the header. This header information can be set via the configuration page of the portal. If required, this value can be rotated periodically via the portal API.

The second variant uses the M2M OAuth flow (also referred to client credentials flow). Here, the proxy uses predefined credentials (client ID and client secret) and scopes to retrieve a token via the token URL. The proxy uses this token accordingly for communication with the HTTP backend. If the token specifies an expires_in, the token is cached and not requested again until it expires. This prevents unnecessary latency and load on the IDP (Identity Provider). This variant can also be specified via the configuration page of the portal.

Proxy compared to direct communication

In the following, various alternatives to the CoAP proxy solution are compared in terms of the data volume per message. This is a purely theoretical consideration, as the actual values may vary in reality due to various parameters. The data is only used to illustrate the difference between those options.

We assume that a POST request is sent with the media type "text" and a payload of an integer value (4 bytes). The IP header, the case of lost messages, and the responses are not considered here. The TLS handshake usually requires between 1 kilobyte and 4 kilobytes, the TCP handshake 120 bytes, and the DTLS handshake between 50 bytes and 300 bytes. In our example, we add the smaller values in each case. So that the disadvantage of handshakes does not carry too much weight, the diagram illustrates a sequence of ten messages for an open connection.

It can be seen that CoAP is generally a sparse protocol. Since messages to the proxy travel over a secure tunnel, encrypted communication is not necessary. Even though the diagram includes UDP and TCP, unsecured communication over the Internet is not recommended. Therefore, the secured variants should be compared beforehand.

proxy compared to communication

Header fields and mapping

Header fields

Custom header fields can be set globally in the configuration page of the portal. In addition to those header fields, the proxy sets additional default header fields. The table below lists the default header fields provided by the proxy. Please note that according to the HTTP/1.1 specification (RFC 7230), HTTP header field names are case-insensitive. This means that header field names can be written in uppercase, lowercase, or a combination of both, and the server should treat them as equivalent. For example, "X-Connect-ICCID" and "X-connect-iccid" refer to the same header field.

HeaderDescription
X-Forwarded-ForPrivate IP address of the SIM card
X-Connect-ICCIDIntegrated Circuit Card Identification Number
X-Connect-IMSIInternational Mobile Subscriber Identity
Content-TypeTranslated from CoAP message media type
Message-IDUnique CoAP message

HTTP Method to CoAP Code mapping

This table shows available methods with their equivalent.

HTTPCoAP code
GET1
POST2
PUT3
DELETE4

HTTP Status Code to CoAP Codes mapping

The following table shows how HTTP status codes in responses are mapped onto CoAP codes.

HTTP codeCoAP code
Continue - 100Continue - 95
Switching Protocols - 101Switching Protocols - 128
Processing - 102Processing - 67
Early Hints - 103Early Hints - 67
OK - 200
Created - 201Created - 65
Accepted - 202Accepted - 67
Non-Authoritative Information - 203Non-Authoritative Information - 129
No Content - 204No Content - 0
Reset Content - 205Reset Content - 66
Partial Content - 206Partial Content - 95
Multi-Status - 207Multi-Status - 67
Already Reported - 208Already Reported - 67
IM Used - 226IM Used - 67
Multiple Choices - 300Multiple Choices - 130
Moved Permanently - 301Moved Permanently - 132
Found - 302Found - 67
See Other - 303See Other - 128
Not Modified - 304Not Modified - 67
Use Proxy - 305Use Proxy - 132
Temporary Redirect - 307Temporary Redirect - 132
Permanent Redirect - 308Permanent Redirect - 132
Bad Request - 400Bad Request - 128
Unauthorized - 401Unauthorized - 129
Payment Required - 402Payment Required - 128
Forbidden - 403Forbidden - 131
Not Found - 404Not Found - 132
Method Not Allowed - 405Method Not Allowed - 133
Not Acceptable - 406Not Acceptable - 134
Proxy Authentication Required - 407Proxy Authentication Required - 129
Request Timeout - 408Request Timeout - 157
Conflict - 409Conflict - 128
Gone - 410Gone - 128
Length Required - 411Length Required - 128
Precondition Failed - 412Precondition Failed - 140
Request Entity Too Large - 413Request Entity Too Large - 141
Request URI Too Long - 414Request URI Too Long - 141
Unsupported Media Type - 415Unsupported Media Type - 143
Requested Range Not Satisfiable - 416Requested Range Not Satisfiable - 128
Expectation Failed - 417Expectation Failed - 128
I'm a teapot - 418No content - 0
Misdirected Request - 421Misdirected Request - 165
Unprocessable Entity - 422Unprocessable Entity - 128
Locked - 423Locked - 131
Failed Dependency - 424Failed Dependency - 136
Too Early - 425Too Early - 136
Upgrade Required - 426Upgrade Required - 136
Precondition Required - 428Precondition Required - 136
Too Many Requests - 429Too Many Requests - 157
Request Header Fields Too Large - 431Request Header Fields Too Large - 128
Unavailable For Legal Reasons - 451Unavailable For Legal Reasons - 132
Internal Server Error - 500Internal Server Error - 160
Not Implemented - 501Not Implemented - 161
Bad Gateway - 502Bad Gateway - 162
Service Unavailable - 503Service Unavailable - 163
Gateway Timeout - 504Gateway Timeout - 164
HTTP Version Not Supported - 505HTTP Version Not Supported - 128
Variant Also Negotiates - 506Variant Also Negotiates - 160
Insufficient Storage - 507Insufficient Storage - 160
Loop Detected - 508Loop Detected - 160
Not Extended - 510Not Extended - 130
Network Authentication Required - 511Network Authentication Required - 129

✶ If the server responds 200 (OK), the mapping variants based on the initial CoAP request code/method. The table below shows the CoAP code you can expect if the server responds 200.

MethodCoAP code
GET - 1Content - 69
POST - 2Created - 65
PUT - 3Changed - 68
DELETE - 4Deleted - 66

HTTP Content Type to CoAP Media Type mapping

The following tables shows how CoAP Media Type and HTTP Content Type request and response are mapped by the proxy.

Content Type (HTTP)Media Type (CoAP)
-App Octets- 42
application/coap-payload-
text/plainText Plain - 0
text/plain;charset=utf-8Text Plain - 0
application/cose; cose-type="cose-encrypt0"App Cose Encrypt0 - 96
application/cose; cose-type="cose-mac0"App Cose Mac0 - 97
application/cose; cose-type="cose-sign1"App Cose Sign1 - 98
application/link-formatApp Link Format - 40
application/xmlApp XML - 41
application/octet-streamApp Octets - 42
application/exiApp Exi - 47
application/jsonApp JSON - 50
application/json-patch+jsonApp JSON Patch - 51
application/merge-patch+jsonApp JSON Merge Patch - 52
application/cborApp CBOR - 60
application/cwtApp CWT - 61
application/cose; cose-type="cose-encrypt"App Cose Encrypt - 96
application/cose; cose-type="cose-mac"App Cose Mac - 97
application/cose; cose-type="cose-sign"App Cose Sign - 98
application/cose-keyApp Cose Key - 101
application/cose-key-setApp Cose Key Set - 102
application/senml+jsonApp Senml JSON - 110
application/senml+cborApp Senml Cbor - 112
coap-group+jsonApp Coap Group - 256
application/senml-etch+jsonApp Senml Etch JSON - 320
application/senml-etch+cborApp Senml Etch Cbor - 322
application/vnd.ocf+cborApp Ocf Cbor - 10000
application/vnd.oma.lwm2m+tlvApp Lwm2m TLV - 11542
application/vnd.oma.lwm2m+jsonApp Lwm2m JSON - 11543
application/vnd.oma.lwm2m+cborApp Lwm2m Cbor - 11544

Simulating a request

Sometimes a backend is developed before the hardware even exists, it is not decided how the backend handles requests or a simple configuration or connectivity test is required. In such cases, a simulation endpoint is provided, enabling the simulation of CoAP requests using a specified proxy configuration and CoAP request parameters.

For the execution of the request, the configuration page is utilized to retrieve the Target URL and HTTP header fields, while the CoAP parameters path, body, code, and media type are obtained from the corresponding pop-up. Simulated requests are treated in the same manner as regular CoAP requests, with the requirement that the body must be encoded as a Base64 string (the decoded content is transmitted by the proxy).

Examples

MS Teams message

Various chatbot webhooks can be used via proxies, such as from MS or Webex Teams. This example illustrates how to generate MS Teams messages using CoAP messages. This can be handy if there is no backend at the current time and IoT device developers want to see their messages. Of course, it is important to keep in mind that the messages must be in a format that Teams can understand.

{"text": "Hello World"}

Webhook setup

Within MS Teams, the desired group chat is selected first. Under the menu "---" the tab "Connectors" is called. There you search for "Incoming Webhook" and add it to the chat with "Add". Under certain circumstances, the window closes. In this case, open the "Connectors" tab again and then press "Configure" next to "Incoming Webhook". On this configuration page, specify a name and an optional image. Then confirm with "Create" and copy the URL to a file or your notes. This URL is the webhook URL you will need in the following steps.

An introduction from Microsoft itself can be found here: https://learn.microsoft.com/en-us/microsoftteams/platform/webhooks-and-connectors/how-to/add-incoming-webhook?tabs=dotnet.

Configure Proxy

In this step, you store the previously generated webhook URL in the proxy configuration. To do this, open the IoT SIM portal, navigate to the "Configurations" tab, and select "IoT Connector Proxy". Paste the previously generated webhook URL into the text field next to "Target URL" and confirm the entry with "Apply".

IoT Connector Proxy

Send CoAP messages

The IoT device can now send messages to the proxy using CoAP, which are then forwarded to the MS Connector. The URL of the proxy is coap://proxy.connect-internal.iot-sim.tech:5683 (only accessible from the network of IoT SIM). For a first test, the body of the message can contain the following JSON data:

{"text": "Hello World"}

Depending on the hardware, external libraries may have to be used for CoAP. In this example, we use an ESP-32 from ESPRESSIF with the CoAP-simple-library library.

// numberLines:true
#include <SoftwareSerial.h>
#include <coap-simple.h>

// Software serial pins for SIM800L module
#define rxPin 16
#define txPin 17
SoftwareSerial sim800lSerial(txPin, rxPin);

// CoAP server endpoint
const char* serverEndpoint = "coap://proxy.connect.iotsim.tech:5683";

void setup() {
  // Initialize software serial for SIM800L module
  sim800lSerial.begin(9600);
  while(!sim800lSerial);

  // Turn on SIM800L module
  sim800lSerial.println("AT");
  delay(1000);
  sim800lSerial.println("AT+CPIN?");
  delay(1000);
  sim800lSerial.println("AT+CREG?");
  delay(1000);
  sim800lSerial.println("AT+CGATT=1");
  delay(1000);

  // Initialize CoAP client
  coap_client_init();

  // Set CoAP server endpoint
  coap_client_set_server_endpoint(serverEndpoint);

  // Connect to GPRS network
  connectGPRS()

  // Send CoAP message to proxy
  sendCoAP("{\"text\": \"Hello World\"}")

  // Disconnect from GPRS network
  disconnectGPRS()
}

void connectGPRS() {
  sim800lSerial.println("AT+CIPSHUT");
  delay(1000);
  sim800lSerial.println("AT+CIPMUX=0");
  delay(1000);
  // sim800lSerial.println("AT+CSTT=\"your_APN\",\"your_USERNAME\",\"your_PASSWORD\"");
  // delay(1000);
  sim800lSerial.println("AT+CIICR");
  delay(1000);
  sim800lSerial.println("AT+CIFSR");
  delay(1000);
}

void disconnectGPRS() {
  sim800lSerial.println("AT+CIPSHUT");
  delay(1000);
}

void sendCoAP(char* payload) {
  // Send
  coap_pdu_t pdu;
  coap_pdu_reset(&pdu);
  coap_add_option(&pdu, COAP_OPTION_CONTENT_TYPE, COAP_CONTENT_TYPE_APPLICATION_JSON);
  coap_send_post(&pdu, (uint8_t*)payload, strlen(payload));

  // Await and print response
  coap_wait_response();
  Serial.print("CoAP response: ");
  Serial.write((char*)pdu.payload, pdu.payload_len);
  Serial.println();
}

void loop() {}

The messages can also be made fancier using Cards. For more information, visit https://learn.microsoft.com/en-us/microsoftteams/platform/webhooks-and-connectors/how-to/connectors-using?tabs=cURL.

Sending e-mails via IFTTT

IFTTT (If This Then That) is a web and mobile application, some of which are free, that enables connections between various online services and devices. So-called "applets" are used to set up these connections. In this example, you use an applet that automatically triggers an action (email) when a certain trigger (webhook) occurs.

Create an account and applet

First of all, an account with IFTTT is required. Follow the current process of IFTTT.

Once you are logged in, you can use various predefined applets under "My Applets" or simply create your own. It is only important that you select "Webhooks" accordingly under "If This" (entry point). For example, you can specify "sensor" as the "Event Name". Under "Then That" (starting point) you can specify a subsequent action on the trigger, such as "Email". This should look like this:

Confirm the setting with "Continue" and "Finish". To get the webhook URL you have to go to the following page https://ifttt.com/maker_webhooks and then press "Documentation". There enter the "Event Name" of the webhook under "{event}" and then copy the URL shown in the example.

Configure proxy

In this step, we store the previously generated webhook URL in the proxy configuration. To do this, open the IoT SIM portal, navigate to the "Configurations" tab, and select "IoT Connector Proxy" there. Paste the previously generated webhook URL into the text field next to "Target URL" and confirm the entry with "Apply".

screenshot iot connector proxy IFTTT

Send CoAP messages

The IoT device can now send messages to the proxy using CoAP, which are then passed to the applet webhook. The URL of the proxy is coap://proxy.connect-internal.iot-sim.tech:5683 (only accessible from IoT SIM's network). For a first test, the body of the message can contain the following JSON data:

{"text": "Hello World"}

Depending on the hardware, external libraries may have to be used for CoAP. In this example, we use an ESP-32 from ESPRESSIF with the CoAP-simple-library library.

#include <SoftwareSerial.h>
#include <coap-simple.h>

// Software serial pins for SIM800L module
#define rxPin 16
#define txPin 17
SoftwareSerial sim800lSerial(txPin, rxPin);

// CoAP server endpoint
const char* serverEndpoint = "coap://proxy.connect.iotsim.tech:5683";

void setup() {
  // Initialize software serial for SIM800L module
  sim800lSerial.begin(9600);
  while(!sim800lSerial);

  // Turn on SIM800L module
  sim800lSerial.println("AT");
  delay(1000);
  sim800lSerial.println("AT+CPIN?");
  delay(1000);
  sim800lSerial.println("AT+CREG?");
  delay(1000);
  sim800lSerial.println("AT+CGATT=1");
  delay(1000);

  // Initialize CoAP client
  coap_client_init();

  // Set CoAP server endpoint
  coap_client_set_server_endpoint(serverEndpoint);

  // Connect to GPRS network
  connectGPRS()

  // Send CoAP message to proxy
  sendCoAP("{\"text\": \"Hello World\"}")

  // Disconnect from GPRS network
  disconnectGPRS()
}

void connectGPRS() {
  sim800lSerial.println("AT+CIPSHUT");
  delay(1000);
  sim800lSerial.println("AT+CIPMUX=0");
  delay(1000);
  // sim800lSerial.println("AT+CSTT=\"your_APN\",\"your_USERNAME\",\"your_PASSWORD\"");
  // delay(1000);
  sim800lSerial.println("AT+CIICR");
  delay(1000);
  sim800lSerial.println("AT+CIFSR");
  delay(1000);
}

void disconnectGPRS() {
  sim800lSerial.println("AT+CIPSHUT");
  delay(1000);
}

void sendCoAP(char* payload) {
  // Send
  coap_pdu_t pdu;
  coap_pdu_reset(&pdu);
  coap_add_option(&pdu, COAP_OPTION_CONTENT_TYPE, COAP_CONTENT_TYPE_APPLICATION_JSON);
  coap_send_post(&pdu, (uint8_t*)payload, strlen(payload));

  // Await and print response
  coap_wait_response();
  Serial.print("CoAP response: ");
  Serial.write((char*)pdu.payload, pdu.payload_len);
  Serial.println();
}

void loop() {}

The service can sometimes take a few seconds to minutes for an email to arrive. Check your spam folder as well if necessary.

Lambda API for time synchronization with Basic Auth

This example creates a simple Lambda function that looks for an "Authorization" header and returns the current time in Unix seconds accordingly. The basics of Lambda are not considered. For more information and a similar example, see the AWS course "Run a Serverless "Hello, World!" with AWS Lambda".

Lambda Function

For this example, we will set up a small Lambda function using Python. The basic idea is that requests with the "Authorization" header "Bearer user:passwd" will receive the current time in Unix-Nano format as a response. Otherwise, an "Unauthorized" should be returned. For the newly created Lambda function, you still need a "Function URL". How to do that is described here.

import time

def lambda_handler(event, context):
    # Get the headers from the event object
    headers = event.get('headers', {})

    # Check if the Authorization header exists and has the correct value
    if headers.get('Authorization') == 'Bearer user:passwd':
        # Return the current timestamp in Unix seconds format
        return {'statusCode': 200, 'body': str(int(time.time()))}
    else:
        # Return an error message if the header is missing or incorrect
        return {'statusCode': 401, 'body': 'Unauthorized'}

Configure proxy

In this step, we store the previously generated function URL and the required "Authorization" header in the proxy configuration. To do this, open the IoT SIM portal, navigate to the "Configurations" tab, and select "IoT Connector Proxy". Paste the previously created "Function URL" into the text field next to "Target URL" and confirm the entry with "Apply". Then enter the "Authorization" header. To do this, first press "Add new header field" and enter "Authorization" as the Key and "Bearer user:passwd" as the Value.

screenshot iot connector proxy AWS Lambda

Send CoAP messages

The IoT device can now send messages to the proxy using CoAP, which are then passed on to the Lambda function with the appropriate URL and the Authorization header. The URL of the proxy is coap://proxy.connect-internal.iot-sim.tech:5683 (only accessible from the network of IoT SIM).

#include <SoftwareSerial.h>
#include <coap-simple.h>
#include <ESP32Time.h>

// Software serial pins for SIM800L module
#define rxPin 16
#define txPin 17
SoftwareSerial sim800lSerial(txPin, rxPin);

// CoAP server endpoint
const char* serverEndpoint = "coap://proxy.connect.iotsim.tech:5683";

void setup() {
  // Initialize software serial for SIM800L module
  sim800lSerial.begin(9600);
  while(!sim800lSerial);

  // Turn on SIM800L module
  sim800lSerial.println("AT");
  delay(1000);
  sim800lSerial.println("AT+CPIN?");
  delay(1000);
  sim800lSerial.println("AT+CREG?");
  delay(1000);
  sim800lSerial.println("AT+CGATT=1");
  delay(1000);

  // Initialize CoAP client
  coap_client_init();

  // Set CoAP server endpoint
  coap_client_set_server_endpoint(serverEndpoint);

  // Connect to GPRS network
  connectGPRS()

  // Request time with CoAP message via proxy and set system time
  syncTime()

  // Disconnect from GPRS network
  disconnectGPRS()
}

void connectGPRS() {
  sim800lSerial.println("AT+CIPSHUT");
  delay(1000);
  sim800lSerial.println("AT+CIPMUX=0");
  delay(1000);
  // sim800lSerial.println("AT+CSTT=\"your_APN\",\"your_USERNAME\",\"your_PASSWORD\"");
  // delay(1000);
  sim800lSerial.println("AT+CIICR");
  delay(1000);
  sim800lSerial.println("AT+CIFSR");
  delay(1000);
}

void disconnectGPRS() {
  sim800lSerial.println("AT+CIPSHUT");
  delay(1000);
}

void syncTime() {
  // Send
  coap_pdu_t pdu;
  coap_pdu_reset(&pdu);
  oap_send_get(&pdu);

  // Await and print response
  coap_wait_response();
  Serial.print("Remote time (unix-second): ");
  Serial.write((char*)pdu.payload, pdu.payload_len);
  Serial.println();

  // Set time
  setTime(atoi((char *)pdu.payload));
}

void loop() {}

Calling API with M2M OAuth

This example shows how to use M2M OAuth (also referred to client credentials flow) with AWS Cognito as the IDP. If you want to use a different IDP for your HTTP backend, such as Keycloak, the setup may differ accordingly.

Set up Cognito

To begin, you need to set up Cognito. Using the AWS Console, navigate to the "Amazon Cognito" service and create a "User Pool" with "Add user directories to your app". On the following pages, select the "User name" option as the sign-in option ("Cognito user pool sign-in options"), disable MFA ("Multi-factor authentication"), and self-registration ("Self-registration"). Since no registration is required, the option "Send an email with Cognito" is selected under "Email". For App Integration, a name for the user pool and app client (this client is not required) is defined.

Then open the newly created user pool and navigate to "App integration". Here you first create a "domain" via "Actions"→"Create Cognito domain". As a prefix, enter a unique text, such as https://demo-iot. This will give you a domain https://demo-iot.auth.eu-central-1.amazoncognito.com. Next, you need a resource server with a scope, which you create via "Create resource server". For example, you can name it iot-rs and set the "Resource server identifier" to iot-rs. Last but not least, create a scope with the name API_ACCESS under "Custom Scope".

Now you only need a user. First, you can delete the automatically created user under "App clients and analytics" and create a new one with "Create app client". For this, select the "App type" "Confidential Client" and give it a name, e.g. proxy. Under "Authentication flows" you can remove all selected options as well as under "Hosted UI settings" the "URL". You must select the option "Client credentials" for "OAuth 2.0 grant types". Finally, give the user the "Custom scope" you created (iot-rs/API_ACCESS in the example). If you now select the user, you can retrieve the client ID and the client secret. So far the setup is complete.

To test the configuration, you can use Postman for example. To do this, open a new Request tab and under it the "Authorization" tab. Under "Type", "OAuth 2.0" is selected. On the configuration page, first set the "Grant Type" to "Client Credentials". Under "Access Token URL" enter your domain URL of the user pool with the postfix /oauth2/token, which in this example corresponds to the URL https://demo-iot.auth.eu-central-1.amazoncognito.com/oauth2/token. In addition, the generated client ID and the client secret as well as the scope are entered. The scope is composed of the "Resource server identifier" + / + scope name, which in our example corresponds to iot-rs/API_ACCESS. As soon as you press "Get New Access Token" you should get a token.

screenshot postman

Configure proxy

After you have all the necessary information, you can configure the proxy accordingly. To do this, open the IoT SIM portal, navigate to the "Configurations" tab, and select "IoT Connector Proxy". Here you enter the "Target URL" and under "M2M OAuth" the corresponding access data ("Client ID" and "Client Secret"), the "Token URL" and the "Scopes".

screenshot iot connector proxy M2M OAuth

Database with Azure IoT Hub

In this example, you will use Azure IoT Hub in combination with Azure Functions to store data in the Cosmos DB.

diagram Azure IoT Hub

Azure setup

The following setup is done via the Azure Portal.

Cosmos DB

First, navigate to "Azure Cosmos DB" in the portal and create a new Azure Cosmos DB account or select an existing account. Next, create a new database via "Create" and select "Azure Cosmos DB for NoSQL" as the API. Fill out the form as needed. For the demo, you can set the "Capacity mode" to "Serverless". Complete the creation with "Review + create".

Once the database is online, open it and create a new container using "Add container". Set a "Database id" and "Container id". Make a note of both and confirm the entries with "OK". After the container has been created, a welcome message should be displayed. Below that, click on "Connect" and copy the "PRIMARY CONNECTION STRING".

Azure IoT Hub

First, navigate to "IoT Hub" in the portal and create a new hub using "Create IoT hub". Give the hub a unique "IoT hub name" and select "Free" as the "Tier". Complete the creation by clicking "Review + create".

Within the hub, navigate to "Devices" and press "Add Device". Assign a name and keep the default configuration. Select the new device and copy the value of the "Primary connection string".

Azure Function App

Now we create the link between Hub and DB. To do this, navigate to "Function App" in the portal and create a new Function App via "Create". Assign a unique "Function App name". Select "Node.js" as the "Runtime Stack", leave the "Operating System" set to "Windows" and select "Consumption (Serverless)" as the "Plan type" for testing purposes. Complete the creation with "Review + create".

Once the deployment is complete, select the "Configuration" category. Now create three variables via "New application settings" ("COSMOSDB_CONNECTION_STRING" with the previously copied "PRIMARY CONNECTION STRING" of the Cosmos DB. "COSMOSDB_DATABASE" with the "Database id" and "COSMOSDB_CONTAINER" with the "Container id"). Confirm the entries with "Save" and "Continue".

Then open the category "Console" and enter the following two commands separately:

npm init -y
npm install @azure/cosmos --save

Now select the category "Functions". Create a new function using the "IoT Hub (Event Hub)" template. template. Press the blue "New" button below "Event Hub connection". In the popup, your previously created IoT Hub should be selected under the first "Event Hub connection" and "Events (built-in endpoint)" under that. Confirm this popup with "OK" and the creation with "Create".

Select the new function and navigate to "Code + Test" to modify the default code.

const { CosmosClient } = require('@azure/cosmos');

module.exports = async function (context, IoTHubMessage) {
  // Create a Cosmos DB client and container object
  const cosmosClient = new CosmosClient(process.env.COSMOSDB_CONNECTION_STRING);
  const database = cosmosClient.database(process.env.COSMOSDB_DATABASE);
  const container = database.container(process.env.COSMOSDB_CONTAINER);

  // Insert new document from the incoming message into the container
  await container.items.create({ messages: IoTHubMessage });

  // Mark call as done
  context.done();
};

You can test the function via "Test/Run". For example, enter the following body and press "Run":

{"Hello": "World"}

In Cosmo's DB, you should now see a new item under the database in the "Items" tab:

screenshot Azure Portal Cosmos DB

Configure proxy

In this step, you store the previously received "Primary connection string" of the device in the proxy configuration. To do this, open the IoT SIM portal, navigate to the "Configurations" tab, and select "IoT Connector Azure". Enter the corresponding value under "Connection string" and apply it by clicking "Apply".

screenshot iot connector Azure IoT Hub

Send CoAP messages

The IoT device can now send messages to the proxy using CoAP, which are then sent to Azure IoT Hub. The URL of the proxy is coap://proxy.connect-internal.iot-sim.tech:5683 (only accessible from the network of IoT SIM). The content (payload) is stored accordingly in the database.

#include <SoftwareSerial.h>
#include <coap-simple.h>

// Software serial pins for SIM800L module
#define rxPin 16
#define txPin 17
SoftwareSerial sim800lSerial(txPin, rxPin);

// CoAP server endpoint
const char* serverEndpoint = "coap://proxy.connect.iotsim.tech:5683";

void setup() {
  // Initialize software serial for SIM800L module
  sim800lSerial.begin(9600);
  while(!sim800lSerial);

  // Turn on SIM800L module
  sim800lSerial.println("AT");
  delay(1000);
  sim800lSerial.println("AT+CPIN?");
  delay(1000);
  sim800lSerial.println("AT+CREG?");
  delay(1000);
  sim800lSerial.println("AT+CGATT=1");
  delay(1000);

  // Initialize CoAP client
  coap_client_init();

  // Set CoAP server endpoint
  coap_client_set_server_endpoint(serverEndpoint);

  // Connect to GPRS network
  connectGPRS()

  // Send CoAP message to proxy
  sendCoAP("{\"text\": \"Hello World\"}")

  // Disconnect from GPRS network
  disconnectGPRS()
}

void connectGPRS() {
  sim800lSerial.println("AT+CIPSHUT");
  delay(1000);
  sim800lSerial.println("AT+CIPMUX=0");
  delay(1000);
  // sim800lSerial.println("AT+CSTT=\"your_APN\",\"your_USERNAME\",\"your_PASSWORD\"");
  // delay(1000);
  sim800lSerial.println("AT+CIICR");
  delay(1000);
  sim800lSerial.println("AT+CIFSR");
  delay(1000);
}

void disconnectGPRS() {
  sim800lSerial.println("AT+CIPSHUT");
  delay(1000);
}

void sendCoAP(char* payload) {
  // Send
  coap_pdu_t pdu;
  coap_pdu_reset(&pdu);
  coap_add_option(&pdu, COAP_OPTION_CONTENT_TYPE, COAP_CONTENT_TYPE_APPLICATION_JSON);
  coap_send_post(&pdu, (uint8_t*)payload, strlen(payload));

  // Await and print response
  coap_wait_response();
  Serial.print("CoAP response: ");
  Serial.write((char*)pdu.payload, pdu.payload_len);
  Serial.println();
}

void loop() {}