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:
- Initialize the library by registering the hooks for your platform.
- 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-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:
- Create a JSON object that includes a valid Notecard API Request.
- Pass the request to one of the
NoteRequest*functions. - 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 thereqkey set. It returnsNULLif memory can't be allocated, so always check the result.
J *req = NoteNewRequest("card.status");NoteRequest()sends a request and returnstrueif it succeeded andfalseif 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 aJobject that you can parse. It returnsNULLif 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()andNoteRequestResponseWithRetry()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);
}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.
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.
JAddBoolToObjectJAddNumberToObjectJAddStringToObjectJAddObjectToObjectJAddArrayToObjectJAddItemToArray
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-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.
JIsPresentJGetBoolJGetIntJGetNumberJGetObjectJGetArrayJGetString
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.
| Port | Platform |
|---|---|
| note-arduino | Any Arduino-compatible board. Wraps note-c in a Notecard C++ class. |
| note-zephyr | Zephyr RTOS, as a West module. |
| note-espidf | ESP-IDF framework for Espressif chips, as an ESP-IDF component. |
| note-stm32l4 | STM32L4, using the STM32Cube HAL. |
| note-stm32g0 | STM32G0, using the STM32Cube HAL. |
| note-stm32f1 | STM32F1, using the STM32Cube HAL. |
| note-stm32l0 | STM32L0, using the STM32Cube HAL. |
| note-nrf52 | Nordic nRF52. |
| note-msp430 | TI MSP430. |
| note-mbed | Mbed 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.
- A guide to the Notecard's binary data store, which note-c supports with
the
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
| Function | Description |
|---|---|
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
| Function | Description |
|---|---|
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
| Function | Description |
|---|---|
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
| Function | Description |
|---|---|
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);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);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);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);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);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);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);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);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);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")) {
...
}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);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);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);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);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().
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);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);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);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);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);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);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);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);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.
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);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);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);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);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);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);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);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);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);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);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);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);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);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);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);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.