Use Your AI Agent to Talk to Your Products with Notehub IQ

Blues Developers
What’s New
Resources
API Reference
Notecard and Notehub API documentation
Blog
Technical articles for developers
Connected Product Guidebook
In-depth guides for connected product development
Developer Certification
Get certified on wireless connectivity with Blues
Newsletter
The monthly Blues developer newsletter
Terminal
Connect to a Notecard in your browser
Webinars
Listing of Blues technical webinars
Blues.comNotehub.io
Shop
Docs
Button IconHelp
Support DocsNotehub StatusSecurity AdvisoriesVisit our Forum
Button IconSign In
Docs Home
What’s New
Resources
API Reference
Notecard and Notehub API documentation
Blog
Technical articles for developers
Connected Product Guidebook
In-depth guides for connected product development
Developer Certification
Get certified on wireless connectivity with Blues
Newsletter
The monthly Blues developer newsletter
Terminal
Connect to a Notecard in your browser
Webinars
Listing of Blues technical webinars
Blues.comNotehub.io
Shop
Docs
Tools & SDKs
Notecard CLI
Firmware Libraries
Arduino Library
C Library
InstallationUsagePortsExamplesAPI ReferenceInitialization FunctionsRequest FunctionsJSON FunctionsHelper Functions
ESP-IDF Library
Go Library
Python Library
Zephyr Library
Notehub SDKs
Notehub JS Library
Notehub Py Library
Notehub API Postman Collection
Generative AI Tools
Blues Expert MCP
Notehub MCP
homechevron_rightDocschevron_rightTools & SDKschevron_rightFirmware Librarieschevron_rightC Library

C Library

note-c is the official C library for communicating with the Notecard over serial or I2C. It is written in portable C, has no mandatory platform dependencies, and runs anywhere you have a C or C++ compiler—from resource-constrained microcontrollers to embedded Linux.

note-c is also the foundation for the other Blues C-based libraries. note-arduino, note-zephyr, and note-espidf all wrap note-c and supply the platform-specific pieces for you. If you're on one of those platforms, start there. Use note-c directly when you're working with a vendor HAL (such as STM32Cube), a bare-metal project, an RTOS Blues doesn't have a port for, or any other environment where you need full control over how the library talks to your hardware.

Installation

note-c isn't distributed through a package manager. Instead, you add its source to your project directly. The library is made up of note.h, plus every .c and .h file in the root of the repository that starts with n_.

Download or clone the note-c repository, and copy the note.h file and all of the n_*.c and n_*.h files into your project alongside your other source files.

$
git clone https://github.com/blues/note-c.git
cp note-c/note.h note-c/n_*.c note-c/n_*.h path/to/your/project/src/

Make sure the directory containing the .h files is on your compiler's include path, and that the .c files are compiled and linked with the rest of your application.

note-c ships with a CMakeLists.txt that exposes a static library target named note_c_lib. If you've cloned the repository into your project, you can link against it like any other CMake target.

add_subdirectory(lib/note-c)
target_link_libraries(my_app PRIVATE note_c_lib)

The target carries note-c's include path and platform compile definitions for you, so there is nothing else to configure.

Usage

To use note-c, include note.h at the top of any source file that talks to the Notecard. The header is C++ safe, so it works unchanged in .c and .cpp files.

#include "note.h"

Unlike the Arduino and Python libraries, note-c doesn't know how to talk to your hardware on its own. It is deliberately platform-neutral, and expects your application to supply a handful of small callback functions, which note-c calls "hooks", for memory allocation, timing, and the serial or I2C peripheral wired to the Notecard. Once those hooks are registered, every other part of the library works the same on every platform.

Getting started therefore has two steps:

  1. Initialize the library by registering the hooks for your platform.
  2. Send Notecard requests using the Notecard API.

Library Initialization

Memory and Timing Hooks

Every port must call NoteSetFn() to give note-c functions for allocating memory, freeing memory, delaying for a number of milliseconds, and reading a millisecond counter. On most platforms these map directly to libc and your HAL.

// malloc and free come from libc. HAL_Delay and HAL_GetTick are from the
// STM32 HAL, but any functions with matching signatures will do.
NoteSetFn(malloc, free, HAL_Delay, HAL_GetTick);

The required signatures are:

void *mallocFn(size_t size);
void freeFn(void *mem);
void delayMsFn(uint32_t ms);
uint32_t getMsFn(void);

Serial Configuration

To communicate over serial, call NoteSetFnSerial() with four hooks: one to reset the serial peripheral, one to transmit a buffer, one to check whether a byte is waiting to be read, and one to read a single byte.

bool noteSerialReset(void);
void noteSerialTransmit(uint8_t *buf, size_t len, bool flush);
bool noteSerialAvailable(void);
char noteSerialReceive(void);

NoteSetFnSerial(noteSerialReset, noteSerialTransmit,
                noteSerialAvailable, noteSerialReceive);

The Notecard's serial interface runs at 9600 baud by default. Your reset hook should (re)initialize the UART at that rate; note-c calls it before the first transaction and again whenever it detects an I/O error.

I2C Configuration

To communicate over I2C, call NoteSetFnI2C() with the Notecard's I2C address, the maximum number of bytes to send in a single I2C transaction, and three hooks: one to reset the I2C peripheral, one to transmit a buffer, and one to receive a buffer.

bool noteI2CReset(uint16_t addr);
const char *noteI2CTransmit(uint16_t addr, uint8_t *buf, uint16_t len);
const char *noteI2CReceive(uint16_t addr, uint8_t *buf, uint16_t len,
                           uint32_t *available);

NoteSetFnI2C(NOTE_I2C_ADDR_DEFAULT, NOTE_I2C_MTU_DEFAULT,
             noteI2CReset, noteI2CTransmit, noteI2CReceive);

Pass NOTE_I2C_ADDR_DEFAULT (0x17) and NOTE_I2C_MTU_DEFAULT unless you've changed the Notecard's I2C address or your platform's I2C driver has a buffer that can't handle the default transaction size.

The I2C hooks are a little more involved than the serial ones, because the Notecard uses a variable-length serial-over-I2C protocol rather than a fixed register map. In short, every write is prefixed with a length byte, and every read starts with a two-byte request followed by a two-byte header in the response. The STM32 example below shows a complete implementation.

note

note-c keeps one active interface at a time. Calling NoteSetFnSerial() makes serial the active interface, and calling NoteSetFnI2C() makes I2C the active interface. You can register both and switch between them with NoteSetActiveInterface().

Mutex Hooks

If more than one task or thread in your application talks to the Notecard, register lock and unlock hooks with NoteSetFnNoteMutex(). note-c calls the lock hook before every transaction and the unlock hook after, so one task can't interrupt another mid-request.

void lockNotecard(void);
void unlockNotecard(void);

NoteSetFnNoteMutex(lockNotecard, unlockNotecard);

If other devices share the Notecard's I2C bus, you can also register a pair of hooks with NoteSetFnI2CMutex() that note-c calls around each I2C transfer. Single-threaded applications can skip both.

Logging

note-c can log the raw JSON it exchanges with the Notecard, along with warnings and errors, but it needs you to tell it where that output should go. Register a debug output hook with NoteSetFnDebugOutput(). The hook receives a C string and returns the number of bytes it wrote.

