Another Big Bang

5 May 2022
# Span# DTLS# Gateways# Inbox# Outbox# API

It's been awfully quiet the last few months but for a good reason: We've been busy building support for Internet-connected devices in Span. Up until now we've only supported cellular IoT-based devices but it shouldn't be a big surprise that IoT networks come in all shapes 's not a one-size-fits-all so we decided to add internet connectivity.

This meant we had to suffle around a few assumptions - the biggest one was "devices has SIM cards". Luckily we haven't used the IMSI or IMEI as the primary identifier for devices but a lot of the internals had to be rewritten for this to work.

The end result is quite nice though - and let's be honest - if you can ignore the networking completely and just focus on the firmware itself you can iterate a lot quicker.

With the new internet-enabled devices you can implement a prototype using WiFi with the same CoAP and UDP interface as you'd use in a CIoT device, including firmware updates, then change over to a NB-IoT or LTE-M modem when you do testing outside of the office.

The old architecture

The old setup was conceptually very simple - devices had SIM cards, connected to the network, the network verified the SIM and the device was approved for use and when it connected to the backend server it's relatively trivial to assign each SIM an unique IP address.

Old setup

For the device this is dead simple: If you are connected to the network you're OK and it's Somebody Else's Problem (it's the best kind of problem) to handle the data. If you can't get a connection you're not registered.

The new architecture

Since the devices move out of the mobile network the world is a little bit different. The SIM card is gone and the device is connecting via two different networks:

New setup

Networks is a keyword here. To fix this we're introducing a new structure for devices: They all connect via gateways which again runs on different logical networks.

Gateways and networks

Further down the line we can add more networks and gateways without having to worry about the configuration details for the devices and you can have collections of devices that runs on multiple different networks.

API changes

The change did cause a lot of API changes, in particular when you are creating and configuring devices. The IMSI and IMEI fields are moved into a new configuration object on the device so we have moved from the old-style device like this:

{
 "collectionId": "[collection id]",
 "deviceId": "[device id]",
 "imsi": "[imsi]",
 "imei": "[imei]"
}

...to a device with separate configuration options:

{
 "collectionId": "[collection id]",
 "deviceId": "[device id]",
 "config": {
  "ciot": {
   "imsi": "[imsi]",
     "imei": "[imei]"
    }
   }
}

We'll keep the IMSI and IMEI fields on the device object for a while longer but they will be phased out in a few months.

Second, the message sending has been revamped a lot. The previous implementation was - to be honest - quite clunky and required you to choose the transport and destination port to send a message to the device. We had both a direct send option and a "send after receive"-option. A direct send is not very easy to manage or implement since it requires the device to be online and listening at all times. Most CIoT devices uses a receive window in combination with a power saving scheme, ie they'll send a message, listen for a given number of seconds and then go to deep(er) sleep. If the device was in deep sleep it wouldn't receive sent messages until it came online. This complicates the firmware implementation so we ended up simplifying everything. Rather than POSTing a request like this:

{
 "port": [port number],
 "payload": "[payload]",
 "transport": "[coap|coap-pull|udp|udp-pull]",
 "coapPath": "[path]"
}

...You can just send a simple request with the payload and nothing else:

{
 "payload": "[payload]"
}

The backend service will handle paths and ports automatically for you.

Better inbox - and an outbox

In addition there's an outbox resource with a list of pending messages to the device. If you want to cancel or remove a message to the device you can remove it by sending a DELETE request to the outbox. Pending messages are sent to the device FIFO-style, ie the ordering will be preserved.

Consequently, an outbox means that the data resource is going away sooner or later and will be replaced with an inbox resource. The interface is otherwise unchanged.

Certificates

If we are going to send data to and from devices on the internet we'll need client certificates to secure the devices. We use the client certificates to identify the devices on the server side. Creating certificates is quite straightforward - just use the Span CLI (or your preferred SSL tool) to generate the certificate request.

We support RSA, ECDSA and Ed25519 public keys and the usual array of signature algorithms (MD5/RSA, SHA1/RSA, SHA[256,384,512]/RSA, ECDSA/SHA1, ECDS/SHA[256,385,512], SHA[256,384,512]/RSAPSS and Ed25519.

Ed25519 generates by far the smallest keys but support for these keys are still not fully implemented in f.e. the mbedtls and tinydtls projects so by default we're using ECDSA256 for certificates.

If you wonder what DTLS code looks like you can have a look at the DTLS mbedtls sample or in the LibCoAP DTLS sample.

Next steps

Some of you might have seen the "gateways" section in the console.

A gateway can be just about anything but a reasonable starting point is "a device with more devices behind it". This can be just about anything - a WiFi device with clients on the local network, a BLE device that's part of a bigger mesh or with other BLE devices, a ZigBee gateway or a LoRaWAN gateway.

We're working on a gateway protocol that will support all the things you'd expect from a gateway: Configuration (for both device and gateway), firmware updates, monitoring, device management and payload transports.

This means we'll have to change the API a bit but fortunately the existing API won't get any breaking changes. There might be some additional fields on the device structure but if you stick with the old API clients or handle unknown fields in the API you should be fine.