diff --git a/ports/zephyr-cp/boards/adafruit/feather_rp2040_zephyr/autogen_board_info.toml b/ports/zephyr-cp/boards/adafruit/feather_rp2040_zephyr/autogen_board_info.toml index e2fdd50530406..05cc100c6e55a 100644 --- a/ports/zephyr-cp/boards/adafruit/feather_rp2040_zephyr/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/adafruit/feather_rp2040_zephyr/autogen_board_info.toml @@ -71,7 +71,7 @@ memorymap = false memorymonitor = false microcontroller = true mipidsi = false -msgpack = false +msgpack = true neopixel_write = false nvm = true # Zephyr board has nvm onewireio = false diff --git a/ports/zephyr-cp/boards/native/native_sim/autogen_board_info.toml b/ports/zephyr-cp/boards/native/native_sim/autogen_board_info.toml index 327c4cbb7e5c6..4b657282ef609 100644 --- a/ports/zephyr-cp/boards/native/native_sim/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/native/native_sim/autogen_board_info.toml @@ -56,7 +56,7 @@ i2cdisplaybus = true # Zephyr board has busio i2cioexpander = false i2ctarget = false imagecapture = false -ipaddress = false +ipaddress = true # Zephyr networking enabled is31fl3741 = false jpegio = false keypad = false @@ -71,7 +71,7 @@ memorymap = false memorymonitor = false microcontroller = true mipidsi = false -msgpack = false +msgpack = true neopixel_write = false nvm = true # Zephyr board has nvm onewireio = false diff --git a/ports/zephyr-cp/cptools/build_circuitpython.py b/ports/zephyr-cp/cptools/build_circuitpython.py index 63a2ed9635142..73918a06a5abb 100644 --- a/ports/zephyr-cp/cptools/build_circuitpython.py +++ b/ports/zephyr-cp/cptools/build_circuitpython.py @@ -60,6 +60,7 @@ "errno", "io", "math", + "msgpack", ] # Flags that don't match with with a *bindings module. Some used by adafruit_requests MPCONFIG_FLAGS = ["array", "errno", "io", "json", "math"] diff --git a/ports/zephyr-cp/tests/test_msgpack.py b/ports/zephyr-cp/tests/test_msgpack.py new file mode 100644 index 0000000000000..09bed35209c0a --- /dev/null +++ b/ports/zephyr-cp/tests/test_msgpack.py @@ -0,0 +1,137 @@ +# SPDX-FileCopyrightText: 2026 Tim Cocks for Adafruit Industries +# SPDX-License-Identifier: MIT + +"""Test the msgpack module.""" + +import pytest + + +ROUNDTRIP_CODE = """\ +import msgpack +from io import BytesIO + +obj = {"list": [True, False, None, 1, 3.125], "str": "blah"} +b = BytesIO() +msgpack.pack(obj, b) +encoded = b.getvalue() +print(f"encoded_len: {len(encoded)}") +print(f"encoded_hex: {encoded.hex()}") + +b.seek(0) +decoded = msgpack.unpack(b) +print(f"decoded: {decoded}") +print(f"match: {decoded == obj}") +print("done") +""" + + +@pytest.mark.circuitpy_drive({"code.py": ROUNDTRIP_CODE}) +def test_msgpack_roundtrip(circuitpython): + """Pack and unpack a dict containing the basic msgpack types.""" + circuitpython.wait_until_done() + + output = circuitpython.serial.all_output + assert "match: True" in output + assert "done" in output + + +USE_LIST_CODE = """\ +import msgpack +from io import BytesIO + +b = BytesIO() +msgpack.pack([1, 2, 3], b) + +b.seek(0) +as_list = msgpack.unpack(b) +print(f"as_list: {as_list} type={type(as_list).__name__}") + +b.seek(0) +as_tuple = msgpack.unpack(b, use_list=False) +print(f"as_tuple: {as_tuple} type={type(as_tuple).__name__}") +print("done") +""" + + +@pytest.mark.circuitpy_drive({"code.py": USE_LIST_CODE}) +def test_msgpack_use_list(circuitpython): + """use_list=False should return a tuple instead of a list.""" + circuitpython.wait_until_done() + + output = circuitpython.serial.all_output + assert "as_list: [1, 2, 3] type=list" in output + assert "as_tuple: (1, 2, 3) type=tuple" in output + assert "done" in output + + +EXTTYPE_CODE = """\ +from msgpack import pack, unpack, ExtType +from io import BytesIO + +class MyClass: + def __init__(self, val): + self.value = val + +data = MyClass(b"my_value") + +def encoder(obj): + if isinstance(obj, MyClass): + return ExtType(1, obj.value) + return f"no encoder for {obj}" + +def decoder(code, data): + if code == 1: + return MyClass(data) + return f"no decoder for type {code}" + +buf = BytesIO() +pack(data, buf, default=encoder) +buf.seek(0) +decoded = unpack(buf, ext_hook=decoder) +print(f"decoded_type: {type(decoded).__name__}") +print(f"decoded_value: {decoded.value}") +print("done") +""" + + +@pytest.mark.circuitpy_drive({"code.py": EXTTYPE_CODE}) +def test_msgpack_exttype(circuitpython): + """ExtType with a custom encoder/decoder should round-trip.""" + circuitpython.wait_until_done() + + output = circuitpython.serial.all_output + assert "decoded_type: MyClass" in output + assert "decoded_value: b'my_value'" in output + assert "done" in output + + +EXTTYPE_PROPS_CODE = """\ +from msgpack import ExtType + +e = ExtType(5, b"hello") +print(f"code: {e.code}") +print(f"data: {e.data}") + +e.code = 10 +print(f"new_code: {e.code}") + +try: + ExtType(128, b"x") +except (ValueError, OverflowError) as ex: + print(f"range_error: {type(ex).__name__}") + +print("done") +""" + + +@pytest.mark.circuitpy_drive({"code.py": EXTTYPE_PROPS_CODE}) +def test_msgpack_exttype_properties(circuitpython): + """ExtType exposes code/data as read/write properties and rejects out-of-range codes.""" + circuitpython.wait_until_done() + + output = circuitpython.serial.all_output + assert "code: 5" in output + assert "data: b'hello'" in output + assert "new_code: 10" in output + assert "range_error:" in output + assert "done" in output diff --git a/shared-module/msgpack/__init__.c b/shared-module/msgpack/__init__.c index a98fb02de39dd..7c3e664bb6c1e 100644 --- a/shared-module/msgpack/__init__.c +++ b/shared-module/msgpack/__init__.c @@ -394,7 +394,9 @@ static mp_obj_t unpack(msgpack_stream_t *s, mp_obj_t ext_hook, bool use_list) { size_t len = code & 0b1111; mp_obj_dict_t *d = MP_OBJ_TO_PTR(mp_obj_new_dict(len)); for (size_t i = 0; i < len; i++) { - mp_obj_dict_store(d, unpack(s, ext_hook, use_list), unpack(s, ext_hook, use_list)); + mp_obj_t key = unpack(s, ext_hook, use_list); + mp_obj_t value = unpack(s, ext_hook, use_list); + mp_obj_dict_store(d, key, value); } return MP_OBJ_FROM_PTR(d); } @@ -462,7 +464,9 @@ static mp_obj_t unpack(msgpack_stream_t *s, mp_obj_t ext_hook, bool use_list) { size_t len = read_size(s, code - 0xde + 1); mp_obj_dict_t *d = MP_OBJ_TO_PTR(mp_obj_new_dict(len)); for (size_t i = 0; i < len; i++) { - mp_obj_dict_store(d, unpack(s, ext_hook, use_list), unpack(s, ext_hook, use_list)); + mp_obj_t key = unpack(s, ext_hook, use_list); + mp_obj_t value = unpack(s, ext_hook, use_list); + mp_obj_dict_store(d, key, value); } return MP_OBJ_FROM_PTR(d); }