---
title: Remote Command and Control
description: A guide to sending commands to one or more Notecards from the cloud over cellular, Satellite, LoRa, or WiFi.
source_url: https://dev.blues.io/guides-and-tutorials/notecard-guides/remote-command-and-control/
canonical_url: https://dev.blues.io/guides-and-tutorials/notecard-guides/remote-command-and-control/
markdown_url: https://dev.blues.io/guides-and-tutorials/notecard-guides/remote-command-and-control.md
---

# Remote Command and Control

The Notecard's communication with its cloud backend, Notehub, is bidirectional. This means that in addition to sending local data from the Notecard to the cloud, you can also send data or commands in the opposite direction (cloud to device) using a pattern we call command and control.

Because the Notecard is a generic data pump, the commands you send to your devices can be as simple or as complex as you need them to be. For example you could tell a light to turn off, a robot to move left, or a machine to stop operating.

The Notecard allows you to send these commands remotely, giving you the ability to perform actions on devices located anywhere in cellular range. Let's look at how it works.

## Sending Commands to Devices

The easiest way to send a command to a device is with the Notehub API's [`note.add` request](https://dev.blues.io/api-reference/notecard-api/note-requests.md#note-add).

> **Note:**
>
> If you're new to the Notehub API, you'll want to read our [Notehub API tutorial](https://dev.blues.io/guides-and-tutorials/using-the-notehub-api.md) before continuing, as the tutorial shows you the basics of using the API, including how to generate a bearer token.

To use the `note.add` request you need to send a `POST` to the Notehub API's `/projects/<projectUID>/devices/<deviceUID>/notes/<file>` endpoint, where `projectUID` is your ProjectUID, `deviceUID` is the DeviceUID of a device within your Notehub project, and `<file>` is the name of the Notefile you'd like to use (e.g. `data.qi`).

> **Note:**
>
> The `<file>` you send commands to must be an *inbound* Notefile, which uses the `.qi` (queue inbound) extension. Outbound Notefiles (`.qo`) are reserved for data flowing from the device up to Notehub and cannot be used to deliver commands to a device. Notehub's default inbound Notefile is `data.qi`.

The `body` of the `note.add` request can be any JSON object you'd like. For example, the `curl` command below shows how to send `{"command": "on"}` to a device.

```bash
curl -X POST
     -L 'https://api.notefile.net/v1/projects/<projectUID>/devices/<deviceUID>/notes/<file>'
     -H 'Authorization: Bearer <access_token>'
     -d '{"body": {"command": "on"}}'
```

## Receiving Commands on a Device

Once you have Notes queued in Notehub, you next need to receive those Notes on your device. You can configure how often your device checks for inbound notes using the [`hub.set` request's](https://dev.blues.io/api-reference/notecard-api/hub-requests.md#hub-set) `mode`, `inbound`, and `sync` arguments.

For example, running the command below places a Notecard in continuous mode. Devices in continuous mode maintain a constant connection with Notehub, but also use considerably more battery to maintain the connection. Setting the request's `sync` argument to `true` ensures inbound notes sync as soon as they're detected on Notehub.

**JSON**

```json
{
  "req": "hub.set",
  "mode": "continuous",
  "sync": true
}
```

**C/C++**

```cpp
J *req = NoteNewRequest("hub.set");
JAddStringToObject(req, "mode", "continuous");
JAddBoolToObject(req, "sync", true);

NoteRequest(req);
```

**Python**

```python
req = {"req": "hub.set"}
req["mode"] = "continuous"
req["sync"] = True

card.Transaction(req)
```

> **Note:**
>
> Check out our [minimizing latency guide](https://dev.blues.io/guides-and-tutorials/notecard-guides/minimizing-latency.md) for additional information on sending low-latency commands.

If your project is more battery-conscious, you may wish to instead place your device in periodic mode. Devices in periodic mode check for inbound Notes at a given interval, which you can specify with the `inbound` argument (in minutes).

The request below tells the Notecard to check for inbound Notes every 60 minutes.

**JSON**

```json
{
  "req": "hub.set",
  "mode": "periodic",
  "inbound": 60
}
```

**C/C++**

```cpp
J *req = NoteNewRequest("hub.set");
JAddStringToObject(req, "mode", "periodic");
JAddNumberToObject(req, "inbound", 60);

NoteRequest(req);
```

**Python**

```python
req = {"req": "hub.set"}
req["mode"] = "periodic"
req["inbound"] = 60

card.Transaction(req)
```

> **Note:**
>
> If you place your device in periodic mode, you can use the [`hub.sync` request](https://dev.blues.io/api-reference/notecard-api/hub-requests.md#hub-sync) while prototyping, as it triggers a synchronization of Notes between your Notecard and Notehub, even if your device is in periodic mode.

Regardless of the configuration you use, once your device has received Notes you can check if any are present by running a [`note.changes` request](https://dev.blues.io/api-reference/notecard-api/note-requests.md#note-changes).

The `note.changes` request returns multiple Notes (if present), so you can use the request to process multiple commands at once.

**JSON**

```json
{
  "req": "note.changes",
  "file": "data.qi"
}
```

**C/C++**

```cpp
J *req = NoteNewRequest("note.changes");
JAddStringToObject(req, "file", "data.qi");

NoteRequest(req);
```

**Python**

```python
req = {"req": "note.changes"}
req["file"] = "data.qi"

rsp = card.Transaction(req)
```

Resulting in the following JSON response:

```json
{
  "notes": {
    "1:8572": {
      "body": {
        "command": "on"
      },
      "time": 1667855195
    }
  },
  "total": 1
}
```

If you want to work with one Note at a time, you can alternatively use the [`note.get` request](https://dev.blues.io/api-reference/notecard-api/note-requests.md#note-get) to retrieve the next Note waiting in the Notefile.

**JSON**

```json
{
  "req": "note.get",
  "file": "data.qi",
  "delete": true
}
```

**C/C++**

```cpp
J *req = NoteNewRequest("note.get");
JAddStringToObject(req, "file", "data.qi");
JAddBoolToObject(req, "delete", true);

NoteRequest(req);
```

**Python**

```python
req = {"req": "note.get"}
req["file"] = "data.qi"
req["delete"] = True

rsp = card.Transaction(req)
```

Resulting in the following JSON response:

```json
{
  "body": {
    "command": "on"
  },
  "time": 1667855195
}
```

If no notes are available the `note.get` request returns a `{note-noexist}` error.

```json
{ "err": "no notes available {note-noexist}" }
```

> **Note:**
>
> Both the `note.changes` and `note.get` requests allow you to pass a `delete` argument, which controls whether to delete Note(s) after you retrieve them from the Notefile.

At this point you've now seen how to send commands to devices using the Notehub API, as well as how to receive those commands using the Notecard. To put everything together, let's look at an example of how you can use a host to receive commands from the Notecard, and then take action.

## Receiving Commands on a Host

When implementing a command-and-control architecture you may want to receive commands on a host microcontroller or single-board computer. A host MCU makes it possible to receive commands and take action, for example using commands like `{"command":"on"}` and `{"command":"off"}` as a trigger for turning on and off a light, respectively.

We provide [Notecard libraries](https://dev.blues.io/tools-and-sdks/firmware-libraries.md) that allow you to communicate with the Notecard on a wide variety of hosts.

In this section you'll see the steps you need to take using the Notecard libraries to implement a command-and-control architecture.

As an example, we'll show how to use the [Notecard Arduino library](https://dev.blues.io/tools-and-sdks/firmware-libraries/arduino-library.md) running on a [Blues Swan](https://dev.blues.io/feather-mcus.md), but you can use the same steps to implement this architecture with any Notecard library and virtually any MCU.

> **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)

### Step 1: Configure Your Notecard

As a first step, remember that you must configure your Notecard so that it can receive commands. You can do this either in the [In-Browser Terminal](https://dev.blues.io/terminal/) using the configuration below (remembering to substitute `YOUR_PRODUCTUID_HERE`) with your own value.

```json
{
  "req": "hub.set",
  "product": "YOUR_PRODUCTUID_HERE",
  "mode": "continuous",
  "sync": true
}
```

Or you can set up your Notecard using one of the Notecard's libraries. For example, the code below shows how to set up a Notecard to immediately receive inbound Notes using the Notecard's Arduino SDK.

```cpp
J *req = notecard.newRequest("hub.set");
JAddStringToObject(req, "product", "YOUR_PRODUCTUID_HERE");
JAddStringToObject(req, "mode", "continuous");
JAddBoolToObject(req, "sync", true);
notecard.sendRequest(req);
```

### Step 2: Check for Inbound Notes

Once you have your Notecard set up, you next need to check for inbound notes. The most common way to do this check is with the `note.changes` or `note.get` requests discussed in the [previous section](#receiving-commands-on-a-device).

As another option, you can also configure the Notecard to use an interrupt that allows you to use the `ATTN` pin to wake your host when you receive a new Note in a specified Notefile. If you're interested in that approach, check out [Handling Notecard Interrupts](https://dev.blues.io/notecard/notecard-walkthrough/inbound-requests-and-shared-data.md#handling-notecard-interrupts).

Regardless of the approach you take, you'll need some logic that can parse the command you need out of the Notecard's JSON response. The code below shows an Arduino example that parses the `note.get` request's JSON and returns the command. (For example, if `note.get` returns `{"body":{"command":"on"}}` this function returns `"on"`).

```cpp
char* getLastCommand()
{
  // To hold "on" and "off". Increase the size if you need to send longer commands.
  static char command[4];

  J *req = notecard.newRequest("note.get");
  JAddStringToObject(req, "file", "data.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));
  }

  notecard.deleteResponse(rsp);
  return command;
}
```

> **Note:**
>
> If you're using `note.changes` you'll need some additional logic as the request can return multiple Notes in one response.

Finally, if you're not using interrupts you need to check for inbound notes in a loop. For our Arduino example this is straightforward as Arduino provides a built-in `loop` function. The code below checks for inbound notes every second.

```cpp
void loop()
{
  char* command = getLastCommand();

  // Use the command (which we'll do in the next step)

  // Wait one second before looking for changes again
  delay(1000);
}
```

### Step 3: Take Action

As a last step, now that you have your command you can take project-specific actions. For example, this is where you may want to turn on/off a light, move a robot in a given direction, or shut off a piece of hardware.

As one example, the code below shows how to use `{"command":"on"}` to turn on the Swan MCU's built-in LED, and `{"command":"off"}` to turn that same light off.

```cpp
char* command = getLastCommand();

if (!strncmp(command, "on", sizeof("on")))
{
  notecard.logDebug("Turning light on");
  digitalWrite(LED_BUILTIN, HIGH);
}
if (!strncmp(command, "off", sizeof("off")))
{
  notecard.logDebug("Turning light off");
  digitalWrite(LED_BUILTIN, LOW);
}
```

This example's full code is below for your reference. Remember that although this implementation uses Arduino and the Swan, you can perform the same steps using any of the Notecard's libraries, and you can run your logic on virtually any MCU.

> **Note:**
>
> Refer back to the section on [sending commands to devices](#sending-commands-to-devices) to learn how to send the necessary Notehub API requests to test this example.

```cpp
#include <Notecard.h>

#define serialDebug Serial
#define productUID "YOUR_PRODUCTUID_HERE"

Notecard notecard;

void setup()
{
  while (!serialDebug);
  serialDebug.begin(115200);
  notecard.begin();
  notecard.setDebugOutputStream(serialDebug);

  // Configure the Notecard
  J *req = notecard.newRequest("hub.set");
  JAddStringToObject(req, "product", productUID);
  JAddStringToObject(req, "mode", "continuous");
  JAddBoolToObject(req, "sync", true);
  notecard.sendRequest(req);

  // Initialize digital pin LED_BUILTIN as an output,
  // and ensure the light starts off.
  pinMode(LED_BUILTIN, OUTPUT);
  digitalWrite(LED_BUILTIN, LOW);
}

char* getLastCommand()
{
  // To hold "on" and "off". Increase the size if you need to send longer commands.
  static char command[4];

  J *req = notecard.newRequest("note.get");
  JAddStringToObject(req, "file", "data.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));
  }

  notecard.deleteResponse(rsp);
  return command;
}

void loop()
{
  char* command = getLastCommand();

  if (!strncmp(command, "on", sizeof("on")))
  {
    notecard.logDebug("Turning light on");
    digitalWrite(LED_BUILTIN, HIGH);
  }
  if (!strncmp(command, "off", sizeof("off")))
  {
    notecard.logDebug("Turning light off");
    digitalWrite(LED_BUILTIN, LOW);
  }

  // Wait one second before looking for changes again
  delay(1000);
}
```
