Note-emu - a drop-in virtual notecard library for note-c and note-cpp [community project]

Hi all :waving_hand:

Following on from note-cpp, I’d like to share note-emu — a virtual Notecard you can use before your hardware arrives, or as part of your integration tests once it does.

It’s a drop-in transport for note-c/note-arduino and note-cpp that speaks to Blues’ cloud-hosted Notecard simulator (“softcard”) over HTTP. Your firmware calls the real Notecard API from day one; only the transport underneath is virtual.

The readme includes links to online Wokwi projects for note-c and note-cpp. Both Wokwi projects are ready to run once you add a Notehub personal access token - see secrets.h in each project.

What a run looks like — same request/response pattern you’d see on real hardware, only the bytes go to softcard.blues.com:

note-emu: resolved account UID: 00000000-0000-0000-0000-000000000000
=== iteration 1/5 ===
note-emu: POST /v1/write (46 bytes) -> rc=0 http=200 [685 ms]
note-emu: POST /v1/read  -> rc=0 http=200 bytes=384 [132 ms]
  version = notecard-11.1.1.1301

That notecard-11.1.1.1301 is a real Notecard firmware version. If your hub.set supplies a product, events reach your Notehub project through the same schema, routes, and event stream you would use with a physical Notecard. The only difference is that they are attributed to a softcard virtual device.

The whole idea is that switching to hardware is a transport swap without having to rewrite your notecard calls. Transport setup with note-emu:

// Virtual: WiFi → softcard
note::emu::Arduino softcard(NOTEHUB_PAT);
softcard.begin(wifiClient);
note::emu::SerialHal hal(*softcard.instance(), millis, delay);
note::link::SerialFramer<> framer(hal);
note::Protocol transport(framer);
note::Notecard nc(transport);

Against a real Notecard, the application is unchanged, just rewire the transport:

// Physical: I²C to Notecard
Notecard nc;
nc.begin(Wire);

Everything after ncnc.hub.set()..., nc.card.version(), nc.note.add() — is unchanged.

A few things I think make it nice:

  • :globe_with_meridians: Zero-install prototyping — Wokwi browser sim + PAT → your first note.add in ~2 minutes
  • :puzzle_piece: Works with both note-c and note-cpp — pick your API. Umbrella headers make integration one line
  • :desktop_computer: Native builds too — a libcurl backend for desktop tests and CI; great for regression tests that hit softcard without any hardware in the loop
  • :electric_plug: Transport-level swap — plugs in at the serial-hook interface (NoteSetFnSerial for note-c, note::link::SerialHal for note-cpp). Same application code all the way to production
  • :package: Standalone — no external deps beyond your chosen HTTP backend
  • :test_tube: Examples for everything — Arduino sketches (note-c and note-cpp), PlatformIO projects, Wokwi projects, a native/libcurl demo

A note on scope: This is an independent community project — not affiliated with or supported by Blues, and “Notecard” is their trademark. It’s aimed at prototyping, testing, and integration. The README has a “Should I use this in Production?” section on why the physical Notecard beats software (cellular, power budget, security, edge reliability); but for the “waiting for hardware” and “keep our CI honest” cases, it’s a solid tool.

Repo, docs, and examples: GitHub - m-mcgowan/note-emu

Feedback, bug reports, and “I tried it on X” reports are all welcome. :folded_hands:

Small update since posting — the transport setup and integration surface both got simpler in note-emu 0.3.3, so the code in the OP no longer matches what the linked Wokwi projects are running.

Two things changed:

  1. Transport is a one-liner now. installNoteCpp() (or installNoteCppBridge() when using note-c and note-cpp together) builds the whole streaming chain internally, so the explicit SerialHal / SerialFramer<> / Protocol / Notecard wiring in the OP isn’t needed anymore.
  2. The library also supports note-c directly, and both APIs side-by-side. The OP only showed the note-cpp path. There are three ways to wire it now, depending on which API you’re using:

Using note-c

note::emu::Arduino softcard(NOTEHUB_PAT);
softcard.begin(wifiClient);
softcard.installNoteC();

J *rsp = NoteRequestResponse(NoteNewRequest("card.version"));

Wokwi: wokwi.com/projects/465203727626487809

Using note-cpp

note::emu::Arduino softcard(NOTEHUB_PAT);
softcard.begin(wifiClient);
auto &nc = note::emu::installNoteCpp(softcard);

auto rsp = nc.card.version().execute();

Wokwi: wokwi.com/projects/469739119860047873

Both together (for incremental adoption of note-cpp in an existing note-c sketch)

note::emu::Arduino softcard(NOTEHUB_PAT);
softcard.begin(wifiClient);
softcard.installNoteC();
auto &nc = note::emu::installNoteCppBridge(softcard);

NoteRequestResponse(NoteNewRequest("hub.set"));   // note-c
nc.card.version().execute();                      // note-cpp

Wokwi: wokwi.com/projects/470467610960276481

The physical-Notecard side is the same swap the OP shows, just with the modern note-cpp type name:

note::arduino::Notecard nc;
nc.begin(Wire);

Everything after nc.begin()nc.hub.set()..., nc.card.version(), nc.note.add() — is unchanged from the virtual installNoteCpp / installNoteCppBridge versions above.