How to use IoT API with curl

IoT API with curl

In this tutorial we will use the API through the well known utility curl. This will show you how to create Hubs and Devices, as well as more advanced features of the Scaleway Elements IoT Hub: Hub Events and Routes.

The API reference is here: IoT API

Prerequisites

You will need to have curl, mosquitto-clients (mqtt client), and jq (json parsing tool) installed to follow this guide.

Preliminary steps

You will need to 2 things to get started:

  • A token, which is an API key. You can get/create them from your Scaleway console, under the Account > Credentials page. For more information, see How to generate an API token.
  • Your organization ID.

Let’s save this for later:

IOT_API="https://api.scaleway.com/iot/v1beta1/regions/fr-par"
SCW_TOKEN="<your scaleway token secret here>"
SCW_ORG="<your organization ID here>"

Getting started with IoT Hub

Let’s learn how to create your first IoT Hub with Scaleway.

Set up the hub

The Hub creation is done through a REST endpoint. To call it, you’ll need to provide:

  • Your Organization ID.
  • A name. The name is not important for this tutorial.
  • A product plan. The product plan is not important for this tutorial.

We will save the output to hub.json file to make it easier later:

curl -sS -H "X-Auth-Token: $SCW_TOKEN" -d '{
    "organization_id": "'$SCW_ORG'",
    "name": "my_first_hub",
    "product_plan": "plan_dedicated"
  }' $IOT_API/hubs > hub.json

jq < hub.json

hub.json will contain something like:

{
  "id": "a15ddd5b-ff73-4fb3-b043-1c9176dae295",
  "name": "my_first_hub",
  "organization_id": "<your organization ID>",
  "status": "creating",
  "product_plan": "plan_dedicated",
  "endpoint": "iot.fr-par.scw.cloud",
  "created_at": "2018-12-13T15:15:27.057005Z",
  "enabled": true,
  "device_count": 0,
  "connected_device_count": 0,
  "events_enabled": true,
  "events_topic_prefix": "$SCW/events"
}

We can poll the hub status until it is ready:

curl -sS -H "X-Auth-Token: $SCW_TOKEN" $IOT_API/hubs/$(jq -r '.id' hub.json) | jq -r '.status'

At some point, the status will switch to ready.

Set up the devices

Now we need to create 2 devices. You just need to provide:

  • Your Organization ID.
  • The Hub ID. This is the "id" field from the JSON response received while creating a hub.
  • A name. Again, the name is not important for this tutorial.

Note: As this tutorial aims to be simple and straightforward, the following commands are allowing the device to connect using insecure protocols, such as plain text MQTT or MQTTs without mutual authentication. In production, you should Deny Insecure connections to have the highest level of security. This is done by setting the field allow_insecure to false.

We will save the response to a file so we can use the fields later.

curl -sS -H "X-Auth-Token: $SCW_TOKEN" -d '{
    "hub_id": "'$(jq -r '.id' hub.json)'",
    "name": "my_first_device",
    "allow_insecure": true
  }' $IOT_API/devices > dev1.json

jq < dev1.json

dev1.json should contain something like:

{
  "device": {
    "id": "ca973002-b87d-457e-8b9b-f5e19796e2b9",
    "organization_id": "<your organization ID>",
    "name": "my_first_device",
    "status": "enabled",
    "hub_id": "a15ddd5b-ff73-4fb3-b043-1c9176dae295",
    "created_at": "2018-12-14T13:59:43.277945Z",
    "allow_insecure": true,
    "last_activity_at": "1970-01-01T00:00:00Z",
    "is_connected": false,
    "message_filters": {
      "publish": {
        "policy": "reject",
        "topics": []
      },
      "subscribe": {
        "policy": "reject",
        "topics": []
      }
    }
  },
  "crt": "<certificate here>",
  "key": "<certificate key here>"
}

Let’s now create a second device:

curl -sS -H "X-Auth-Token: $SCW_TOKEN" -d '{
    "hub_id": "'$(jq -r '.id' hub.json)'",
    "name": "my_second_device",
    "allow_insecure": true
  }' $IOT_API/devices > dev2.json

jq < dev2.json

Subscribe and Publish

Now that everything is set up, let’s simulate 2 devices and send data now.

Setup the subscriber:

# In one terminal
mosquitto_sub \
  -h $(jq -r '.endpoint' hub.json) \
  -i $(jq -r '.device.id' dev1.json) \
  -t mytopic/mysubtopic

Run the publisher:

# In another terminal
mosquitto_pub \
  -h $(jq -r '.endpoint' hub.json) \
  -i $(jq -r '.device.id' dev2.json) \
  -t mytopic/mysubtopic \
  -m 'Hello, world!'

You should see the subscriber receive the Hello, world! message.

The secure way

If you require security, you can also connect your device to the Hub using TLS mutual authentication. With this method, the Hub can check the device’s identity, and the device can check the Hub’s identity.

