---
title: Controlling Device State and Receiving Acknowledgment
description: This sample app shows how to control device state from a web app using DB Notefiles, and how the device acknowledges changes by reporting the state it actually has.
source_url: https://dev.blues.io/example-apps/samples/controlling-device-state-and-receiving-acknowledgment/
canonical_url: https://dev.blues.io/example-apps/samples/controlling-device-state-and-receiving-acknowledgment/
markdown_url: https://dev.blues.io/example-apps/samples/controlling-device-state-and-receiving-acknowledgment.md
---

# Controlling Device State and Receiving Acknowledgment

## Introduction

This sample app demonstrates how to control a device from the cloud by storing the device's state in a [DB Notefile](https://dev.blues.io/guides-and-tutorials/notecard-guides/storing-device-state-with-db-notefiles.md). A web app writes the state it wants the device to have, the device applies that state, and the device then acknowledges the change by reporting the state it actually has — giving the web app a reliable way to show pending and completed changes.

![An example of controlling device state in a web app](https://dev.blues.io/images/example-apps/acknowledgments/example.gif?v=672f832e)

### Wireless Connectivity with Blues

This sample app is built around the [Blues Notecard](https://blues.com/products/notecard/) and [Blues Notehub](https://blues.com/notehub/).

The **Blues Notecard** is the easiest way for developers to add secure, robust, and affordable pre-paid wireless connectivity to their microcontroller or single-board computer of choice. Notecard is a System-on-Module (SoM) that combines pre-paid data, low-power hardware (\~8μA-18μA when idle), and secure communications. It acts as a device-to-cloud data pump to communicate with the Blues cloud service Notehub.

**Notehub** is the Blues cloud service for routing Notecard-provided data to third-party cloud applications, deploying OTA firmware updates, and securely managing fleets of Notecards. Notehub allows for secure communications between edge devices and the cloud without certificate management or manual provisioning of devices.

## General Information

### System Hardware

You can use any Notecard and any host microcontroller to implement this article's pattern. However, this example will demonstrate using the following hardware.

| Component                                                                                                                                          | Purpose                                                             |
| -------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------- |
| [Blues Notecard Cellular WBGLWT](https://shop.blues.com/products/notecard-cell-wifi?utm_source=dev-blues\&utm_medium=web\&utm_campaign=store-link) | Wireless connectivity module enabling device-to-cloud data syncing. |
| [Blues Notecarrier F](https://shop.blues.com/products/notecarrier-f?utm_source=dev-blues\&utm_medium=web\&utm_campaign=store-link)                 | Carrier board for connecting Notecard to an MCU.                    |
| [Blues Swan](https://shop.blues.com/collections/swan/products/swan?utm_source=dev-blues\&utm_medium=web\&utm_campaign=store-link)                  | Example host MCU.                                                   |

### List of Acronyms

| Acronym | Definition       |
| ------- | ---------------- |
| MCU     | Microcontroller  |
| SoM     | System-on-Module |

## Summary

An LED that a web app can turn on and off is device *state*: it has one current value, and both the device and the cloud need to read and change it. The hard part is that a cloud app can't just assume its changes took effect—the device might be asleep, offline, or mid-sync—so the app needs a way to know the state the device *actually* has, not just the state it last asked for.

The Notecard's DB Notefiles are built for exactly this. A DB Notefile is a database of Notes that is bidirectionally replicated between a Notecard and Notehub, where each Note has a name and a JSON body, and both sides can read and write it.

This sample stores the LED's state as two Notes in the Notecard's default `vars.db` Notefile, using the [`var` requests](https://dev.blues.io/api-reference/notecard-api/var-requests/latest.md):

| Note           | Written by   | Meaning                                                      |
| -------------- | ------------ | ------------------------------------------------------------ |
| `led-desired`  | The web app  | The state the user wants the LED to have                     |
| `led-reported` | The firmware | The state the LED actually has — the device's acknowledgment |

The flow works like this:

1. The user flips a switch in the web app, which writes `led-desired` through the Notehub API and shows a pending indicator.
2. The change syncs to the Notecard, and the firmware notices that `led-desired` no longer matches the LED.
3. The firmware changes the LED, then acknowledges the change by writing the new state to `led-reported`, which syncs back to Notehub.
4. The web app sees that `led-reported` now matches what it asked for — the acknowledgment — and clears the pending indicator.

Because each Note is written by exactly one side, the two sides never edit the same Note concurrently, so [synchronization conflicts](https://dev.blues.io/guides-and-tutorials/notecard-guides/storing-device-state-with-db-notefiles.md#handling-conflicting-changes) never come into play. And because the acknowledgment is the device's *current* state rather than a stream of one-off messages, any number of dashboards can control the same device and all of them converge on the truth.

### Requirements

1. Host firmware that can:

   1. Turn on and off an LED (the host's onboard LED is fine).

   2. Read and write variables in a DB Notefile.

2. A web or mobile application that reads and writes the same variables using the Notehub API.

## Technical Implementation

The full source code for this example is [available on GitHub](https://github.com/tjvantoll/notecard-ack-example).

> **Tip:**
>
> **Let AI write your firmware.** Blues Expert MCP connects your AI coding assistant (Claude Code, GitHub Copilot, Cursor) directly to our API docs, providing live request validation and firmware best practices for Arduino, C, Zephyr, and Python. [Install the Blues Expert MCP →](https://dev.blues.io/tools-and-sdks/generative-ai-tools/blues-expert-mcp.md)

### Firmware

This section covers the most important aspects of writing firmware that keeps a device's state in sync with the cloud. The firmware for this example uses the [Arduino Notecard library](https://dev.blues.io/tools-and-sdks/firmware-libraries/arduino-library.md), although you can implement this pattern using any of the [Notecard firmware libraries](https://dev.blues.io/tools-and-sdks/firmware-libraries.md).

#### hub.set

The [`hub.set` request](https://dev.blues.io/api-reference/notecard-api/hub-requests/latest.md#hub-set) controls how a Notecard connects to Notehub. This example's firmware uses the following configuration.

```c
J *req = notecard.newRequest("hub.set");
JAddStringToObject(req, "product", productUID);
JAddStringToObject(req, "mode", "continuous");
JAddBoolToObject(req, "sync", true);
if (!notecard.sendRequest(req)) {
  JDelete(req);
}
```

- The `"mode": "continuous"` option places the Notecard in continuous mode, which has it maintain a constant network connection with Notehub.
- The `"sync": true` option ensures changes made in Notehub — including changes to DB Notefiles like `vars.db` — sync to the Notecard as soon as Notehub detects them.

This configuration ensures the device will receive state changes as soon as possible, which is ideal for any device that needs to quickly respond to its users. However, this configuration does mean the device will use considerably more battery to maintain the connection to Notehub than it would with a periodic connection.

You can use this sample's pattern with devices in periodic mode. However, when in periodic mode your Notecard will not send/receive instantly, and instead those intervals will be determined by your [configured `inbound` and `outbound` options](https://dev.blues.io/notecard/notecard-walkthrough/essential-requests.md#setting-sync-times-for-outbound-and-inbound-data).

#### Initializing the State on Startup

The firmware stores its state in two variables that live in the Notecard's default `vars.db` DB Notefile: `led-desired` (the state the user wants, written by the web app) and `led-reported` (the state the LED actually has, written by the firmware). At the end of `setup()`, the firmware makes sure both variables exist, and then forces an immediate sync so they appear in Notehub right away.

```c
void setup()
{
  // ...Notecard and LED initialization shown above...

  // Make sure both state variables exist: led-desired for the web app
  // to write, and led-reported for the web app to read.
  initializeLedDesired();
  reportLedState();

  // Sync with Notehub immediately, so the variables appear there right
  // away rather than waiting for the next periodic sync.
  J *req = notecard.newRequest("hub.sync");
  notecard.sendRequest(req);
}
```

The `initializeLedDesired()` function creates `led-desired` with a default value if it doesn't exist yet (the `{note-noexist}` error), so the variable is always available for the web app to read and update. (`reportLedState()` is covered in [Acknowledging Changes](#acknowledging-changes) below.)

```c
// Create the led-desired variable with a default value if it doesn't
// exist yet.
void initializeLedDesired()
{
  J *req = notecard.newRequest("var.get");
  JAddStringToObject(req, "name", "led-desired");

  J *rsp = notecard.requestAndResponse(req);
  if (notecard.responseError(rsp) &&
      NoteResponseErrorContains(rsp, "{note-noexist}")) {
    J *set = notecard.newRequest("var.set");
    JAddStringToObject(set, "name", "led-desired");
    JAddBoolToObject(set, "flag", false);
    notecard.sendRequest(set);
  }
  notecard.deleteResponse(rsp);
}
```

#### Reading the Desired State

To check what state the user currently wants, the firmware calls the [`var.get` request](https://dev.blues.io/api-reference/notecard-api/var-requests/latest.md#var-get) in a loop.

```c
bool ledState = false;

void loop()
{
  J *req = notecard.newRequest("var.get");
  JAddStringToObject(req, "name", "led-desired");

  J *rsp = notecard.requestAndResponse(req);
  if (!notecard.responseError(rsp)) {
    bool desired = JGetBool(rsp, "flag");
    if (desired != ledState) {
      notecard.logDebug(desired ? "Turning light on\n" : "Turning light off\n");
      digitalWrite(LED_BUILTIN, desired ? HIGH : LOW);
      ledState = desired;
      reportLedState();
    }
  }
  notecard.deleteResponse(rsp);

  // Wait one second before checking again.
  delay(1000);
}
```

A few things worth noting in this code:

- The firmware only acts when the desired state differs from the LED's actual state, so processing the same value repeatedly is harmless.
- Because the firmware reads a current value rather than consuming a queue of commands, a device that was offline doesn't replay every change it missed — it simply applies the latest state.

> **Note:**
>
> Polling `var.get` once a second is simple and works well for a device that's already in continuous mode. For battery-powered designs, the Notecard's attention pin can instead interrupt the host when a watched Notefile changes. See the [Attention Pin Guide](https://dev.blues.io/guides-and-tutorials/notecard-guides/attention-pin-guide.md) for details.

#### Acknowledging Changes

After changing the LED, the firmware acknowledges the change by writing the state the device actually has to a second variable, `led-reported`, using the [`var.set` request](https://dev.blues.io/api-reference/notecard-api/var-requests/latest.md#var-set).

```c
void reportLedState()
{
  J *req = notecard.newRequest("var.set");
  JAddStringToObject(req, "name", "led-reported");
  JAddBoolToObject(req, "flag", ledState);
  JAddBoolToObject(req, "sync", true);
  notecard.sendRequest(req);
}
```

The request's `"sync": true` option tells the Notecard to synchronize the change to Notehub immediately, rather than waiting for the next outbound sync interval, so the web app learns about the change as quickly as possible.

Keeping `led-desired` and `led-reported` as two separate Notes is deliberate: the web app only ever writes `led-desired`, and the firmware only ever writes `led-reported`. Because each Note has a single writer, the device and the cloud never change the same Note at the same time, and [synchronization conflicts](https://dev.blues.io/guides-and-tutorials/notecard-guides/storing-device-state-with-db-notefiles.md#handling-conflicting-changes) can't occur.

#### Testing the Firmware

To test that the firmware is working correctly, change the `led-desired` variable in Notehub. The easiest way is through the Notefile explorer: open your device in Notehub, go to the **Notefiles** tab, open `vars.db`, and edit the `led-desired` Note's body to `{"flag": true}`.

![Editing vars.db Notes in Notehub](https://dev.blues.io/images/example-apps/acknowledgments/editing-vars-db.png?v=0e5d7e7f)

> **Tip:**
>
> You can also make the same change with the [Update DB Note](https://dev.blues.io/api-reference/notehub-api/device-api.md#update-db-note) Notehub API request.
>
> ```bash
> curl -X PUT
>     -L 'https://api.notefile.net/v1/projects/<projectUID>/devices/<deviceUID>/notes/vars.db/led-desired'
>     -H 'Authorization: Bearer <access_token>'
>     -d '{"body": {"flag": true}}'
> ```

You should see your microcontroller's LED turn on, and moments later the `led-reported` Note in the Notefile explorer should read `{"flag": true}`, marked as last updated by Notecard.

![Result of vars.db update in Notehub](https://dev.blues.io/images/example-apps/acknowledgments/editing-vars-db-2.png?v=bc00d466)

> **Note:**
>
> If you repeat this test to turn the LED *off*, you'll see `led-reported`'s body become `{}` rather than `{"flag": false}`. This is expected: the Notecard omits empty values, including `false` booleans, when it serializes a variable—so an empty body is how `var.set` represents a `false` flag. Any code that reads variables needs to treat a missing `flag` as `false`, as the web app below does.

### Web Application

This section covers the most important aspects of writing user interfaces that control device state. The web application for this example is a Next.js app that uses [Notehub JS](https://dev.blues.io/tools-and-sdks/notehub-sdks/notehub-js-library.md) to communicate with Notehub.

#### Writing the Desired State

The main task of the user interface is to let the user change the device's state. To do so the example application uses a [Switch component](https://ant.design/components/switch). When the user toggles the Switch, the UI sends the new desired state to the API route and shows a pending indicator.

```html
<Switch
  onChange={updateLed}
  value={ledState}
  disabled={isPending}
  loading={isPending}
/>
```

```js
const [isPending, setIsPending] = React.useState(false);
const [ledState, setLedState] = React.useState(false);

const updateLed = (checked: boolean) => {
  setLedState(checked);
  setIsPending(true);

  fetch("/api/led", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ flag: checked }),
  }).catch(console.error);
};
```

#### Receiving the Acknowledgment

To know when the device has actually applied a change, and to stay accurate when the state changes some other way, the UI polls the API route (and through it, the `led-reported` Note) every few seconds.

```js
const getReportedState = () => {
  fetch("/api/led", { cache: "no-store" })
    .then((response) => response.json())
    .then((data) => {
      if (isPending) {
        // Waiting on the device: clear the pending indicator once the
        // device reports the state the user asked for.
        if (data.flag === ledState) {
          setIsPending(false);
        }
      } else {
        // Not waiting on anything: show whatever the device reports.
        setLedState(data.flag);
      }
    })
    .catch(console.error);
};

React.useEffect(() => {
  getReportedState();
  const intervalId = setInterval(getReportedState, POLL_INTERVAL_MS);
  return () => clearInterval(intervalId);
}, [ledState, isPending]);
```

This is also what keeps multiple interfaces honest. If two dashboards control the same device, or someone flips the LED with a physical button on the device itself, every UI polling `led-reported` converges on the device's actual state.

### Expected Results

And with that, you should be good to go! Try running the web application, and you should see the switch change to a disabled and pending state, and become reenabled seconds later when the device reports that the change was applied.

![An example of controlling device state in a web app](https://dev.blues.io/images/example-apps/acknowledgments/example.gif?v=672f832e)

In your Notehub project's Notefile explorer, you should see the `led-desired` and `led-reported` Notes update every time you flip the switch.

### Potential Issues

This example, while powerful, does have a few limitations.

#### One-Shot Commands

DB Notefiles represent *state*—a current value. If you need to tell a device to perform an *action*, especially one that isn't safe to repeat or skip (reboot, dispense an item, run a calibration), an inbound queue (`.qi`) is the right tool, because each command Note is delivered and consumed exactly once. See the [Remote Command and Control guide](https://dev.blues.io/guides-and-tutorials/notecard-guides/remote-command-and-control.md) for that pattern.

#### Error Handling

If the device can fail to apply a state change, the firmware can report that too—for example by writing error details into the Notefile with a `note.update` request that gives `led-reported` a richer body, such as `{"flag":false,"error":"led-driver-fault"}`. Your interface can then surface the error rather than waiting on a pending indicator that will never clear.

#### Polling

Both sides of this example poll: the firmware calls `var.get` once a second, and the web app calls the Notehub API every few seconds. That's simple and fine for a demo, but for production you may want the firmware to use the [attention pin](https://dev.blues.io/guides-and-tutorials/notecard-guides/attention-pin-guide.md) instead of polling, and the web app to use [Notehub routes](https://dev.blues.io/notehub/notehub-walkthrough.md#routing-data-with-notehub) to push state changes to your backend rather than polling for them.

## Additional Resources

- [Source code](https://github.com/tjvantoll/notecard-ack-example) - The source code for this example.
- [Storing Device State with DB Notefiles](https://dev.blues.io/guides-and-tutorials/notecard-guides/storing-device-state-with-db-notefiles.md) - A guide to managing device state with DB Notefiles and the `var` API.
- [Remote Command and Control](https://dev.blues.io/guides-and-tutorials/notecard-guides/remote-command-and-control.md) - A guide to sending one-shot commands to a Notecard from the cloud.
- [Notehub JS](https://dev.blues.io/tools-and-sdks/notehub-sdks/notehub-js-library.md) - The official library for working with the Notehub API in JavaScript.
- [note-arduino](https://dev.blues.io/tools-and-sdks/firmware-libraries/arduino-library.md) - The official Arduino library for communicating with the Notecard over serial or I2C.
