Using IoT Cloud Twins
This page shows you how to use Cloud Twins through both REST and inband MQTT API. We will use the example of a Device consisting of an internet-enabled alarm control panel with a siren and door sensor, managed 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 collects, and write new updated data to the ‘current’ Cloud Twin document.
You may need certain IAM permissions to carry out some actions described on this page. This means:
- you are the Owner of the Scaleway Organization in which the actions will be carried out, or
- you are an IAM user of the Organization, with a policy granting you the necessary permission sets
- You have an account and are logged into the Scaleway console
- You have installed curl and
jq
(JSON parsing tool) on your local computer - You have created an IoT Hub
- You have added a device
- Your device allows insecure connections
For more information, refer to our documentation about Cloud Twins.
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.
- If 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
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:
- Sending the request to read the
desired
document and publishing$SCW/twins/me/desired/get
- Storing the new state internally when the response arises on
$SCW/twins/me/desired/get/response/success
, - Reading the door sensor and storing it internally.
- Computing the siren state depending on the internal state, and storing the new
siren_ringing
state internally. - Updating the update time internally.
- 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}} - Repeating the process.
Note:
This is a minimalistic description, some steps could be done asynchronously.
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.
- 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 - When the user toggles the
armed
button, it will send the newdesired
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
-
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 -
Update the document.
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"}}' | jqThe updated document becomes version 2 (as shown in the output below).
{"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:
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.
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.
- Follow steps 1 and 2 from the previous section.
- Open the webclient.
- Subscribe to the
$SCW/twins/me/#
topic. - Publish the following payload on the
$SCW/twins/me/concurency/put
topic:The response is once again a version error.{"version": 1,"data": {"test_value": "MQTT actor"}}{"error": "document version mismatch","twin_id": "<your twin id>","document_name": "concurency","expected_version": 1,"actual_version": 2} - Publish the right version with the following payload:
The new document updated to version 3.{"version": 2,"data": {"test_value": "MQTT actor"}}