Layer: L6 Presentation ยท Build flags: PROTOCORE_ENABLE_MSGPACK
What this example teaches
This is the CBOR example in a different wire format, in both directions: GET /telemetry.msgpack encodes the same {heap, uptime, rssi} map with the zero-heap MessagePack writer and streams it as application/msgpack, and POST /decode parses a posted MessagePack map with the zero-heap cursor decoder. MessagePack is widely supported across languages, so pick it over CBOR when your consuming stack already speaks it - the API and the zero-heap pattern are identical.
Encoding with protocore_span. Initialize over a stack buffer, declare the map size, emit pairs, check protocore_span_ok(), write protocore_span_len() bytes:
uint8_t buf[64];
protocore_span w;
w = protocore_span_from( buf, sizeof(buf));
MsgPack.put_map(&w, 3);
MsgPack.put_str(&w, "heap"); MsgPack.put_uint(&w, ESP.getFreeHeap());
MsgPack.put_str(&w, "uptime"); MsgPack.put_uint(&w, millis() / 1000);
MsgPack.put_str(&w, "rssi"); MsgPack.put_int(&w, Physical.wifi->rssi());
ctx.len = protocore_span_ok(w) ? protocore_span_len(w) : 0;
As with CBOR, the payload is binary so it is delivered through the binary-safe send_chunked(), which pulls the encoded bytes from a ChunkSource generator (slice by slice, returning 0 to finish) rather than the C-string send(). The generator's ctx must outlive the call, so it is static.
Decoding with protocore_cspan. The cursor decoder is the mirror image: bind a reader to the bytes, read the map header, then each key/value, and check protocore_cspan_ok() once at the end (it is sticky, so a single check covers the whole parse). Strings point straight into the source buffer, no copy:
protocore_cspan r;
r = protocore_cspan_from( req->body, req->body_len);
size_t count;
if (!MsgPack.get_map(&r, &count)) { }
for (size_t i = 0; i < count && protocore_cspan_ok(r); i++) {
const char *key; size_t klen; int64_t val;
if (!MsgPack.get_str(&r, &key, &klen) || !MsgPack.get_int(&r, &val)) break;
}
if (!protocore_cspan_ok(r)) { }
Every read is bounds-checked, so malformed or truncated input fails closed rather than over-reading. MsgPack.peek() reports the next object's type if you need to branch on it.
Build and run
pio ci --board=esp32dev --project-option="framework=arduino" \
--project-option="build_flags=-DPROTOCORE_ENABLE_MSGPACK=1" \
--lib="." examples/L6-Presentation/MsgPack/MsgPack.ino
curl -s http://<ip>/telemetry.msgpack | xxd
# decode side: post a one-pair map {"led": 1} (0x81 0xa3 'led' 0x01)
printf '\x81\xa3led\x01' | curl -s --data-binary @- http://<ip>/decode
Annotated source
The complete sketch (MsgPack.ino), reproduced verbatim with added explanatory comments:
#define PROTOCORE_ENABLE_MSGPACK 1
#include "protocore.h"
static const char *SSID = "YOUR_SSID";
static const char *PASSWORD = "YOUR_PASSWORD";
PC server;
struct MpCtx
{
uint8_t buf[64];
size_t len, off;
};
static size_t protocore_msgpack_source(uint8_t *out, size_t cap, void *vctx)
{
MpCtx *c = (MpCtx *)vctx;
if (c->off >= c->len)
return 0;
size_t n = c->len - c->off;
if (n > cap)
n = cap;
memcpy(out, c->buf + c->off, n);
c->off += n;
return n;
}
static void on_decode(uint8_t
id,
HttpReq *req)
{
protocore_cspan r;
size_t count;
if (!MsgPack.get_map(&r, &count))
{
server.send(id, 400, "text/plain", "expected a MessagePack map");
return;
}
char out[160];
size_t o = 0;
for (size_t i = 0; i < count && protocore_cspan_ok(r); i++)
{
const char *key;
size_t klen;
int64_t val;
if (!MsgPack.get_str(&r, &key, &klen) || !MsgPack.get_int(&r, &val))
break;
o += snprintf(out + o, sizeof(out) - o, "%.*s=%lld\n", (int)klen, key, (long long)val);
}
if (!protocore_cspan_ok(r))
{
server.send(id, 400, "text/plain", "malformed MessagePack");
return;
}
server.send(id, 200, "text/plain", out);
}
void setup()
{
Serial.begin(115200);
Physical.wifi->init(SSID, PASSWORD);
Serial.print("Connecting to WiFi");
while (!Physical.wifi->ready())
{
delay(250);
Serial.print('.');
}
uint32_t ip = Physical.link->egress_ip();
Serial.printf("IP: %u.%u.%u.%u\n", (unsigned)(ip & 0xFF), (unsigned)((ip >> 8) & 0xFF),
(unsigned)((ip >> 16) & 0xFF), (unsigned)((ip >> 24) & 0xFF));
static MpCtx ctx;
protocore_span w;
w = protocore_span_from( ctx.buf, sizeof(ctx.buf));
MsgPack.put_map(&w, 3);
MsgPack.put_str(&w, "heap");
MsgPack.put_uint(&w, ESP.getFreeHeap());
MsgPack.put_str(&w, "uptime");
MsgPack.put_uint(&w, millis() / 1000);
MsgPack.put_str(&w, "rssi");
MsgPack.put_int(&w, Physical.wifi->rssi());
ctx.len = protocore_span_ok(w) ? protocore_span_len(w) : 0;
ctx.off = 0;
server.send_chunked(id, 200, "application/msgpack", protocore_msgpack_source, &ctx);
});
server.begin(80);
}
void loop()
{
server.handle();
}
@ HTTP_GET
Safe, idempotent read.
@ HTTP_POST
Non-idempotent create / action.
Layer 6 (Presentation) - zero-heap MessagePack encoder and decoder.
Layer 1 (Physical) - link bring-up, the interface registry, and live egress reporting.
uint8_t body[BODY_BUF_SIZE+1]