Skip to content

m-mcgowan/note-cpp

Repository files navigation

note-cpp

CI codecov C++ Standard Header Only License: MIT

Type-safe C++ API for the Blues Notecard. Header-only, zero dependencies beyond the standard library. C++17, C++20, and C++23 — each unlocks additional features.

Community project. Not affiliated with or supported by Blues Inc. Notecard is a trademark of Blues Inc. Familiarity with the Notecard and its API is assumed.

Quick start

Arduino

#include <note.hpp>

Notecard nc;

nc.begin(Serial1, 9600);       // serial — or nc.begin(Wire) for I2C

nc.hub.set()
    .product("com.example.app")
    .mode("periodic")
    .execute();

Install from GitHub: Sketch → Include Library → Add .ZIP Library and point to this repository's ZIP download.

PlatformIO

lib_deps = https://github.com/m-mcgowan/note-cpp.git

CMake

add_subdirectory(note-cpp)
target_link_libraries(my_app PRIVATE note-cpp)

Examples

The typed API is the same on every platform. As an alternative to the fluent style shown above, you can also use assignment to set request properties.

auto req = nc.hub.set();
req.product = "com.example.app";
req.mode = "periodic";
req.outbound = 60_mins;
req.execute();

Your own custom structs allow you to send and receive arbitrary bodies (say, with note.add). The same struct is used for sending and receiving notes, as well as for note.template registration.

struct Readings {
    float temperature;
    int16_t humidity;
    NOTE_FIELDS(temperature, humidity)  // optional on C++20
};

Readings readings{.temperature = 22.5f, .humidity = 60};
nc.note.add()
   .file("sensors.qo")
   .body(readings)
   .execute();

The same struct shape works for environment variables — see examples/stdcpp/env-vars.cpp for a worked example that reads env.get results into a typed DeviceConfig.

Responses carry typed fields and a truthy operator:

auto rsp = nc.card.version().execute();
if (rsp) {
    Serial.println(rsp.version);
    Serial.println(rsp.device);
} else {
    Serial.println(rsp.error());
}

Full walkthrough: docs/getting-started.md, with the same code as examples/stdcpp/getting-started.cpp. If you're migrating from note-arduino / note-c, the migration guide has side-by-side examples.

What's in the library

  • Typed API — typed requests and responses for all 74 Notecard APIs; fluent or direct-assignment styles; on C++20 GCC the compiler validates string constants for fields like mode or triggers at compile time, rejecting typos before the binary runs.
  • Focused operations — distinct types per intent for multi-purpose requests (note.get vs note.pop, card.location.mode.fixed() vs .get()); setting a non-applicable field is a compile error.
  • Zero-heap operation — pair the library with a MonotonicArena (or HeapResetPool) and the entire send/receive cycle stays out of malloc. The same typed API works on an Arduino Uno with no heap at all, the arena lifecycle reclaims memory by reset() rather than per-allocation.
  • Body values and Note templates — one struct for send, receive, and template registration; plain aggregates work directly on C++20+, with NOTE_FIELDS(...) available on C++17 or for non-aggregates.
  • Error handling — truthy responses on success, structured ErrorInfo on failure; per-request safety classification (ReadOnly, Idempotent, NonIdempotent, Destructive) informs retry.
  • Target filtering (C++20) — constrain the available APIs by hardware variant (WiFi/Cell/Skylo/LoRa) and/or minimum firmware version; unsupported requests become compiler warnings, or errors in strict mode.
  • Choice of streaming and tree modes — two JSON-parsing strategies; tree mode keeps a walkable JsonReader on the response, while streaming mode reads the wire directly into your typed struct with no tree in memory.
  • Memory control — you choose when and how memory is allocated, including fully heap-free allocation, such as where response strings live (primitive types like numbers and booleans appear directly in the responses): options include a static MonotonicArena (zero heap, bounded RAM, predictable on every target), a HeapResetPool (malloc-backed with arena lifecycle), default malloc/free, std::pmr, or a custom function-pointer Allocator. The same surface is used by all of them, so you can swap the allocator, the typed API does not change.
  • JSONB wire format — optional NOTE_JSONB swaps JSON text for compact binary opcodes; reduces flash on constrained targets. (Enabled automatically on constrained targets.)
  • Wire protocols — Implements the expected wire protocol for Notecard. header-only serial (SerialFramer) and I2C (I2cFramer) with CRC auto-detection, segmented TX/RX, retry, auto-reset and RTS/CTS transactions. Binary transfer requests (card.binary.put/get) uses COBS framing internally.
  • Duration unitsMinutes, Seconds, Hours, Days with safe implicit conversion to smaller units; wrong direction is a compile error.

C++ version compatibility matrix — what's available on C++17, what unlocks on C++20/23.

Composition: three orthogonal axes

The library exposes three independent choices that compose freely. The typed API surface looks the same in every combination — pick each axis where it makes sense for your target, mix and match without changing call-site code.

