Sending Inbound Notes and Receiving Acknowledgment
Introduction
This sample app demonstrates a pattern for sending commands from the cloud to a Notecard-connected device, processing the command on the device, and then sending an acknowledgment back to the cloud.
By implementing this pattern you can implement more robust user interfaces for your devices.
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-12μ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 WBGLW | 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
The Notecard is a wireless SoM that offers bidirectional connectivity between a device and cloud applications. This article focuses on data that you wish to send from the cloud to a device, which we refer to as inbound data. For example, a cloud application may wish to tell a device to turn off a connected LED by sending the following inbound Note:
{"command":"led-on"}
When building user interfaces for Notecard-based devices, the UI often needs
to receive some indication that the requested action completed successfully.
For example, after sending {"command":"led-on"}
to a device, the UI may wish
to display a pending indicator while the action is in progress, a completed
indicator once the action has successfully completed, and an error if the
action failed.
The following sections demonstrate how to write firmware and web applications that implement this pattern.
Requirements
-
Host firmware that can:
-
Turn on and off an LED (the host's onboard LED is fine).
-
Receive inbound data.
-
Send outbound acknowledgments as Notes.
-
-
A web or mobile application that sends data to Notehub using the Notehub API.
Technical Implementation
The full source code for this example is available on GitHub.
Firmware
This section covers the most important aspects of writing firmware that sends out acknowledgments. The firmware for this example uses the Arduino Notecard library, although you can implement this pattern using any of the Notecard SDKs.
You may wish to bring up the full source file on GitHub while reading through this section.
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": true
option ensures inbound Notes sync as soon as they're detected on Notehub.
This configuration ensures the device will receive inbound data as soon as possible, which is ideal for any device that needs to quickly respond to commands. 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 receive commands and send acknowledgments from 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.
note.get
After running the hub.set
request, the Notecard will perform a connection with Notehub
and start listening for inbound data. To check whether data has come in, the firmware
calls the note.get
request
in a loop.
void loop()
{
// To hold "led-on" and "led-off". Increase the size if you need to send longer commands.
char command[7];
char id[10];
J *req = notecard.newRequest("note.get");
JAddStringToObject(req, "file", "commands.qi");
JAddBoolToObject(req, "delete", true);
J *rsp = notecard.requestAndResponse(req);
if (notecard.responseError(rsp)) {
notecard.logDebug("No notes available");
command[0] = '\0';
} else {
J *body = JGetObject(rsp, "body");
strncpy(command, JGetString(body, "command"), sizeof(command));
strncpy(id, JGetString(body, "id"), sizeof(id));
}
notecard.deleteResponse(rsp);
...
delay(1000);
}
The code above checks whether any inbound commands.qi
Notes have been
synchronized from Notehub. If it detects any, it extracts the "command"
and "id"
from the inbound Note and saves them in variables.
note.add
Now that the firmware has received a command it needs to take the requested action (turn the light on or off), and then acknowledge that action by sending an outbound Note.
The note.add
request
is the Notecard's main mechanism for working with outbound data. The firmware
uses the function below to send outbound notes for a provided state (e.g. "on"
)
and identifier.
void sendAck(char *state, char *id)
{
J *req = notecard.newRequest("note.add");
if (req != NULL)
{
JAddStringToObject(req, "file", "ack.qo");
JAddBoolToObject(req, "sync", true);
J *body = JAddObjectToObject(req, "body");
if (body)
{
JAddStringToObject(body, "led-state", state);
JAddStringToObject(body, "id", id);
}
notecard.sendRequest(req);
}
}
The above request uses the note.add
request's sync
option, which tells
the Notecard to immediately synchronize this Note to Notehub.
The Notecard uses the configured outbound
interval to determine how often to
to synchronize outbound data, even in continuous mode.
If you want to immediately synchronize data you can either use the note.add
request's sync
option (as shown above), or use the
hub.sync
request.
To put everything together, let's return to the firmware's loop()
, and look at
the new code at the bottom.
void loop()
{
// To hold "led-on" and "led-off". Increase the size if you need to send longer commands.
char command[7];
char id[10];
J *rsp = notecard.requestAndResponse(req);
if (notecard.responseError(rsp)) {
notecard.logDebug("No notes available");
command[0] = '\0';
} else {
J *body = JGetObject(rsp, "body");
strncpy(command, JGetString(body, "command"), sizeof(command));
strncpy(id, JGetString(body, "id"), sizeof(id));
}
notecard.deleteResponse(rsp);
// ⬇️ the new stuff ⬇️
if (!strncmp(command, "led-on", sizeof("led-on")))
{
notecard.logDebug("Turning light on");
digitalWrite(LED_BUILTIN, HIGH);
sendAck("on", id);
}
if (!strncmp(command, "led-off", sizeof("led-off")))
{
notecard.logDebug("Turning light off");
digitalWrite(LED_BUILTIN, LOW);
sendAck("off", id);
}
// Wait one second before looking for changes again
delay(1000);
}
After the Notecard receives a command from the note.get
request, it checks
whether the command is a known command ("led-on"
or "led-off"
). If it
finds a known command it makes the appropriate change to the LED itself,
and then uses the sendAck()
function to send an acknowledgment Note.
Testing the Firmware
To test that the firmware is working correctly you'll need to send an inbound Note to your device. You can do this with the Notehub UI or API as shown here, or use the provided web application below.
If you do want to test with only Notehub API, try sending Notes to the command.qi
Notefile in the following form:
{"command":"led-on","id":"abc123"}
You should see your microcontroller's LED turn on, and you should see a new ack.qo
Note appear as an event in your Notehub project.
{"led-state":"on","id":"abc123"}
Web Application
This section covers the most important aspects of writing user interfaces that send commands and receive acknowledgments. The web application for this example uses Notehub JS.
You may wish to bring up the full source file on GitHub while reading through this section.
Sending Notes
The main task of the user interface is to allow the user to send commands to a device.
To do so the example application uses a Switch component.
When the Switch is toggled on the UI sends an "led-on"
command to Notehub, and when
the Switch is toggled off the UI sends an "led-off"
command to Notehub.
<Switch
onChange={updateLed}
value={ledState}
disabled={isPending}
loading={isPending}
/>
const deviceApiInstance = new NotehubJs.DeviceApi();
const [lastId, setLastId] = React.useState("");
const [isPending, setIsPending] = React.useState(false);
const [ledState, setLedState] = React.useState(false);
const updateLed = (checked: boolean) => {
setLedState(checked);
setIsPending(true);
const id = generateRandomString(10);
setLastId(id);
const note = new NotehubJs.Note();
note.body = {
command: checked ? "led-on" : "led-off",
id,
};
deviceApiInstance
.handleNoteAdd(projectUID, deviceUID, "commands.qi", note)
.then(() => console.log("Successfully added note"))
.catch(console.error);
};
This simple bit of code works, and you can use it to send commands to your device— which is cool!—but it also has one big limitation: there is nothing to stop the user from rapidly clicking the switch and getting it out of sync with the physical device.
This is where acknowledgments come in. With acknowledgment events the UI can disable the switch after sending a command, and wait to enable the switch until it detects a matching acknowledgment event. Let's see how it works.
Reading Notes
There are two primary techniques for receiving updates in a web application: WebSockets and polling. This application takes the simpler approach, polling, and uses Notehub JS to retrieve the latest acknowledgment event every few seconds.
Here is the function that performs the call itself. Notice how the code checks
to ensure that the acknowledgment id
matches before it calls setIsPending(false)
,
which re-enables the switch.
const eventApiInstance = new NotehubJs.EventApi();
const [lastId, setLastId] = React.useState("");
const [isPending, setIsPending] = React.useState(false);
const [ledState, setLedState] = React.useState(false);
const getLatestValue = () => {
eventApiInstance
.getProjectEvents(projectUID, {
deviceUID: [deviceUID],
files: ["ack.qo"],
pageSize: 1,
sortBy: "captured",
sortOrder: "desc",
})
.then((data: EventsResponse) => {
if (!data || !data.events || data.events.length === 0) {
return;
}
const state = data.events[0].body["led-state"];
const id = data.events[0].body.id;
if (lastId && id !== lastId) {
return;
}
setLedState(state === "on");
setIsPending(false);
})
.catch(console.error);
};
Finally, the code uses the browser's setInterval
function to have the
getLatestValue()
run every POLL_INVERVAL_MS
(5 seconds, by default).
React.useEffect(() => {
const intervalId = setInterval(getLatestValue, POLL_INVERVAL_MS);
return () => clearInterval(intervalId);
}, [lastId]);
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 UI receives acknowledgment when the action completed.
In your Notehub project you should see a pair of command.qi
and ack.qo
events every time you perform a command in the app.
Potential Issues
This example, while powerful, does have a few limitations.
Multiple Interfaces
The current implementation assumes that only a single interface is being used to send commands to the device. If you need your dashboard to stay accurate while being used by multiple devices simultaneously, you'll need to adjust the logic that loads events.
Specifically, you'll need to alter the event code to retrieve multiple acknowledgment events (in case other devices sent acknowledged commands), and cycle through them to determine the device's current state, and to ensure your interface's command was processed.
Error Handling
You may want to implement error handling in your firmware to transmit error codes when commands fail, and update your user interface to handle these error codes appropriately.
Additional Resources
- Source code - The source code for this example.
- Remote Command and Control - A guide to sending 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.