Note: It is possible to connect to the Hub using TLS but without Mututal authentication. In this case the device certificates are not needed as the Hub does not need to check the device identity. But the Hub certificate will still be needed as your client must check the hub’s identity.

First, download the IoT Hub CA:

curl -sS -O https://iot.s3.nl-ams.scw.cloud/certificates/fr-par/iot-hub-ca.pem
sha1sum iot-hub-ca.pem
# 13cf3e59ed52d4c4b6bc249e85539d5fd5d572fb  iot-hub-ca.pem

Now, extract the certificates from the device JSON files, so that the mosquitto clients may use them:

jq -r '.crt' dev1.json > dev1.crt
jq -r '.key' dev1.json > dev1.key
jq -r '.crt' dev2.json > dev2.crt
jq -r '.key' dev2.json > dev2.key

Finally, run the same test as before, but with the added security:

# In one terminal
mosquitto_sub \
  -h $(jq -r '.endpoint' hub.json) -p 8883 \
  --cert dev1.crt --key dev1.key --cafile iot-hub-ca.pem \
  -i $(jq -r '.device.id' dev1.json) \
  -t mytopic/mysubtopic
# In another terminal
mosquitto_pub \
  -h $(jq -r '.endpoint' hub.json) -p 8883 \
  --cert dev2.crt --key dev2.key --cafile iot-hub-ca.pem \
  -i $(jq -r '.device.id' dev2.json) \
  -t mytopic/mysubtopic \
  -m 'Hello, SECURE world!'

Note: You can mix MQTT and MQTTs clients on the same hub.

Further reading

You can harness the real power of MQTT Pub/Sub with a few more lines of reading.

IoT Hub Events

Device information, such as events or errors, are reported on your Hub under the default topic prefix $SCW/events. For more information on Hub Events, you can read the IoT Hub events documentation.

In the following example we will trigger an event by trying to connect without security on a device where security is required.

First, we connect to previously created dev1 device as events consumer (we want only error events)

# In one terminal
mosquitto_sub \
  -h $(jq -r '.endpoint' hub.json) -p 1883 \
  -i $(jq -r '.device.id' dev1.json) \
  -t '$SCW/events/error/#'

Then we create a secured-only device and try to connect it in insecure mode:

curl -sS -H "X-Auth-Token: $SCW_TOKEN" -d '{
    "hub_id": "'$(jq -r '.id' hub.json)'",
    "name": "my_secured_device",
    "allow_insecure": false
  }' $IOT_API/devices > secure-dev.json

jq < secure-dev.json
# In another terminal
mosquitto_pub \
  -h $(jq -r '.endpoint' hub.json) -p 1883 \
  -i $(jq -r '.device.id' secure-dev.json) \
  -t mytopic/mysubtopic \
  -m 'This should fail'

dev1 will receive following message, on topic $SCW/events/device/<secure-dev-id>/error:

{
  "time":"2020-01-17T15:01:29Z",
  "severity": "error",
  "object-type": "device",
  "object-id": "dev3-id",
  "msg":"connection refused because this device requires mutual TLS authentication",
  "packet":"CONNECT: dup: false qos: 0 retain: false rLength: 48"
}

Routes

Routes are integrations with the Scaleway ecosystem: they can forward MQTT messages to Scaleway services.

The documentation is there: IoT Hub Routes

S3 Route

The S3 route allows you to put the payload of MQTT messages directly into Scaleway’s Object Storage.

Important: This section is an addition to the “Getting started with IoT Hub” above, make sure to follow it first!

As a prerequisite, you should have s3cmd installed and configured for Scaleway. See this guide if you don’t.

BUCKET="my-bucket-$RANDOM" # Buckets are globally unique, suffix with a random number
REGION="fr-par"
PREFIX="iot/messages"
# Create the bucket
s3cmd mb --region "$REGION" "s3://$BUCKET"
# Grant write access to IoT Hub S3 Route Service to your bucket
s3cmd setacl --region "$REGION" "s3://$BUCKET" --acl-grant=write:555c69c3-87d0-4bf8-80f1-99a2f757d031:555c69c3-87d0-4bf8-80f1-99a2f757d031
# Create the IoT Hub S3 Route
curl -sS -H "X-Auth-Token: $SCW_TOKEN" -d '{
    "hub_id": "'$(jq -r '.id' hub.json)'",
    "name": "my-s3-route",
    "topic": "hello/world",
    "bucket_region": "'$REGION'",
    "bucket_name": "'$BUCKET'",
    "object_prefix": "'$PREFIX'",
    "strategy": "per_topic"
  }' $IOT_API/routes/s3 | jq

The output will contain something like:

{
  "id": "e0d1e048-6f67-4c2d-8911-a75f0ef4d787",
  "name": "my-s3-route",
  "organization_id": "<your organization ID>",
  "hub_id": "a15ddd5b-ff73-4fb3-b043-1c9176dae295",
  "topic": "hello/world",
  "created_at": "2018-12-13T15:17:22.052005Z",
  "bucket_region": "fr-par",
  "bucket_name": "my-bucket",
  "object_prefix": "iot/messages",
  "strategy": "per_topic"
}

