Jump toUpdate content

Using IoT Cloud Twins

This page shows you how to use Cloud Twins through both REST and inband MQTT API, using the example of a Device consisting of an internet-enabled alarm control panel with a siren and door sensor, the alarm being activated or deactivated by a smartphone application. We show:

  • how to use a Cloud Twin to store information when a user arms (activates) the alarm from their smartphone
  • how the Device can use the same Cloud Twin to store information about whether its door sensor has detected an open door and whether its siren is ringing in a ‘current state’ document
  • how the Device can send requests to read the ‘desired’ documents in its Cloud Twin, compute new states for itself based on the information it receives and the information from its own sensors, and write new updated data to the ‘current’ Cloud Twin document.
Requirements:
Note:

For more information, refer to our documentation about Cloud Twins.

Important:

The Cloud Twins feature is currently in beta status.

Understanding Cloud Twins

Internet Alarm Control Panel

In the following example, you have an internet-enabled alarm control panel with a door sensor and a siren.

Be aware that:

  • The siren will sound when the door is opened only if the system is armed.
  • In case the power is cut and restored, the system should be armed/disarmed as previously set.
  • The app on your smartphone should be able to display if your door is open. If your alarm system is not reachable, the app should display the last known state and the last time this state was updated.

The control panel is one device and uses the inband MQTT API to communicate with its twin.

The smartphone application uses the REST API to access directly the control panel twin.

Cloud Twin Documents

The twin represents the panel’s current state and the desired states using two documents:

  • The desired document: represents the wanted state as controlled from the application to arm/disarm the alarm. It is written by the application and read by the device.

Example

{
"alarm_armed": true
}
  • The current document: represents the actual state of the alarm panel including its door sensor and siren. It is written by the device and read by the application.

Example

{
"last_update": "<time>",
"alarm_armed": false,
"siren_ringing": false,
"door_open": true
}

As each document has a single writer, we do not need to handle any concurrency and can send documents without version information.

Creating the resources

Note:

In production, you should Deny Insecure connections to have the highest level of security.

Set the following variables:

IOT_API="https://api.scaleway.com/iot/v1/regions/fr-par"
SCW_SECRET_KEY="<your scaleway token secret here>"
SCW_PROJECT="<your project ID here>"
PANEL_DEVICE_ID="<the device_id of the device you created>"

Understanding Device behavior

To receive responses to its inband API call, the device will subscribe to the response topics: $SCW/twins/me/#.

The device will perform the following tasks:

  1. Sending the request to read the desired document and publishing $SCW/twins/me/desired/get
  2. Storing the new state internally when the response arises on $SCW/twins/me/desired/get/response/success,
  3. Reading the door sensor and storing it internally.
  4. Computing the siren state depending on the internal state, and storing the new siren_ringing state internally.
  5. Updating the update time internally.
  6. Publishing the internal state to the current document, using $SCW/twins/me/current/put, with the document as payload, for example:
    {
    "data":{
    "last_update": "<time>",
    "alarm_armed": false,
    "siren_ringing": false,
    "door_open": true
    }
    }
  7. Repeating the process.
Note:

This is a minimalistic description, some steps could be done asynchronously.

Tip:

To manually test this you can use our webclient. Use the link on your device page and it will connect you directly. The device must accept insecure connections to use the webclient.

Understanding Application behavior

The application will use the REST API.

  1. It will read the current document and use it to display its user interface.

    curl -sS -H "X-Auth-Token: $SCW_SECRET_KEY" $IOT_API/twins/$PANEL_DEVICE_ID/documents/current | jq
  2. When the user toggles the armed button, it will send the new desired document.

    curl -sS -H "X-Auth-Token: $SCW_SECRET_KEY" -X PUT $IOT_API/twins/$PANEL_DEVICE_ID/documents/desired -d '{
    "data": {
    "alarm_armed": true
    }
    }' | jq

Setting up partial updates

Instead of pushing the full document each time, you might want to update only the part that changed. This is particularly useful if your document is large.

To do so, you can use the patch command.

Continuing the previous example, if you want to only update the last_update time and door_open state you will publish on the $SCW/twins/me/current/patch topic with the following patch document as a payload:

{
"data":{
"last_update": "<new_time>",
"door_open": false
}
}

Enabling concurrency

Consider a single document accessed by several actors. In this case, you can add a constraint so that one actor does not discard data added by another.

The first solution is to use different keys and the patch method (see above).

The second solution is to use optimistic locking through version numbers.

When modifying a document(put or patch) the user sends the current version along with the new data.

  • If the version matches the one in the twin, the change is applied.
  • If it does not match, an error is returned, and the user can then fetch the document (get), compute the modification again and send it.

REST API

  1. Create a document. This is version 1.

    curl -sS -H "X-Auth-Token: $SCW_SECRET_KEY" -X PUT $IOT_API/twins/$PANEL_DEVICE_ID/documents/concurency -d '{
    "data": {
    "test_value": "initial"
    }
    }' | jq
  2. Update the document.

    ```bash
    curl -sS -H "X-Auth-Token: $SCW_SECRET_KEY" -X PUT $IOT_API/twins/$PANEL_DEVICE_ID/documents/concurency -d '{
    "version": 1,
    "data": {
    "test_value": "first actor"
    }
    }' | jq
    ```
    The updated document becomes version 2 (as shown in the output below).

    ```json
    {
    "twin_id": "<your id>",
    "document_name": "concurency",
    "version": 2,
    "data": {
    "test_value": "first actor"
    }
    }
    ```
    Suppose that a second user runs the same call 'almost' simultaneously, without noticing that version 2 already existed, and tries to update version 1 again:
    ```bash
    curl -sS -H "X-Auth-Token: $SCW_SECRET_KEY" -X PUT $IOT_API/twins/$PANEL_DEVICE_ID/documents/concurency -d '{
    "version": 1,
    "data": {
    "test_value": "second actor"
    }
    }' | jq
    ```

    The output returned, in this case, is an invalid version argument.

Tip:

As the patch format does not handle arrays, this trigger can be used to update arrays concurrently.

Inband MQTT API

Concurrency can also be set up with the inband MQTT API.

  1. Follow steps 1 and 2 from the previous section.

  2. Open the webclient.

  3. Subscribe to the $SCW/twins/me/# topic.

  4. Publish the following payload on the $SCW/twins/me/concurency/put topic:

    {
    "version": 1,
    "data": {
    "test_value": "MQTT actor"
    }
    }

    The response is once again a version error.

    {
    "error": "document version mismatch",
    "twin_id": "<your twin id>",
    "document_name": "concurency",
    "expected_version": 1,
    "actual_version": 2
    }
  5. Publish the right version with the following payload:

    {
    "version": 2,
    "data": {
    "test_value": "MQTT actor"
    }
    }

    The new document updated to version 3.

See Also