Reusable framework for building domain-isolated components on Zephyr RTOS, communicating exclusively over zbus. Each zephlet is a non-singleton, instance-per-ZEPHLET_NEW module with two channels: a synchronous pointer-based command channel and a value-typed events channel.
See CLAUDE.md for the full architecture reference.
manifest:
projects:
- name: zephlet
url: https://github.com/rodrigopex/zephlet
revision: main
path: modules/lib/zephlet
west-commands: west/west-commands.yml
self:
path: appwest init -l .
west update --narrow --fetch-opt=--depth=1
west packages pip --installFrom anywhere you want the zephlet to land:
cd path/to/wherever
west zephlet new -n <Name> -d "<description>" -a "<author>"or non-interactively with an explicit destination:
west zephlet new -o path/to/wherever -n <Name> -d "<description>" -a "<author>"Two further options shape the layout:
--prefix STR— file-name prefix for the generated sources. Defaultzlet_(so a zephlettickproduceszlet_tick.{c,h,proto}); pass--prefix=to drop the prefix entirely (tick.{c,h,proto}). The prefix also flows into the header guard, log module name, and thePREFIXargument ofzephyr_zephlet_generateso the generated<prefix>_interface.{h,c}stays consistent.--no-module— produce a minimal scaffold with justCMakeLists.txt,Kconfig, and the source files. Skips thetests/integration/folder andzephyr/module.yml, leaving you free to wire the directory into your app however you like (e.g. as part of a larger module rather than as a standalone Zephyr module).
The scaffold has no opinion on where zephlets must live — the Copier template drops a complete module under the destination directory. Users wire it into their app by adding its path to EXTRA_ZEPHYR_MODULES and enabling CONFIG_ZEPHLET_<NAME>=y.
set(EXTRA_ZEPHYR_MODULES "${CMAKE_SOURCE_DIR}/path/to/my_zephlet" ...)Instantiate and use:
#include "zlet_my_zephlet.h"
static struct my_zephlet_data my_data;
static struct my_zephlet_config my_cfg = { /* ... */ };
ZEPHLET_NEW(my_zephlet, my_instance, &my_cfg, &my_data, my_zephlet_init_fn);
/* ... in main or elsewhere ... */
struct lifecycle_status st;
my_zephlet_start(&my_instance, &st, K_MSEC(500));commandchannel (pointer, listener-only): synchronous command via zbus sync-listener. Wrapper returns the handler's rc directly — no correlation IDs, no semaphores, no result struct.eventschannel (value-typed): async fan-out. Publishers call<type>_emit(z, &ev, timeout); consumers observe withZEPHLET_EVENTS_LISTENER(instance, type, callback)(wrapsZBUS_ASYNC_LISTENER_DEFINE).- Non-singleton: multiple instances per type coexist; each has its own channel pair and data.
- Weak handler overrides: generator emits
__weak int <type>_<cmd>_impl(...)returning-ENOSYS; user provides strong overrides in<prefix>.c.
Not a framework concept in v0.3. An "adapter" is plain user code — usually a single .c file composed from the ZEPHLET_EVENTS_LISTENER primitive:
static void on_tick(const struct zephlet *z,
const struct tick_events *ev) {
ARG_UNUSED(z);
ARG_UNUSED(ev);
(void)ui_blink(&ui_instance, K_MSEC(100));
}
ZEPHLET_EVENTS_LISTENER(tick_instance, tick, on_tick);Guard the translation unit at CMake level when it references channels from optional zephlets:
if(CONFIG_ZEPHLET_TICK AND CONFIG_ZEPHLET_UI)
target_sources(app PRIVATE adapters.c)
endif()| Command | Purpose |
|---|---|
west zephlet new [-o <dir>] [-n -d -a] [--prefix STR] [--no-module] |
Copier scaffold. Destination = $PWD unless -o. --prefix overrides the default zlet_ ("" drops it); --no-module skips the tests folder and zephyr/module.yml. |
west zephlet new-adapter |
Prints the v0.3 recipe. No codegen. |
west zephlet gen <zephlet_dir> |
Regenerate <prefix>_interface.{h,c} from its proto. |
- Zephyr RTOS (with
zbus,nanopbmodules). - Python packages:
proto-schema-parser,jinja2,copier.
Reference implementation: ports_adapter_zbus.
Apache-2.0