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. 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.

Wireless Connectivity with Blues
This sample app is built around the Blues Notecard and Blues 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 | Wireless connectivity module enabling device-to-cloud data syncing. |
| Blues Notecarrier F | Carrier board for connecting Notecard to an MCU. |
| Blues Swan | 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:
| 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:
- The user flips a switch in the web app, which writes
led-desiredthrough the Notehub API and shows a pending indicator. - The change syncs to the Notecard, and the firmware notices that
led-desiredno longer matches the LED. - The firmware changes the LED, then acknowledges the change by writing
the new state to
led-reported, which syncs back to Notehub. - The web app sees that
led-reportednow 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 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
-
Host firmware that can:
-
Turn on and off an LED (the host's onboard LED is fine).
-
Read and write variables in a DB Notefile.
-
-
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.
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 →
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, although you can implement this pattern using any of the Notecard firmware libraries.
hub.set
The hub.set request
controls how a Notecard connects to Notehub. This example's firmware uses the
following configuration.
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": trueoption ensures changes made in Notehub — including changes to DB Notefiles likevars.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.
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.
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 below.)
// 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
in a loop.
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.
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
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.
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
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}.

You can also make the same change with the Update DB Note Notehub API request.
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.

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 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. When the user toggles the Switch, the UI sends the new desired state to the API route and shows a pending indicator.
<Switch
onChange={updateLed}
value={ledState}
disabled={isPending}
loading={isPending}
/>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.
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.

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
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
instead of polling, and the web app to use
Notehub routes to
push state changes to your backend rather than polling for them.
Additional Resources
- Source code - The source code for this example.
- Storing Device State with DB Notefiles -
A guide to managing device state with DB Notefiles and the
varAPI. - Remote Command and Control - A guide to sending one-shot commands to a Notecard from the cloud.
- Notehub JS - The official library for working with the Notehub API in JavaScript.
- note-arduino - The official Arduino library for communicating with the Notecard over serial or I2C.