size_t noteDebugOutput(const char *text) {
    // Write to whatever you use for console output. For example, on
    // POSIX systems:
    return fputs(text, stdout) >= 0 ? strlen(text) : 0;
}

NoteSetFnDebugOutput(noteDebugOutput);

note-c has four log levels: NOTE_C_LOG_LEVEL_ERROR, NOTE_C_LOG_LEVEL_WARN, NOTE_C_LOG_LEVEL_INFO (the default, which includes every JSON request and response), and NOTE_C_LOG_LEVEL_DEBUG. You can change the level at runtime with NoteSetLogLevel(), or set the compile-time default by passing -DNOTE_C_LOG_LEVEL=<n> to your compiler, where <n> is 0 for errors only through 3 for everything.

// Only log warnings and errors
NoteSetLogLevel(NOTE_C_LOG_LEVEL_WARN);

You can also write to the same output stream from your own code with NoteDebug(), NoteDebugln(), and NoteDebugf(), and poll the Notecard for its sync status with NoteDebugSyncStatus().

Sending Notecard Requests

Whether using serial or I2C, sending Notecard requests and reading responses follows the same pattern:

  1. Create a JSON object that includes a valid Notecard API Request.
  2. Pass the request to one of the NoteRequest* functions.
  3. If you called a function that returns a response, check it for errors and read the data you need, then free it.
#define productUID "com.your-company.your-name:your_product"

J *req = NoteNewRequest("hub.set");
if (req) {
    JAddStringToObject(req, "product", productUID);
    JAddStringToObject(req, "mode", "continuous");
    JAddBoolToObject(req, "sync", true);
    if (!NoteRequest(req)) {
        NoteDebug("FATAL: Failed to configure Notecard!\n");
        while (1);
    }
}

Choosing a Request Function

note-c provides a few functions for sending a request, depending on whether you need the response and how you'd like to handle a Notecard that is temporarily busy.

  • NoteNewRequest() creates a JSON (J) object for the specified Notecard request, with the req key set. It returns NULL if memory can't be allocated, so always check the result.
J *req = NoteNewRequest("card.status");
  • NoteRequest() sends a request and returns true if it succeeded and false if there was an I/O error or the Notecard responded with an error. Use it when you don't need the response itself.
J *req = NoteNewRequest("hub.sync");
if (req) {
    NoteRequest(req);
}
  • NoteRequestResponse() sends a request and returns the Notecard's response as a J object that you can parse. It returns NULL if the request couldn't be sent.
J *req = NoteNewRequest("card.status");
if (req) {
    J *rsp = NoteRequestResponse(req);
    if (rsp != NULL && !NoteResponseError(rsp)) {
        // do something with the response
    }
    NoteDeleteResponse(rsp);
}
  • NoteRequestWithRetry() and NoteRequestResponseWithRetry() behave like the functions above, but keep retrying for up to the number of seconds you pass if the Notecard doesn't respond or returns an I/O error. These are handy during startup, when the Notecard may still be booting.
J *req = NoteNewRequest("card.version");
if (req) {
    J *rsp = NoteRequestResponseWithRetry(req, 5);
    // ...
    NoteDeleteResponse(rsp);
}
note

The NoteRequest* functions always free the request object you pass in, whether or not the request succeeds. You only need to free responses, which you do with NoteDeleteResponse(). (It's safe to call on NULL.)

If you need to reuse a request object, use the lower-level NoteTransaction() instead, which leaves ownership of the request with you.

note

In some situations you may need to send the Notecard a request and not wait for a response. For example, when using the card.attn request's sleep mode to power down a host MCU, the host is disabled and can't receive a response.

For these scenarios use NoteNewCommand(), which creates a J object with the cmd key set instead of req. The Notecard processes commands without sending a response. You send them with the same NoteRequest() function.

J *cmd = NoteNewCommand("card.attn");
if (cmd) {
    JAddStringToObject(cmd, "mode", "sleep");
    JAddNumberToObject(cmd, "seconds", 3600);
    NoteRequest(cmd);
}

Checking for Errors

When the Notecard can't process a request it returns a response with an err field. NoteResponseError() returns true if a response contains one, and JGetString(rsp, "err") gives you the message. Notecard error messages include bracketed tags such as {io} or {bad-bin} that you can test for with NoteErrorContains(). See Notecard Error and Status Codes for a list.

J *rsp = NoteRequestResponse(req);
if (rsp == NULL) {
    NoteDebug("No response from Notecard\n");
} else if (NoteResponseError(rsp)) {
    NoteDebugf("Notecard error: %s\n", JGetString(rsp, "err"));
}
NoteDeleteResponse(rsp);

Working with Raw JSON Strings

If you'd rather build requests as plain C strings, NoteRequestResponseJSON() takes a newline-terminated JSON string and returns the Notecard's response as a newly allocated, newline-terminated string. This is useful for passing requests through from another source (for example, a serial console or Notehub). Unlike the J-based functions, you own both strings, so free the response with NoteFree() when you're done.

char *rsp = NoteRequestResponseJSON("{\"req\":\"card.version\"}\n");
if (rsp) {
    NoteDebug(rsp);
    NoteFree(rsp);
}

JSON Handling

note-c bundles a modified version of the cJSON library and exposes it through a J type. Treat J as opaque: you'll only ever hold a J * and use the functions below to build and read it.

Creating JSON Objects

Start with NoteNewRequest(), which allocates a J object containing a "req" key-value pair. From there, the following functions add fields to the object.

  • JAddBoolToObject
  • JAddNumberToObject
  • JAddStringToObject
  • JAddObjectToObject
  • JAddArrayToObject
  • JAddItemToArray

To see how to use these functions, suppose you need to create a J JSON object that contains the following JSON data.

{
    "req": "example.request",
    "key-1": true,
    "key-2": 2,
    "key-3": "3",
    "key-4": {
        "key-a": ["a"]
    }
}

The code below constructs the above JSON structure, and then passes that object to the Notecard using NoteRequest().

J *req = NoteNewRequest("example.request");
if (req) {
    JAddBoolToObject(req, "key-1", true);
    JAddNumberToObject(req, "key-2", 2);
    JAddStringToObject(req, "key-3", "3");
    J *key4 = JAddObjectToObject(req, "key-4");
    J *keyA = JAddArrayToObject(key4, "key-a");
    JAddItemToArray(keyA, JCreateString("a"));

    NoteRequest(req);
}
note

note-c includes several functions for creating standalone JSON "items", such as JCreateObject, JCreateArray, JCreateNumber, JCreateBool, and JCreateString. You only need these when an API takes an item as an argument, such as JAddItemToArray, or when building a body object to pass to a helper like NoteAdd().

Parsing JSON Objects

NoteRequestResponse() returns a J object containing the Notecard's response. For example, after a card.status request, rsp points at JSON with this structure:

{
  "connected": true,
  "status": "{normal}",
  "storage": 2,
  "time": 1667924973,
  "cell": true
}

There are a number of functions you can use to parse individual fields out of a JSON object.

  • JIsPresent
  • JGetBool
  • JGetInt
  • JGetNumber
  • JGetObject
  • JGetArray
  • JGetString

The JGet* functions return a sensible zero value (false, 0, or "") when a field is missing, so you can read optional fields without checking JIsPresent first. Strings returned by JGetString point into the response, so copy anything you need to keep before calling NoteDeleteResponse().

char status[20];
J *req = NoteNewRequest("card.status");
if (req) {
    J *rsp = NoteRequestResponse(req);

    bool connected = JGetBool(rsp, "connected");
    strlcpy(status, JGetString(rsp, "status"), sizeof(status));
    int storage = JGetInt(rsp, "storage");
    JTIME time = JGetInt(rsp, "time");
    bool cell = JGetBool(rsp, "cell");

    NoteDeleteResponse(rsp);
}

When the response contains nested objects you can parse them using JGetObject. For example, the file.changes request returns a JSON object with a structure that looks like the one below.

{
  "info": {
    "data.qo": {
      "total": 1
    },
    "data.qi": {
      "total": 2
    },
    "total": 3
  }
}

Given this response, you could use the following code to parse out the total from within the "data.qo" object.

J *req = NoteNewRequest("file.changes");
if (req) {
    J *rsp = NoteRequestResponse(req);

    J *info = JGetObject(rsp, "info");
    J *data = JGetObject(info, "data.qo");
    int total = JGetInt(data, "total");
    NoteDebugf("Total: %d\n", total); // 1

    NoteDeleteResponse(rsp);
}

If you need the response as text, for example to log it, JPrintUnformatted() serializes a J object to a newly allocated string that you free with JFree().

Using Helpers

Alongside the general-purpose request functions, note-c includes a set of helpers that wrap common Notecard requests so you don't need to build the JSON yourself: NoteSetProductID(), NoteSetSyncMode(), NoteAdd(), NoteTemplate(), NoteGetEnv(), NoteGetLocation(), NoteIsConnected(), and others. For example, sending a Note with NoteAdd() takes a Notefile name and a J body, which the helper frees for you.

J *body = JCreateObject();
if (body) {
    JAddNumberToObject(body, "temp", 22.5);
    JAddNumberToObject(body, "humidity", 48);
    NoteAdd("sensors.qo", body, false);
}

The helpers make common tasks shorter, but they cover a fixed set of arguments. Whenever you need a request argument a helper doesn't expose, build the request with NoteNewRequest() instead. See Helper Functions for the full list.

Ports

Because note-c leaves the platform-specific hooks to you, Blues maintains a number of open source ports that implement those hooks for popular platforms. If one of these matches your hardware, it's the fastest way to get running: the port handles library initialization and you can go straight to sending requests.

PortPlatform
note-arduinoAny Arduino-compatible board. Wraps note-c in a Notecard C++ class.
note-zephyrZephyr RTOS, as a West module.
note-espidfESP-IDF framework for Espressif chips, as an ESP-IDF component.
note-stm32l4STM32L4, using the STM32Cube HAL.
note-stm32g0STM32G0, using the STM32Cube HAL.
note-stm32f1STM32F1, using the STM32Cube HAL.
note-stm32l0STM32L0, using the STM32Cube HAL.
note-nrf52Nordic nRF52.
note-msp430TI MSP430.
note-mbedMbed OS.

Even if your platform isn't listed, these ports are good references. The hooks are small and the vendor HAL calls they wrap usually have a direct equivalent on other platforms.

Examples

Complete Example: STM32L4

The code below is adapted from the note-stm32l4 port, and shows everything a bare-metal application needs: hook implementations for the STM32 HAL, library initialization, and a first request. The I2C hooks are a complete implementation of the Notecard's serial-over-I2C protocol, and can be adapted to any platform with blocking I2C transmit and receive functions.

#include <stdlib.h>
#include <string.h>
#include "main.h"   // STM32Cube-generated HAL handles and init functions
#include "note.h"

#define PRODUCT_UID "com.your-company.your-name:your_product"

// Hooks for note-c (implemented below)
bool noteI2CReset(uint16_t addr);
const char *noteI2CTransmit(uint16_t addr, uint8_t *buf, uint16_t len);
const char *noteI2CReceive(uint16_t addr, uint8_t *buf, uint16_t len,
                           uint32_t *available);
size_t noteDebugOutput(const char *text);

int main(void)
{
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();
    MX_USART2_UART_Init();  // debug console

    // Memory and timing hooks: libc for memory, the HAL for time.
    NoteSetFn(malloc, free, HAL_Delay, HAL_GetTick);

    // Send library logs to the debug console.
    NoteSetFnDebugOutput(noteDebugOutput);

    // Talk to the Notecard over I2C.
    NoteSetFnI2C(NOTE_I2C_ADDR_DEFAULT, NOTE_I2C_MTU_DEFAULT,
                 noteI2CReset, noteI2CTransmit, noteI2CReceive);

    // Configure the Notecard. Retry for a few seconds in case it's still
    // booting.
    J *req = NoteNewRequest("hub.set");
    if (req) {
        JAddStringToObject(req, "product", PRODUCT_UID);
        JAddStringToObject(req, "mode", "periodic");
        JAddNumberToObject(req, "outbound", 60);
        NoteRequestWithRetry(req, 5);
    }

    while (1) {
        // Read the Notecard's temperature sensor and send it as a Note.
        JNUMBER temp;
        if (NoteGetTemperature(&temp)) {
            J *body = JCreateObject();
            if (body) {
                JAddNumberToObject(body, "temp", temp);
                NoteAdd("sensors.qo", body, false);
            }
        }
        HAL_Delay(60 * 1000);
    }
}

// Reinitialize the I2C peripheral. note-c calls this before the first
// transaction and after any I/O error.
bool noteI2CReset(uint16_t addr)
{
    HAL_I2C_DeInit(&hi2c1);
    MX_I2C1_Init();
    return true;
}

// Serial-over-I2C write: a length byte followed by the payload.
const char *noteI2CTransmit(uint16_t addr, uint8_t *buf, uint16_t len)
{
    uint8_t writebuf[NOTE_I2C_MTU_MAX + 1];
    writebuf[0] = (uint8_t)len;
    memcpy(&writebuf[1], buf, len);
    if (HAL_I2C_Master_Transmit(&hi2c1, addr << 1, writebuf, len + 1, 250)
            != HAL_OK) {
        return "i2c: HAL_I2C_Master_Transmit error";
    }
    return NULL;
}

// Serial-over-I2C read: ask for up to `len` bytes, then read a two-byte
// header (bytes still available, bytes in this packet) followed by the data.
const char *noteI2CReceive(uint16_t addr, uint8_t *buf, uint16_t len,
                           uint32_t *available)
{
    uint8_t hdr[2] = { 0, (uint8_t)len };
    if (HAL_I2C_Master_Transmit(&hi2c1, addr << 1, hdr, sizeof(hdr), 250)
            != HAL_OK) {
        return "i2c: HAL_I2C_Master_Transmit error";
    }

    uint8_t readbuf[NOTE_I2C_MTU_MAX + 2];
    if (HAL_I2C_Master_Receive(&hi2c1, addr << 1, readbuf, len + 2, 250)
            != HAL_OK) {
        return "i2c: HAL_I2C_Master_Receive error";
    }
    if (readbuf[1] != len) {
        return "i2c: incorrect amount of data";
    }

    *available = readbuf[0];
    memcpy(buf, &readbuf[2], len);
    return NULL;
}

// Write note-c's log output to the debug UART.
size_t noteDebugOutput(const char *text)
{
    size_t len = strlen(text);
    HAL_UART_Transmit(&huart2, (uint8_t *)text, len, 5000);
    return len;
}

For a serial connection, replace the NoteSetFnI2C() call and the I2C hooks with NoteSetFnSerial() and four serial hooks. The note-stm32l4 source includes an interrupt-driven implementation of both.

More Examples

  • Writing Host Firmware with STM32Cube
    • A step-by-step tutorial that adds note-c to an STM32CubeIDE project and sends sensor data to Notehub, with variants for a Nucleo board (serial) and a Discovery board (I2C).
  • note-arduino
    • The Arduino port's source is a compact, readable example of implementing every note-c hook, including the serial, I2C, mutex, and debug output hooks, in C++.
  • note-zephyr examples
    • Complete Zephyr applications that call the Notecard API through note-c, including a blinky that sends Notes, a multi-threaded message queue example, and sending and receiving binary data.
  • Sending and Receiving Large Binary Objects
    • A guide to the Notecard's binary data store, which note-c supports with the NoteBinaryStore* helpers.

API Reference

The sections below document the note-c functions you'll use in most applications. All of them are declared in note.h, which also documents the less common functions not covered here.

Initialization

FunctionDescription
NoteSetFn()Register the memory allocation, memory free, millisecond delay, and millisecond counter hooks. Required.
NoteSetFnSerial()Register the serial hooks and make serial the active interface.
NoteSetFnI2C()Register the I2C address, transfer size, and I2C hooks, and make I2C the active interface.
NoteSetActiveInterface()Switch between registered serial and I2C interfaces.
NoteSetFnNoteMutex()Register lock and unlock hooks called around every Notecard transaction. Optional.
NoteSetFnI2CMutex()Register lock and unlock hooks called around every I2C transfer. Optional.
NoteSetFnDebugOutput()Register the hook that receives note-c's log output. Optional.
NoteSetLogLevel()Set the runtime log level.

Requests

FunctionDescription
NoteNewRequest()Create a J object with the req field set to the given request name.
NoteNewCommand()Create a J object with the cmd field set, for requests that need no response.
NoteRequest()Send a request and return true on success. Frees the request.
NoteRequestWithRetry()Like NoteRequest(), but retries for up to the given number of seconds.
NoteRequestResponse()Send a request and return the response as a J object. Frees the request.
NoteRequestResponseWithRetry()Like NoteRequestResponse(), but retries for up to the given number of seconds.
NoteRequestResponseJSON()Send a request given as a JSON string and return the response as a JSON string.
NoteTransaction()Send a request and return the response without freeing the request.
NoteResponseError()Return true if a response contains an err field.
NoteErrorContains()Return true if an error string contains a given error tag.
NoteDeleteResponse()Free a response object.
NoteSetRequestTimeout()Change how long note-c waits for the Notecard to respond.
NoteDebugSyncStatus()Poll the Notecard for sync status and write it to the debug output.

JSON

FunctionDescription
JCreateObject()Create an empty object, for example a Note body.
JAddBoolToObject()Add a boolean field to an object.
JAddNumberToObject()Add a numeric field to an object.
JAddStringToObject()Add a string field to an object.
JAddObjectToObject()Add a nested object and return it.
JAddArrayToObject()Add an array and return it.
JAddItemToArray()Append an item to an array.
JAddBinaryToObject()Add binary data to an object as a base64 string.
JIsPresent()Return true if an object contains the given field.
JGetBool()Read a boolean field.
JGetInt()Read a numeric field as an integer.
JGetNumber()Read a numeric field as a floating-point number.
JGetString()Read a string field.
JGetObject()Read a nested object.
JGetArray()Read an array.
JGetBinaryFromObject()Read a base64 string field as decoded binary data.
JPrintUnformatted()Serialize a J object to a string.
JParse()Parse a JSON string into a J object.
JDelete()Free a J object.
JFree()Free memory returned by JPrintUnformatted() or JGetBinaryFromObject().

Helpers

FunctionDescription
NoteSetProductID()Set the Notecard's ProductUID.
NoteSetSyncMode()Set the Notecard's sync mode and intervals.
NoteAdd()Add a Note to a Notefile.
NoteTemplate()Set a Note template for a Notefile.
NoteGetEnv()Read an environment variable as a string.
NoteGetEnvInt()Read an environment variable as an integer.
NoteGetEnvNumber()Read an environment variable as a floating-point number.
NoteIsConnected()Return true if the Notecard is connected to Notehub.
NoteTime()Get the current time from the Notecard.
NoteGetLocation()Get the Notecard's last known location.
NoteGetTemperature()Read the Notecard's onboard temperature sensor.
NoteGetVoltage()Read the Notecard's supply voltage.
NoteSleep()Ask the Notecard to power down the host for a number of seconds.
NoteBinaryStoreTransmit()Write binary data to the Notecard's binary store.
NoteBinaryStoreReceive()Read binary data from the Notecard's binary store.

Initialization Functions

NoteSetFn

Register the memory allocation, memory free, millisecond delay, and millisecond counter hooks. Every application must call this before any other note-c function.

void NoteSetFn(mallocFn mallocHook, freeFn freeHook,
               delayMsFn delayMsHook, getMsFn getMsHook);
Arguments

mallocHook

void *(*)(size_t size)

Allocate size bytes and return a pointer to them, or NULL on failure.

freeHook

void (*)(void *mem)

Free memory previously returned by mallocHook.

delayMsHook

void (*)(uint32_t ms)

Block for ms milliseconds.

getMsHook

uint32_t (*)(void)

Return a free-running millisecond counter. It may wrap.

// STM32 HAL
NoteSetFn(malloc, free, HAL_Delay, HAL_GetTick);

NoteSetFnSerial

Register the serial hooks and make serial the active interface.

void NoteSetFnSerial(serialResetFn resetFn, serialTransmitFn transmitFn,
                     serialAvailableFn availFn, serialReceiveFn receiveFn);
Arguments

resetFn

bool (*)(void)

Reinitialize the serial peripheral connected to the Notecard at 9600 baud. Return true on success. note-c calls this before the first transaction and after any I/O error.

transmitFn

void (*)(uint8_t *buf, size_t len, bool flush)

Write len bytes from buf to the Notecard. If flush is true, wait for the bytes to leave the transmit buffer before returning.

availFn

bool (*)(void)

Return true if at least one byte from the Notecard is waiting to be read.

receiveFn

char (*)(void)

Return the next byte from the Notecard. note-c only calls this after availFn returns true.

NoteSetFnSerial(noteSerialReset, noteSerialTransmit,
                noteSerialAvailable, noteSerialReceive);

NoteSetFnI2C

Register the Notecard's I2C address, the maximum transfer size, and the I2C hooks, and make I2C the active interface. The transmit and receive hooks must implement the Notecard's serial-over-I2C protocol. See the STM32 example for a complete implementation.

void NoteSetFnI2C(uint32_t notecardAddr, uint32_t maxTransmitSize,
                  i2cResetFn resetFn, i2cTransmitFn transmitFn,
                  i2cReceiveFn receiveFn);
Arguments

notecardAddr

uint32_t

The Notecard's I2C address. Pass NOTE_I2C_ADDR_DEFAULT (0x17) or 0 for the default.

maxTransmitSize

uint32_t

The maximum number of bytes to send in a single I2C transaction. Pass NOTE_I2C_MTU_DEFAULT or 0 for the default. The upper limit is NOTE_I2C_MTU_MAX (253 bytes). Lower it if your platform's I2C driver has a smaller buffer.

resetFn

bool (*)(uint16_t addr)

Reinitialize the I2C peripheral connected to the Notecard. Return true on success.

transmitFn

const char *(*)(uint16_t addr, uint8_t *buf, uint16_t len)

Write len bytes from buf to the Notecard at I2C address addr, prefixed with a byte containing len. Return NULL on success or an error string on failure.

receiveFn

const char *(*)(uint16_t addr, uint8_t *buf, uint16_t len, uint32_t *available)

Read len bytes from the Notecard into buf, and set *available to the number of bytes the Notecard still has waiting. Return NULL on success or an error string on failure.

NoteSetFnI2C(NOTE_I2C_ADDR_DEFAULT, NOTE_I2C_MTU_DEFAULT,
             noteI2CReset, noteI2CTransmit, noteI2CReceive);

NoteSetActiveInterface

Select which registered interface note-c uses to talk to the Notecard. NoteSetFnSerial() and NoteSetFnI2C() each make their own interface active, so you only need this if you've registered both and want to switch between them.

void NoteSetActiveInterface(int interface);
Arguments

interface

int

One of NOTE_C_INTERFACE_SERIAL, NOTE_C_INTERFACE_I2C, or NOTE_C_INTERFACE_NONE.

NoteSetActiveInterface(NOTE_C_INTERFACE_I2C);

NoteSetFnNoteMutex

Register lock and unlock hooks that note-c calls before and after every transaction with the Notecard. Use this when more than one task or thread sends Notecard requests.

void NoteSetFnNoteMutex(mutexFn lockFn, mutexFn unlockFn);
Arguments

lockFn

void (*)(void)

Block until the Notecard is available, then acquire it.

unlockFn

void (*)(void)

Release the Notecard.

// FreeRTOS
static SemaphoreHandle_t notecardMutex;

void lockNotecard(void) {
    xSemaphoreTake(notecardMutex, portMAX_DELAY);
}

void unlockNotecard(void) {
    xSemaphoreGive(notecardMutex);
}

notecardMutex = xSemaphoreCreateMutex();
NoteSetFnNoteMutex(lockNotecard, unlockNotecard);

NoteSetFnI2CMutex

Register lock and unlock hooks that note-c calls before and after every I2C transfer. Use this when other devices share the Notecard's I2C bus, and have the code that drives those devices call the same lock and unlock functions.

void NoteSetFnI2CMutex(mutexFn lockI2Cfn, mutexFn unlockI2Cfn);
Arguments

lockI2Cfn

void (*)(void)

Block until the I2C bus is available, then acquire it.

unlockI2Cfn

void (*)(void)

Release the I2C bus.

NoteSetFnI2CMutex(lockI2C, unlockI2C);

NoteSetFnDebugOutput

Register the hook that receives note-c's log output. Until you call this, note-c produces no output.

void NoteSetFnDebugOutput(debugOutputFn fn);
Arguments

fn

size_t (*)(const char *text)

Write the C string text to your console and return the number of bytes written.

size_t noteDebugOutput(const char *text) {
    size_t len = strlen(text);
    HAL_UART_Transmit(&huart2, (uint8_t *)text, len, 5000);
    return len;
}

NoteSetFnDebugOutput(noteDebugOutput);

NoteSetLogLevel

Set the runtime log level. Messages above this level are discarded before they reach your debug output hook.

void NoteSetLogLevel(int level);
Arguments

level

int

One of NOTE_C_LOG_LEVEL_ERROR, NOTE_C_LOG_LEVEL_WARN, NOTE_C_LOG_LEVEL_INFO (the default), or NOTE_C_LOG_LEVEL_DEBUG. The default INFO level logs every JSON request and response. Lower the level to WARN or ERROR to quiet that output in production.

NoteSetLogLevel(NOTE_C_LOG_LEVEL_DEBUG);

Request Functions

NoteNewRequest

Create a J object with the req field set to the given request name.

J *NoteNewRequest(const char *request);
note

NoteNewRequest allocates memory, which can fail if system resources are low. It returns NULL in that case, so check the result before adding fields to it.

if (J *req = NoteNewRequest("hub.sync")) {
  ...
}
Arguments

request

const char *

The request name, for example "note.add".

J *req = NoteNewRequest("card.status");

Returns

A pointer to a J object containing {"req": "<request>"}, or NULL if memory couldn't be allocated.

NoteNewCommand

Create a J object with the cmd field set to the given request name. The Notecard processes commands without sending a response, which is useful when the host won't be around to receive one, for example when putting the host to sleep with card.attn.

J *NoteNewCommand(const char *request);
Arguments

request

const char *

The request name, for example "card.attn".

J *cmd = NoteNewCommand("card.attn");
if (cmd) {
    JAddStringToObject(cmd, "mode", "sleep");
    JAddNumberToObject(cmd, "seconds", 3600);
    NoteRequest(cmd);
}

Returns

A pointer to a J object containing {"cmd": "<request>"}, or NULL if memory couldn't be allocated.

NoteRequest

Send a request to the Notecard and discard the response. Use this when you only need to know whether the request succeeded.

bool NoteRequest(J *req);
Arguments

req

J *

The request object. It is freed before this function returns, whether or not the request succeeds.

J *req = NoteNewRequest("hub.sync");
if (req) {
    if (!NoteRequest(req)) {
        NoteDebug("hub.sync failed\n");
    }
}

Returns

true if the request was sent and the response contained no err field. false if the request couldn't be sent, memory ran out, or the Notecard returned an error. For commands created with NoteNewCommand(), true only means the command was transmitted, since the Notecard sends no response.

NoteRequestWithRetry

Send a request to the Notecard, retrying until it succeeds or the timeout elapses, and discard the response. Retries happen when the Notecard doesn't respond or returns an I/O error, which is common while the Notecard is still booting.

bool NoteRequestWithRetry(J *req, uint32_t timeoutSeconds);
Arguments

req

J *

The request object. It is freed before this function returns.

timeoutSeconds

uint32_t

How long to keep retrying, in seconds.

J *req = NoteNewRequest("hub.set");
if (req) {
    JAddStringToObject(req, "product", PRODUCT_UID);
    JAddStringToObject(req, "mode", "continuous");
    NoteRequestWithRetry(req, 5);
}

Returns

true if the request eventually succeeded, false if it timed out or the Notecard returned a non-I/O error.

NoteRequestResponse

Send a request to the Notecard and return its response.

J *NoteRequestResponse(J *req);
Arguments

req

J *

The request object. It is freed before this function returns, whether or not the request succeeds.

J *req = NoteNewRequest("card.status");
if (req) {
    J *rsp = NoteRequestResponse(req);
    if (rsp != NULL && !NoteResponseError(rsp)) {
        bool connected = JGetBool(rsp, "connected");
        NoteDebugf("Connected: %d\n", connected);
    }
    NoteDeleteResponse(rsp);
}

Returns

A pointer to a J object containing the response, or NULL if the request couldn't be sent. A non-NULL response may still contain an err field, so check it with NoteResponseError().

warning

You are responsible for freeing the response with NoteDeleteResponse().

NoteRequestResponseWithRetry

Send a request to the Notecard, retrying until it succeeds or the timeout elapses, and return its response.

J *NoteRequestResponseWithRetry(J *req, uint32_t timeoutSeconds);
Arguments

req

J *

The request object. It is freed before this function returns.

timeoutSeconds

uint32_t

How long to keep retrying, in seconds, when there is no response or the response contains an I/O error.

J *req = NoteNewRequest("card.version");
if (req) {
    J *rsp = NoteRequestResponseWithRetry(req, 5);
    if (rsp != NULL && !NoteResponseError(rsp)) {
        NoteDebugf("Notecard firmware: %s\n", JGetString(rsp, "version"));
    }
    NoteDeleteResponse(rsp);
}

Returns

A pointer to a J object containing the response, or NULL if the request couldn't be sent before the timeout. Free it with NoteDeleteResponse().

NoteRequestResponseJSON

Send a request given as a JSON string and return the response as a JSON string. This bypasses the J object API entirely, which is useful when forwarding requests from another source such as a serial console.

char *NoteRequestResponseJSON(const char *reqJSON);
Arguments

reqJSON

const char *

A newline-terminated JSON string containing the request. Unlike the J-based functions, this function does not free the request, since it may be a string literal.

char *rsp = NoteRequestResponseJSON("{\"req\":\"card.version\"}\n");
if (rsp) {
    NoteDebug(rsp);
    NoteFree(rsp);
}

Returns

A newly allocated, newline-terminated JSON string containing the response, or NULL if there was no response or the request was a command. Free it with NoteFree().

NoteTransaction

Send a request to the Notecard and return its response, without freeing the request. This is the lower-level function the NoteRequest* functions are built on. Use it when you want to send the same request object more than once.

J *NoteTransaction(J *req);
Arguments

req

J *

The request object. You remain responsible for freeing it with JDelete().

J *req = NoteNewRequest("card.temp");
if (req) {
    for (int i = 0; i < 5; i++) {
        J *rsp = NoteTransaction(req);
        if (rsp != NULL && !NoteResponseError(rsp)) {
            NoteDebugf("Temperature: %f\n", JGetNumber(rsp, "value"));
        }
        NoteDeleteResponse(rsp);
        NoteDelayMs(1000);
    }
    JDelete(req);
}

Returns

A pointer to a J object containing the response, or NULL if the request couldn't be sent. Free it with NoteDeleteResponse().

NoteResponseError

Determine whether a response contains an err field. This is a macro, so it is safe to pass NULL.

bool NoteResponseError(J *rsp);
Arguments

rsp

J *

A response object.

J *rsp = NoteRequestResponse(req);
if (NoteResponseError(rsp)) {
    NoteDebugf("Notecard error: %s\n", JGetString(rsp, "err"));
}
NoteDeleteResponse(rsp);

Returns

true if the response contains a non-empty err field.

NoteErrorContains

Check whether an error string contains a particular error tag. Notecard error messages include bracketed tags, such as {io} or {file-noexist}, that are stable across firmware releases. See Notecard Error and Status Codes for a list.

bool NoteErrorContains(const char *errstr, const char *errtype);
Arguments

errstr

const char *

The error string, typically from JGetString(rsp, "err").

errtype

const char *

The tag to look for, including the braces.

J *rsp = NoteRequestResponse(req);
if (NoteResponseError(rsp)) {
    if (NoteErrorContains(JGetString(rsp, "err"), "{io}")) {
        // I/O error: safe to retry
    }
}
NoteDeleteResponse(rsp);

Returns

true if errstr contains errtype.

NoteDeleteResponse

Free a response object. This is a macro around JDelete(), and is safe to call with NULL.

void NoteDeleteResponse(J *rsp);
Arguments

rsp

J *

The response object to free.

J *rsp = NoteRequestResponse(req);
// ... use rsp ...
NoteDeleteResponse(rsp);

NoteSetRequestTimeout

Change how long note-c waits for the Notecard to respond to a request. The default is adequate for nearly all requests, but a few, such as web.* requests, can legitimately take longer.

uint32_t NoteSetRequestTimeout(uint32_t overrideSecs);
Arguments

overrideSecs

uint32_t

The new timeout in seconds, or 0 to restore the default.

uint32_t previous = NoteSetRequestTimeout(120);
// ... send a slow request ...
NoteSetRequestTimeout(previous);

Returns

The timeout that was in effect before the call.

NoteDebugSyncStatus

Poll the Notecard for sync status and write any new messages to the debug output. Call it from your main loop while developing to watch the Notecard connect and sync.

bool NoteDebugSyncStatus(int pollFrequencyMs, int maxLevel);
Arguments

pollFrequencyMs

int

How often to query the Notecard, in milliseconds. Calls made sooner than this return immediately.

maxLevel

int

The most detailed level of messages to display: SYNCSTATUS_LEVEL_MAJOR (0), SYNCSTATUS_LEVEL_MINOR (1), SYNCSTATUS_LEVEL_DETAILED (2), SYNCSTATUS_LEVEL_ALGORITHMIC (3), or SYNCSTATUS_LEVEL_ALL (-1).

while (1) {
    NoteDebugSyncStatus(2500, SYNCSTATUS_LEVEL_ALL);
    // ... rest of loop ...
}

Returns

true if a message was written to the debug output.

warning

Sync status messages are not part of the stable API. Don't write code that depends on their contents.

JSON Functions

note-c's JSON API is a thin layer over a bundled, modified copy of cJSON. Every function takes or returns a J *, which you should treat as opaque.

Building Objects

JCreateObject

Create an empty JSON object. Use this to build a Note body or any other object you'll pass to a helper, rather than a top-level request (use NoteNewRequest() for those).

J *JCreateObject(void);
J *body = JCreateObject();
if (body) {
    JAddNumberToObject(body, "temp", 22.5);
    NoteAdd("sensors.qo", body, false);
}

Related functions create other kinds of standalone items: JCreateArray(), JCreateString(), JCreateNumber(), and JCreateBool(). You only need these when an API takes an item as an argument, such as JAddItemToArray().

JAddBoolToObject

Add a boolean field to an object. Jbool is an int, so pass true or false.

J *JAddBoolToObject(J *object, const char *name, Jbool boolean);
JAddBoolToObject(req, "sync", true);

JAddNumberToObject

Add a numeric field to an object. The value is stored as a JNUMBER (double by default, or float when NOTE_C_SINGLE_PRECISION is defined), so integers and floating-point values both go through this function.

J *JAddNumberToObject(J *object, const char *name, JNUMBER number);
JAddNumberToObject(req, "outbound", 60);
JAddNumberToObject(body, "temp", 22.5);

JAddStringToObject

Add a string field to an object. The string is copied, so the source doesn't need to outlive the object.

J *JAddStringToObject(J *object, const char *name, const char *string);
JAddStringToObject(req, "file", "sensors.qo");

JAddObjectToObject

Add an empty nested object to an object and return it so you can populate it.

J *JAddObjectToObject(J *object, const char *name);
J *body = JAddObjectToObject(req, "body");
if (body) {
    JAddNumberToObject(body, "temp", 22.5);
}

JAddArrayToObject

Add an empty array to an object and return it so you can populate it with JAddItemToArray().

J *JAddArrayToObject(J *object, const char *name);
J *readings = JAddArrayToObject(body, "readings");
if (readings) {
    JAddItemToArray(readings, JCreateNumber(22.5));
    JAddItemToArray(readings, JCreateNumber(22.7));
}

JAddItemToArray

Append an item to an array. The array takes ownership of the item.

void JAddItemToArray(J *array, J *item);
JAddItemToArray(readings, JCreateString("ok"));

JAddBinaryToObject

Add binary data to an object as a base64-encoded string. This is useful for small payloads; for anything large, use the binary store helpers instead.

bool JAddBinaryToObject(J *json, const char *fieldName,
                        const void *binaryData, uint32_t binaryDataLen);
uint8_t sample[16];
JAddBinaryToObject(body, "sample", sample, sizeof(sample));

Returns true if the data was encoded and added.

Reading Objects

The JGet* functions return a zero value (false, 0, "", or NULL) when a field is missing or has the wrong type, so you can read optional fields without checking JIsPresent() first. Values returned by JGetString(), JGetObject(), and JGetArray() point into the parent object and become invalid when it is freed.

JIsPresent

Return true if an object contains the given field.

bool JIsPresent(J *json, const char *field);
if (JIsPresent(rsp, "lat") && JIsPresent(rsp, "lon")) {
    // location is available
}

JGetBool

Read a boolean field.

bool JGetBool(J *json, const char *field);
bool connected = JGetBool(rsp, "connected");

JGetInt

Read a numeric field as a JINTEGER (int64_t).

JINTEGER JGetInt(J *json, const char *field);
JTIME time = JGetInt(rsp, "time");

JGetNumber

Read a numeric field as a JNUMBER (double).

JNUMBER JGetNumber(J *json, const char *field);
JNUMBER voltage = JGetNumber(rsp, "value");

JGetString

Read a string field. Copy the result if you need it after the object is freed.

char *JGetString(J *json, const char *field);
char version[32];
strlcpy(version, JGetString(rsp, "version"), sizeof(version));

JGetObject

Read a nested object.

J *JGetObject(J *json, const char *field);
J *body = JGetObject(rsp, "body");
if (body) {
    JNUMBER setpoint = JGetNumber(body, "setpoint");
}

JGetArray

Read an array. Iterate it with JGetArraySize() and JGetArrayItem().

J *JGetArray(J *json, const char *field);
J *files = JGetArray(rsp, "files");
if (files) {
    int count = JGetArraySize(files);
    for (int i = 0; i < count; i++) {
        J *item = JGetArrayItem(files, i);
        NoteDebugf("%s\n", JGetStringValue(item));
    }
}

JGetBinaryFromObject

Decode a base64 string field into a newly allocated buffer.

bool JGetBinaryFromObject(J *json, const char *fieldName,
                          uint8_t **retBinaryData, uint32_t *retBinaryDataLen);
uint8_t *data;
uint32_t len;
if (JGetBinaryFromObject(body, "payload", &data, &len)) {
    // ... use data ...
    JFree(data);
}

Returns true on success. You must free the buffer with JFree().

Serializing and Freeing

JPrintUnformatted

Serialize a J object to a compact JSON string, with no extra whitespace.

char *JPrintUnformatted(const J *item);
char *json = JPrintUnformatted(rsp);
if (json) {
    NoteDebugln(json);
    JFree(json);
}

Returns a newly allocated string, or NULL on error. Free it with JFree().

JParse

Parse a JSON string into a J object.

J *JParse(const char *value);
J *obj = JParse("{\"temp\": 22.5}");
if (obj) {
    JNUMBER temp = JGetNumber(obj, "temp");
    JDelete(obj);
}

Returns a new J object, or NULL if the string isn't valid JSON. Free it with JDelete().

JDelete

Free a J object and everything it contains. Safe to call with NULL. NoteDeleteResponse() is an alias for this function.

void JDelete(J *item);

JFree

Free a block of memory allocated by note-c, such as the strings returned by JPrintUnformatted() or the buffers returned by JGetBinaryFromObject(). This is a wrapper around the free hook you registered with NoteSetFn().

void JFree(void *p);

Helper Functions

Helpers wrap a specific Notecard request so you don't need to build the JSON yourself. Each one sends the request, checks the response for errors, and frees everything. They cover a fixed set of arguments, so when you need an argument a helper doesn't expose, build the request with NoteNewRequest() instead.

NoteSetProductID

Set the Notecard's ProductUID, which associates it with a Notehub project. Sends a hub.set request.

bool NoteSetProductID(const char *productID);
Arguments

productID

const char *

The ProductUID of your Notehub project. Pass an empty string to clear it.

NoteSetProductID("com.your-company.your-name:your_product");

Returns

true if the request succeeded.

NoteSetSyncMode

Set the Notecard's sync mode and sync intervals. Sends a hub.set request.

bool NoteSetSyncMode(const char *uploadMode, int uploadMinutes,
                     int downloadMinutes, bool align, bool sync);
Arguments

uploadMode

const char *

The hub.set mode: "periodic", "continuous", "minimum", or "off".

uploadMinutes

int

The outbound interval in minutes, or 0 to leave it unchanged.

downloadMinutes

int

The inbound interval in minutes, or 0 to leave it unchanged.

align

bool

Whether to align sync times to the interval (the align argument). Only sent when uploadMinutes is non-zero.

sync

bool

Whether to sync immediately after the mode change (the sync argument).

// Periodic mode: upload every 60 minutes, download every 120, aligned
NoteSetSyncMode("periodic", 60, 120, true, false);

Returns

true if the request succeeded.

NoteAdd

Add a Note to a Notefile. Sends a note.add request.

bool NoteAdd(const char *target, J *body, bool urgent);
Arguments

target

const char *

The Notefile name, for example "sensors.qo".

body

J *

The Note body, typically created with JCreateObject(). The helper frees it whether or not the request succeeds.

urgent

bool

Reserved. Pass false. To sync a Note immediately, build a note.add request with sync: true using NoteNewRequest().

J *body = JCreateObject();
if (body) {
    JAddNumberToObject(body, "temp", 22.5);
    JAddNumberToObject(body, "humidity", 48);
    NoteAdd("sensors.qo", body, false);
}

Returns

true if the request succeeded.

NoteTemplate

Set a Note template for a Notefile. Sends a note.template request. note-c defines constants for the template field types, such as TBOOL, TINT16, TFLOAT32, TSTRING(N), and TSTRINGV.

bool NoteTemplate(const char *notefileID, J *templateBody);
Arguments

notefileID

const char *

The Notefile name.

templateBody

J *

A J object describing the template. The helper frees it whether or not the request succeeds.

J *body = JCreateObject();
if (body) {
    JAddNumberToObject(body, "temp", TFLOAT32);
    JAddNumberToObject(body, "humidity", TUINT8);
    JAddStringToObject(body, "status", TSTRING(16));
    NoteTemplate("sensors.qo", body);
}

Returns

true if the request succeeded.

NoteGetEnv

Read an environment variable as a string. Sends an env.get request.

bool NoteGetEnv(const char *variable, const char *defaultVal,
                char *buf, uint32_t buflen);
Arguments

variable

const char *

The environment variable name.

defaultVal

const char *

The value to place in buf if the variable isn't set, or NULL for an empty string.

buf

char *

A buffer to receive the value.

buflen

uint32_t

The size of buf.

char mode[16];
NoteGetEnv("mode", "normal", mode, sizeof(mode));

Returns

true if the request succeeded. buf holds the default value if the variable isn't set or the request failed.

NoteGetEnvInt

Read an environment variable as an integer.

JINTEGER NoteGetEnvInt(const char *variable, JINTEGER defaultVal);
Arguments

variable

const char *

The environment variable name.

defaultVal

JINTEGER

The value to return if the variable isn't set or the request fails.

int intervalMins = NoteGetEnvInt("interval_mins", 60);

Returns

The variable's value as an integer, or defaultVal.

NoteGetEnvNumber

Read an environment variable as a floating-point number.

JNUMBER NoteGetEnvNumber(const char *variable, JNUMBER defaultVal);
Arguments

variable

const char *

The environment variable name.

defaultVal

JNUMBER

The value to return if the variable isn't set or the request fails.

JNUMBER threshold = NoteGetEnvNumber("temp_threshold", 30.0);

Returns

The variable's value as a number, or defaultVal.

NoteIsConnected

Check whether the Notecard is connected to Notehub. Sends a hub.status request.

bool NoteIsConnected(void);
Arguments
None
if (NoteIsConnected()) {
    NoteDebug("Connected to Notehub\n");
}

Returns

true if the Notecard reports it is connected.

NoteTime

Get the current time from the Notecard, as seconds since the Unix epoch. Sends a card.time request. The related NoteTimeST() returns the same value but only asks the Notecard periodically, keeping time locally with the millisecond counter hook in between, which is better suited to code that reads the time often.

JTIME NoteTime(void);
Arguments
None
if (NoteTimeValid()) {
    JTIME now = NoteTime();
    NoteDebugf("Unix time: %lu\n", (unsigned long)now);
}

Returns

The current UTC time in seconds. If the Notecard hasn't yet synchronized its clock, the value is seconds since the host booted instead, so check NoteTimeValid() first when it matters.

NoteGetLocation

Get the Notecard's last known location. Sends a card.location request.

bool NoteGetLocation(JNUMBER *retLat, JNUMBER *retLon, JTIME *time,
                     char *statusBuf, int statusBufLen);
Arguments

retLat

JNUMBER *

Receives the latitude, or NULL if you don't need it.

retLon

JNUMBER *

Receives the longitude, or NULL if you don't need it.

time

JTIME *

Receives the time the location was acquired, or NULL if you don't need it.

statusBuf

char *

Receives the Notecard's location status string, or NULL if you don't need it.

statusBufLen

int

The size of statusBuf.

JNUMBER lat, lon;
JTIME when;
if (NoteGetLocation(&lat, &lon, &when, NULL, 0)) {
    NoteDebugf("Location: %f, %f\n", lat, lon);
}

Returns

true if the Notecard has a location. false if it doesn't, in which case statusBuf explains why.

NoteGetTemperature

Read the Notecard's onboard temperature sensor. Sends a card.temp request.

bool NoteGetTemperature(JNUMBER *temp);
Arguments

temp

JNUMBER *

Receives the temperature in degrees Celsius.

JNUMBER temp;
if (NoteGetTemperature(&temp)) {
    NoteDebugf("Temperature: %.1f C\n", temp);
}

Returns

true if the request succeeded.

NoteGetVoltage

Read the Notecard's supply voltage. Sends a card.voltage request.

bool NoteGetVoltage(JNUMBER *voltage);
Arguments

voltage

JNUMBER *

Receives the voltage in volts.

JNUMBER voltage;
if (NoteGetVoltage(&voltage)) {
    NoteDebugf("Voltage: %.2f V\n", voltage);
}

Returns

true if the request succeeded.

NoteSleep

Ask the Notecard to cut power to the host for a number of seconds, using the ATTN pin. See Low-Power Firmware Design for the required wiring. Sends a card.attn command with mode: "sleep".

bool NoteSleep(char *stateb64, uint32_t seconds, const char *modes);
Arguments

stateb64

char *

An optional base64-encoded string the Notecard stores while the host sleeps and returns to NoteWake() afterward, or NULL.

seconds

uint32_t

How long to sleep. The Notecard restores power after this many seconds.

modes

const char *

Optional additional card.attn modes, comma-separated, that should wake the host early, for example "motion", or NULL.

// Sleep for an hour
NoteSleep(NULL, 3600, NULL);

Returns

true if the command was sent. Because this is a command, there is no response to confirm the Notecard acted on it.

NoteBinaryStoreTransmit

Write binary data to the Notecard's binary store, handling COBS encoding and MD5 verification for you. See Sending and Receiving Large Binary Objects for the full workflow. Sends card.binary.put.

const char *NoteBinaryStoreTransmit(uint8_t *unencodedData,
                                    uint32_t unencodedLen,
                                    uint32_t bufLen,
                                    uint32_t notecardOffset);
Arguments

unencodedData

uint8_t *

A buffer holding the data to send. The data is encoded in place, so the buffer must be larger than the data; NoteBinaryCodecMaxEncodedLength() returns the exact size needed.

unencodedLen

uint32_t

The number of data bytes in the buffer.

bufLen

uint32_t

The total size of the buffer.

notecardOffset

uint32_t

The byte offset in the Notecard's binary store to write to. Pass 0 for a single write, or the running total when sending a large payload in chunks.

char buf[64] = "Hello, Notecard!";
const char *err = NoteBinaryStoreTransmit((uint8_t *)buf, strlen(buf),
                                          sizeof(buf), 0);
if (err) {
    NoteDebugf("Binary transmit failed: %s\n", err);
}

Returns

NULL on success, or an error string.

NoteBinaryStoreReceive

Read binary data from the Notecard's binary store, handling COBS decoding and MD5 verification for you. Use NoteBinaryStoreDecodedLength() to find out how much data is available. Sends card.binary.get.

const char *NoteBinaryStoreReceive(uint8_t *buffer, uint32_t bufLen,
                                   uint32_t decodedOffset,
                                   uint32_t decodedLen);
Arguments

buffer

uint8_t *

A buffer to receive the data. The data is decoded in place, so the buffer must be larger than decodedLen; NoteBinaryCodecMaxEncodedLength() returns the exact size needed.

bufLen

uint32_t

The total size of the buffer.

decodedOffset

uint32_t

The byte offset in the Notecard's binary store to read from.

decodedLen

uint32_t

The number of bytes to read.

uint32_t len;
if (NoteBinaryStoreDecodedLength(&len) == NULL && len > 0) {
    uint32_t bufLen = NoteBinaryCodecMaxEncodedLength(len);
    uint8_t *buf = NoteMalloc(bufLen);
    if (buf) {
        const char *err = NoteBinaryStoreReceive(buf, bufLen, 0, len);
        if (!err) {
            // ... use the first len bytes of buf ...
        }
        NoteFree(buf);
    }
}

Returns

NULL on success, or an error string.

Can we improve this page? Send us feedback
© 2026 Blues Inc.
© 2026 Blues Inc.
AboutDocsAPI ReferenceTermsPrivacy