Better IoT design patterns: Desired state vs. actual state

Communicating with IoT devices over a network connection presents a few challenges you must consider when designing your firmware. The most obvious is how to deal with spotty connections. Did your device get the command you sent? Does the reported state in the cloud accurately reflect the actual device state?

At Golioth we’ve built a device cloud that eases the process of provisioning, connecting with, and controlling fleets of IoT devices. To get the most out of the platform consider using a few design patterns related to Digital Twin, a virtual representation of a device that is present in the cloud and provides a framework for interacting with the real hardware.

I’ll demonstrate this using the Golioth platform, but of course the concept is applicable to all connected devices.

Avoiding Confused Users and Confused Devices

While there are innumerable ways in the IoT realm to confuse your users and your devices (trust me, I’m trying to find them all), the most obvious is the challenge keeping the device and the cloud in sync with each other. For ease of understanding, let’s consider a box with one button and one LED that is connected to the internet over a cellular connection. We want the button to toggle the LED but also allow the cloud side to change the state of the LED. This might represent the state of some running service, but to keep it simple we’ll leave that out of the example.

If we rely on the cloud to control the device LED, the user will push the button and have to wait for the button press to make it to the cloud, wait for the state of LED on the cloud to be changed, and wait for this change to make its way back to the device to update the LED. The user is likely to think the button press was missed and press it again.

If the device directly controls the LED, what happens if the cloud misses the button press because of connectivity issues? When not carefully planned for, the state of the LED will be different from the state of the service in the cloud. What happens if the LED state on the cloud is changed at almost the same time the button is pressed? We have a race condition to see which update propagates the fastest.

One design pattern we should all be familiar with uses a separation of state from command/control so that the device and its digital twin will remain in sync even when there are latency or connection issues. A device just coming on line should be able to quickly enter a desired state from the server, and report back the actual state.

Keeping Desired State and Actual State Separate

The issues outline above can be avoided by using different endpoints for a device to communicate with its virtual equivalent.

  • On the cloud there will be one representation where the physical device reports its current state. For the cloud this data is read-only, only the device can make updates to it.
  • A separate representation is made for the desired state of the device. The cloud side can make changes to this and the physical device will watch for and react to these changes. The physical device usually has a mechanism that indicates the desired operations were received.

Digital twin uses both a desired state and an actual stateOur simple example only has one “actual state” variable that keeps track of the LED state. A server might watch this data via a WebSocket to toggle some service as the value changes.

The cloud can populate the “desired state” with endpoints that request changes to the LED status. This might happen when some service the cloud is watching changes state and the IoT device needs to be aware of that.

Demo Using Golioth

Golioth stores persistent data using LightDB state. We can use a simple data structure to represent the digital twin of our hypothetical IoT device:

[sourcecode lang=”yaml”]
{
"state": {
"led": 0
},
"cmd": {
"led_on": 1649691282
}
}
[/sourcecode]

The state endpoint stores the last-reported state (on) of the device’s LED. The cmd endpoint reflects the cloud’s desire for that LED to be on by setting the led_on key to the current timestamp. The cloud could also set led_off to a timestamp, and when the device acts upon the desired state, it can compare timestamps for all existing keys to establish which command is the most recent.

I like to have the IoT device delete the command keys once they are received (another approach would be to have the device set an acknowledged value to the timestamp the desired state was serviced). In the above example, since the led_on key exists, we assume the device has not yet seen it. Once it does, the device will turn on the LED, update the status key for that LED to 1, and delete the cmd/led_on endpoint.

Our app can be set up to watch the desired state by observing the cmd endpoint and running an on_update function when a change is detected.

[sourcecode lang=”cpp”]
err = golioth_lightdb_observe(client,
GOLIOTH_LIGHTDB_PATH("cmd"),
COAP_CONTENT_FORMAT_TEXT_PLAIN,
observe_reply, on_update);
[/sourcecode]

The IoT device will write a simple string of “0” or “1” as the actual state when the button is pressed, and each time the device acts upon a desired state request.

[sourcecode lang=”cpp”]
char sbuf[2] = "0";
if (led_state & 0x01) {
sbuf[0] = ‘1’;
}
int err = golioth_lightdb_set(client,
GOLIOTH_LIGHTDB_PATH("state/led"),
COAP_CONTENT_FORMAT_TEXT_PLAIN,
sbuf, strlen(sbuf));
[/sourcecode]

So let’s imagine that our IoT device is in power-saving mode. When it wakes up and connects to the network, it will immediately see the command endpoint is requesting an LED state change. Using this design pattern means your desired state changes are not missed; even when a network connection is unavailable the device will be able to update to the most recent commands the next time it connects.

You can try this out for yourself on the Golioth cloud right now. I’ve implemented the desired state pattern in sample code, and the Dev tier of Golioth lets you test 50 devices on our platform for free.

Further Learning

At its core, this design pattern is incredibly simple. When you put it into use, things may become more complex. Perhaps you want to have a command queue so that there is a history of changes for the device to review the next time it connects to a network. Instead of deleting the commands when acting upon them, a device may place an acknowledged timestamp next to them, or some other scheme that fits your needs. But at its core, the separation between a device reporting it’s state and the cloud requesting state changes is an excellent IoT approach, and a great way to start learning about the concept of digital twins.

Mike Szczys
Mike Szczys
Mike is a Firmware Engineer at Golioth. His deep love of microcontrollers began in the early 2000s, growing from the desire to make more of the BEAM robotics he was building. During his 12 years at Hackaday (eight of them as Editor in Chief), he had a front-row seat for the growth of the industry, and was active in developing a number of custom electronic conference badges. When he's not reading data sheets he's busy as an orchestra musician in Madison, Wisconsin.

Post Comments

More from this author

Related posts

spot_img

Latest posts

Provisioning Devices over NFC

There are a hundred ways to provision a device, and with this NFC-based hack day project there are now 101. Follow along as we us a Nordic nRF53 as a writable NFC tag that parses the NDEF message, locates PSK-ID and PSK, then stores those credentials to add the device to an IoT fleet.

Unwrapping Certificates

Certificates are the most secure way to connect your device to the cloud. This article reduces confusion surrounding them with simple examples.

A Device That Can’t Be Updated Is a Device That Can’t Be Trusted

Over-the-air updates are a crucial part of building and deploying secured devices, yet many product companies skip this step. This post outlines why it's difficult and how Golioth is making it easier.

Want to stay up to date with the latest news?

Subscribe to our newsletter and get updates every 2 weeks. Follow the latest blogs and industry trends.