Skip to content

User Guide

Overview

chumicro-msgpack serializes Python objects to compact binary bytes using the MessagePack format and deserializes them back. It covers the subset of msgpack needed on microcontrollers: integers up to 32-bit, 32-bit floats, strings, bytes, booleans, None, lists, tuples, and dicts.

The library exposes four functions: packb and unpackb for bytes-based encoding/decoding, and pack and unpack for stream-based I/O. On CircuitPython boards with the native C msgpack module, all four delegate to the built-in — the pure-Python encoder is never loaded.

When to use msgpack vs struct

Python's struct module and msgpack both produce compact binary data, but they solve different problems:

struct msgpack
Schema Fixed layout — both sides must agree on a format string (e.g., ">HBf") Self-describing — types are encoded in the data
Flexibility Adding or removing a field changes the layout and breaks readers Dicts and arrays grow naturally; old readers ignore unknown keys
Size Smallest possible for a known fixed layout Slightly larger due to type tags, but still much smaller than JSON
Best for Fixed sensor packets, register maps, wire protocols with a spec Settings dicts, configuration storage, messages between devices that may run different firmware versions

Use struct when the data layout is fixed and both sides are compiled together (e.g., a sensor reading struct that never changes). Use msgpack when the data is dict-like, may evolve over time, or when you want to inspect the data without knowing the schema.

Getting started

from chumicro_msgpack import packb, unpackb

data = packb({"ssid": "MyNetwork", "configured": True})
print(data)          # compact binary bytes

restored = unpackb(data)
print(restored)      # {'ssid': 'MyNetwork', 'configured': True}

Stream-based API (preferred on microcontrollers)

pack and unpack write to and read from stream objects (anything with .write() or .read()). This matches CircuitPython's native msgpack API.

When writing to a file, socket, or NVM wrapper, prefer pack over packb — it writes directly to the destination without building an intermediate bytes object in RAM.

from io import BytesIO
from chumicro_msgpack import pack, unpack

buffer = BytesIO()
pack({"key": [1, 2, 3]}, buffer)

buffer.seek(0)
result = unpack(buffer)
print(result)  # {'key': [1, 2, 3]}

Bytes-based API

packb and unpackb work with bytes objects directly. They are convenient when you need the encoded data in memory — for example, to measure its length before writing it with a framing header, or to pass it to an API that expects bytes.

On microcontrollers, be aware that packb allocates a temporary bytearray, grows it during encoding, then copies it to bytes. For small payloads (typical settings dicts) this is fine. For larger data or tight loops, prefer the stream-based pack to avoid the intermediate allocation.

from chumicro_msgpack import packb, unpackb

# Encode any supported Python object.
packed = packb([1, "hello", None, True])

# Decode from bytes, bytearray, or memoryview.
original = unpackb(packed)
print(original)  # [1, 'hello', None, True]

unpackb accepts bytes, bytearray, and memoryview, so you can decode directly from a pre-allocated buffer without copying.

Integer keys for compact storage

When storing settings in NVM or sleep memory, use integer keys instead of strings. Integer keys encode in 1 byte (vs. multiple bytes for quoted strings), saving space on every entry:

from chumicro_msgpack import packb, unpackb
import json

settings = {0: "MyNetwork", 1: "secret123", 2: "lamp", 3: True}

msgpack_size = len(packb(settings))
json_size = len(json.dumps(settings))

print(f"msgpack: {msgpack_size} bytes")
print(f"JSON:    {json_size} bytes")
# msgpack is significantly smaller

Supported types

Python type Notes
None, True, False 1 byte each
int −2³¹ to 2³²−1; uses the smallest encoding automatically
float 32-bit (float32); limited precision compared to CPython's 64-bit float
str UTF-8 encoded; up to 65535 bytes
bytes / bytearray Up to 65535 bytes
list / tuple Tuples encode as arrays; decoding always returns lists
dict Up to 65535 entries; keys can be any supported type

Unsupported types raise TypeError. Integers outside the 32-bit range raise OverflowError.

Platform notes

Runtime What happens
CircuitPython (hardware) Native C msgpack module handles all four functions. The pure-Python encoder (_pure.py) is never imported — saves ~700 bytes of heap RAM.
CircuitPython (unix port) Native module is not compiled in; uses the pure-Python encoder.
MicroPython Pure-Python encoder (MicroPython has no built-in msgpack).
CPython Pure-Python encoder (CPython's msgpack is a third-party PyPI package, not stdlib).

The wire format is identical regardless of which implementation is used — data packed on one runtime can be unpacked on any other.

What's new

  • 0.1.23: CI and documentation improvements.