Now you may publish and see the result!

sleep 5 # wait a little for the route to start
mosquitto_pub \
  -h $(jq -r '.endpoint' hub.json) \
  -i $(jq -r '.device.id' dev2.json) \
  -t hello/world \
  -m 'Hello, world!'

An object iot/messages/hello/world should now be stored in your bucket with “hello world” as contents.

s3cmd get --region "$REGION" "s3://$BUCKET/$PREFIX/hello/world"
cat world

Database Route

Database route allows you to store messages in your database.

When creating such a route for one of you hubs, you need to specify a topic (wildcards are allowed), a database (with valid credentials) and a query to execute. That query may contains $TOPIC and $PAYLOAD variables.

The route will subscribe on this hub to this topic, and execute the query onto the given database for each received message:

  • First, $TOPIC and $PAYLOAD are replaced with the topic and payload of the received MQTT message,
  • Then the generated query is executed

NOTE: The topic database field must be a of text type, and payload a bytea

For now, we support only PostgreSQL database system. You can use a Scaleway Database instance, or any other PostgreSQL instance publicly accessible.

Let's practice

This section is an addition to the “Getting started with IoT Hub” above, make sure you follow it first!

As a prerequisite, you must have a working PostgreSQL database, with valid credentials (username and password).

# Database settings
DBHOST=<your db host>
DBPORT=<your db port>
DBNAME=<your db name>
DBUSER=<your db user>
DBPASS=<your db password>

# Create the target database table
psql -h $DBHOST --port $DBPORT -U $DBUSER -d $DBNAME -c '
  CREATE TABLE messages (
    time timestamp,
    topic text,
    payload bytea
  )
'

# Create the IoT Hub Database Route
# The query will insert message topic and payload with current timestamp
curl -sS -H "X-Auth-Token: $SCW_TOKEN" -d '{
    "name": "my first database route",
    "hub_id": "'$(jq -r '.id' hub.json)'",
    "topic": "hello/world",
    "query": "INSERT INTO messages VALUES (NOW(), $TOPIC, $PAYLOAD)",
    "database": {
      "host": "'$DBHOST'",
      "port": '$DBPORT',
      "dbname": "'$DBNAME'",
      "username": "'$DBUSER'",
      "password": "'$DBPASS'"
    }
  }' $IOT_API/routes/database | jq

The output will contain something like

{
  "id": "486944d3-848e-4886-8945-938fbb828aa6",
  "name": "my first database route",
  "organizationId": "<your organization id>",
  "hubId": "<your hub id>",
  "topic": "hello/world",
  "createdAt": "2019-10-11T13:58:42.710Z",
  "query": "INSERT INTO messages VALUES (NOW(), $TOPIC, $PAYLOAD)",
  "database": {
    "host": "127.0.0.1",
    "port": 5432,
    "dbname": "route_tests",
    "username": "jdoe",
    "password": ""
  }
}

Now you can publish a message and checks it is inserted into the message table.

sleep 5 # wait a little for the route to start
mosquitto_pub \
  -h $(jq -r '.endpoint' hub.json) \
  -i $(jq -r '.device.id' dev2.json) \
  -t hello/world \
  -m 'Hello, world!'
psql -h $DBHOST --port $DBPORT -U $DBUSER -d $DBNAME -c "SELECT * FROM messages"

More advanced examples are available on the database route tips & tricks page.

Rest Route

Rest route allows you call any http(s) endpoint on received MQTT message. You can choose the HTTP verb use to call your REST uri, as well as adding extra headers.

We can see what a rest route would publish on a rest API by simply listening to the port 80 on a public IP.

You can use a Scaleway instance, or any other machine with a public IP address.

On the machine, as root, lauch the following command:

nc -p 80 -l

Define a variable with the public IP address of your server

RESTHOST=<the_public_ip_address>

Let’s create the route

# Create the IoT Hub Rest Route
curl -sS -H "X-Auth-Token: $SCW_TOKEN" -d '{
    "name": "my first rest route",
    "hub_id": "'$(jq -r '.id' hub.json)'",
    "topic": "hello/world",
    "verb": "post",
    "uri": "http://'$RESTHOST'/",
    "headers": {
      "X-My-Header": "Tutorial"
    }
  }' $IOT_API/routes/rest | jq

Now you can publish a message and checks it triggers a request on the server:

sleep 5 # wait a little for the route to start
mosquitto_pub \
  -h $(jq -r '.endpoint' hub.json) \
  -i $(jq -r '.device.id' dev2.json) \
  -t hello/world \
  -m 'Hello, world!'

On your server the above nc output should be:

POST / HTTP/1.1
Host: <the_public_ip_address>
User-Agent: Go-http-client/1.1
Content-Length: 13
X-Mqtt-Retain: false
X-Mqtt-Topic: hello/world
X-My-Header: Tutorial
Accept-Encoding: gzip

Hello, world!

Discover the Cloud That Makes Sense