graph LR
    A["Wire format<br/>JSON · JSONB"] --- B["Response presentation<br/>streaming · tree"] --- C["Binary payload<br/>text-only · with-binary"]
Loading
  1. Wire format — JSON text or JSONB binary opcodes (-DNOTE_JSONB=1). Smaller payloads on bandwidth-sensitive links; the typed API at the call site is byte-identical between the two.
  2. Response presentationstreaming (SAX events directly into your typed Response and .into(struct) sinks; no tree held in memory) or tree (a JsonBackend assembles a walkable JsonReader you can query by key after the call). Picked by which Notecard constructor you call.
  3. Binary payloadcard.binary.put() / .get() and large note bodies use a COBS-framed binary channel alongside the JSON/JSONB request channel. Works in every cell of the first-two-axes matrix.

The four combinations of wire format × presentation — JSON×streaming, JSON×tree, JSONB×streaming, JSONB×tree — all have host-side and on-device coverage. See docs/composition.md for the full matrix and the configuration-by-configuration breakdown, and examples/stdcpp/wire-format-and-presentation.cpp for a runnable demo where the same demo() body walks all four cells.

How it scales

The library is built to scale from resource-constrained MCUs to desktop-class hardware. The same API surface can be used from ATmega328P (32 KB flash / 2 KB RAM) up to ESP32, Cortex-M, and desktop hosts. For additional optimization, you dial resource use by choosing how much of the stack to pull in.

Target tiers

Target Defaults Recommended flags Typical flash / RAM
AVR Uno (ATmega328P) streaming, arena-backed (zero heap) NOTE_MINIMAL (auto-enables NOTE_JSONB), JsonView / note::scan for responses 10.9 – 24.3 KB / 680 – 836 B
Cortex-M0 / STM32 streaming, arena-backed (zero heap) NOTE_MINIMAL typed API fits comfortably
ESP32 / Cortex-M4+ streaming with arena allocator defaults full typed API + body structs
Linux / macOS host tree path with a JSON backend cJSON or nlohmann backend full surface, heap allowed

"Zero heap" on the constrained tiers means arena-backed — string interning lands in a user-supplied MonotonicArena (or HeapResetPool) and is reclaimed wholesale by arena.reset(). Plug in the default note::Allocator{} (malloc/free) and you'll get a heap; the typed API doesn't care which, but only arenas keep the build genuinely heap-free.

The full progression (Arduino Uno, 8-request app)

Each row peels off one more layer; the typed API (rows 1–2) is what most users want, rows 3–5 trade some convenience for reduced flash use. All five styles share the same transport and can be mixed in one image — the compiler drops what you don't call.

# Style Flash Δ flash vs typed RAM
note-c baseline (Notecard::requestAndResponse) 25,076 B −18 B 729 B + 371 B heap
1 Typed API groups (api.hub.set().product(...).execute()) 25,680 B +586 B 773 B + 0 B heap
2 Typed direct (nc.execute(HubSet{...})) 25,094 B baseline 753 B + 0 B heap
3 Raw JSON + SAX sink (JsonBuf + transact_dispatch + JsonSink) 21,754 B −3,340 B 781 B + 0 B heap
4 Raw + JsonView scan (RAM keys) 11,790 B −13,304 B 695 B + 0 B heap
5 Raw + JsonView scan (F() flash keys) 11,692 B −13,402 B 679 B + 0 bytes heap

Rows 1–2 build with -DNOTE_NO_RESPONSE_RAII=1 (arena allocators don't need per-Response cleanup — see the AVR guide § Worked example). Without that flag rows 1–2 grow by ~2.2 KB.

Per-row code patterns: Arduino guide § Binary size comparison. Compile-time switches: docs/feature-flags.md. Benchmark harness: tools/binary-size-comparison/.

Documentation

note-cpp ships full prose documentation alongside the headers. The four pointers below cover the most common entry points; docs/README.md is the full index.

  • Start here: Getting started — top-down walkthrough from a clean project to your first request
  • Migrating from note-arduino — side-by-side examples for common patterns
  • Feature flags — compile-time options for binary size optimization (AVR, Cortex-M0)
  • Full documentation index — all guides, from getting started to internals
  • API reference (Doxygen) — generate locally with ./ci.sh --docs or use an IDE such as VS Code for inline docs and auto-completion of the API.

Quality assurance

The same TEST_CASEs — over 2,000 of them — run on the host doctest binaries (under five compilers) and on real Notecard hardware over serial/I2C, plus a Wokwi-simulated ATmega328P runtime to catch Uno-specific init/stack issues. Host coverage stays high (95%+ lines) and is enforced in CI — see the coverage badge above. Docs are verified pre-push: every internal link resolves, every code snippet comes from a compiled source file, and the migration tables stay column-aligned by tooling.

Full breakdown: docs/quality-assurance.md.

Contributing

Bug reports and PRs welcome via Issues and Pull Requests. See CONTRIBUTING.md for development setup, building, testing, and codegen.

About

Type-safe C++ API for the Blues Notecard. Header-only, zero dependencies. C++17/20/23.

Resources

License

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors