feat(coap): per-type RPC dispatch over /zlet/<type>/# (#31)#41
Merged
Conversation
Each opted-in zephlet type owns one COAP_RESOURCE_DEFINE at /zlet/<type>/# emitted by codegen alongside the per-type interface. The handler parses instance + method from the URI, validates the api against the type record, runs a per-method switch with typed nanopb req/resp locals on the stack, publishes via zbus_chan_pub on the command channel, and ships the encoded response through the shared send helpers in the frontend. Adds three Kconfig knobs (MAX_SEGMENT_LEN, MAX_URI_SEGMENTS, CMD_TIMEOUT_MS), selects COAP_EXTENDED_OPTIONS_LEN so URI segments up to the configured max survive coap_find_options, vendors a minimal `tick` + `ui` test zephlet for the functional pytest, and ships the 8-case `test_rpc.py` covering the happy path plus the unhappy paths bracketing the routing + decode error map. Refs #31
The per-zephlet `_codegen` custom target is already declared as a
dependency of the zephlet's own zephyr_library. The user's `app`
library that compiles `main.c` also #includes the generated
`<prefix>_interface.h` transitively (via the user-owned
`<prefix>.h`), but it has no dependency on the codegen target.
CMake's source-file scanner cannot infer the dependency because the
header does not yet exist at scan time, so ninja is free to race
the `main.c` compile against codegen. On x86_64 CI hosts the codegen
wins; on slower aarch64 Docker containers the compile starts first
and fails with `fatal error: <prefix>_interface.h: No such file or
directory`.
Defer an `add_dependencies(app ${_zg_PREFIX}_codegen)` via
`cmake_language(EVAL CODE ...)` so the prefix is substituted at
registration time (DEFER CALL would otherwise evaluate the variable
after it goes out of scope) and the dependency is wired up after
the user's CMakeLists has declared the `app` target.
Refs #31
Apple Silicon and other aarch64 hosts require the explicit `native_sim/native/64` variant — Zephyr's CMake refuses to build the default 32-bit variant on a 64-bit userspace. Listing both the bare `native_sim` (matches x86_64 CI runners) and `native_sim/native/64` (matches ARM64 local Docker containers) lets the same testcase yaml serve both environments.
Drop `eth_native_tap` in favor of `NET_NATIVE_OFFLOADED_SOCKETS`. The native simulator forwards Zephyr's socket calls to the host BSD stack, so the CoAP server binds a real host socket. The pytest fixture reaches it at 127.0.0.1:5683 without any TAP device, host- side IP configuration, root privileges, or `/dev/net/tun` access — works the same on bare Linux runners, Docker containers, and Colima on macOS. Also widen `COAP_EXTENDED_OPTIONS_LEN_VALUE` to 64 (from 32) under `ZEPHLETS_COAP` so the CoAP parser accepts URI segments slightly longer than the zephlet handler's segment buffer — the handler is the one to surface 4.04 on overflow, which is what the oversized- segment test asserts. A flat 32 had the parser reject the frame before the handler ever ran, leaving the client retransmitting indefinitely. Pattern follows zephyr/samples/net/sockets/echo_server/overlay-nsos.conf.
The existing `twister` job only exercised the app's per-zephlet integration tests (`app/src/<z>/tests/integration/`). The infra's own suites — `coap_translate` (ztest), `shared/dispatch` (ztest), and `coap_functional` (pytest with aiocoap) — were never invoked, so build or behavior regressions in the codegen / frontend could slip past CI. Add a second `west twister` invocation against `modules/lib/zephlet/tests` after the app run, with an `aiocoap + pytest-asyncio` pip install so the functional pytest harness can drive the live CoAP frontend.
The zephyr-build:main image ships without aiocoap, pytest-asyncio, proto-schema-parser, or the rest of the codegen Python deps that twister needs. Baking them into a local `zephlet-tester:latest` image once removes the per-invocation `pip install` and the accompanying VNC/X startup chatter. `just docker-build` builds the image, `just docker-test` runs the full twister suite against the workspace root mounted at /workdir, and `just docker-shell` opens an interactive shell in the same image for ad-hoc debugging. `platform` defaults to `native_sim/native/64` (aarch64 hosts); pass `native_sim` explicitly for x86_64.
15a615f to
47c863e
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Phase 3 of the CoAP frontend adoption: per-type RPC dispatch over UDP.
COAP_RESOURCE_DEFINEper opted-in zephlet type at/zlet/<type>/#in<prefix>_coap_interface.calongside a per-type POST handler. The handler parses instance + method out of the URI, validatesz->api == &<type>_apidefensively, and dispatches via a per-methodswitchwith typed nanopb req/resp locals on the stack. The C compiler sizes the function frame from those locals — no cross-type max macros.zephlet_coap_send_{error,response}and URI-parsing helpers.zephlet_coap_frontend.cowns the singleCOAP_SERVICE_DEFINE(zlet_coap_service, …).MAX_SEGMENT_LEN,MAX_URI_SEGMENTS,CMD_TIMEOUT_MS.ZEPHLETS_COAPnow selectsCOAP_EXTENDED_OPTIONS_LENso URI segments up to the configured max survivecoap_find_optionswithout truncation.tick+uizephlets so the api-mismatch path runs end-to-end. Pytest cases cover happy + five unhappy paths plus the GET stub (4.05 until the events bridge lands). The test target usesNET_NATIVE_OFFLOADED_SOCKETSso the host pytest fixture reaches the guest CoAP server directly on127.0.0.1— no TAP setup, no root, no/dev/net/tun— and the same suite runs identically on bare Linux, Docker, and Colima.modules/lib/zephlet/tests(the existing job only ran the app's per-zephlet integration tests). Codegen race fix added: defersadd_dependencies(app <prefix>_codegen)viacmake_language(EVAL CODE …)so slower builders no longer compilemain.cbefore the per-zephlet codegen finishes.Test plan
west twister --testsuite-root modules/lib/zephlet/tests -p native_sim --inline-logs(CI on Linux) — 35 of 35 test cases pass, 4 of 4 configurations green:coap.translate(ztest),shared.dispatch(ztest),coap_functional.smoke(pytest),coap_functional.rpc(pytest, 8 RPC cases).just docker-testfrommodules/lib/zephlet/(local Apple Silicon,native_sim/native/64viazephlet-tester:latestimage) — same 35/35 pass.just c b(app build onmps2/an385) — green, footprint identical to pre-Phase-3 (FLASH 50556 B, RAM 13972 B).just test(app's per-zephlet integration tests) — 23/23 pass.west twister --testsuite-root modules/lib/zephlet/tests -p mps2/an385(local) — 22/22 ztest cases pass; pytest configurations correctly filtered (mps2/an385 has no networking).Notes
/zlet/#" was relaxed in favor of per-type resources — each opted-in zephlet's codegen output owns its own resource registration. Gives each type self-contained CoAP routing and eliminates the catch-all's URI dispatch table.MAX_FROM_LISTaggregator or generated handler-max header.zephlet_coap_decode_request/..._encode_responsehelpers stay inzephlet_coap_translate.cas a documented transport-free API for callers that prefer the wrapping; the dispatch path inlinespb_decode/pb_encodefor explicitness.Dockerfile+justfileat the infra root give a one-shotjust docker-build/just docker-testworkflow for local iteration without re-installing Python deps per run.Closes #31