Writing a Module¶
An ESPM module is a single C file (which may include sibling files) plus a module.toml manifest. It has no ESP-IDF dependency: every firmware service is reached through the syscall table.
Two ways to write the entry point¶
Convenience header (recommended)¶
Include espm.h once. It supplies module_entry, a file-scope static espm_sys_t *sys, weak callbacks, and macros that map names like printf, malloc, and gpio_set directly onto the syscall table. Do not include <stdio.h>, <stdlib.h>, or <string.h> - the header provides equivalents.
#include "espm.h"
void espm_init(void *arg) { // called once on load - register commands here
cmd_register("scan", cmd_scan, NULL);
printf("scanner loaded\n");
}
void espm_exec(void) { // main task, runs in its own FreeRTOS task
while (!task_notify_wait(5000)) {
watchdog_feed();
}
}
void espm_stop(void) { // before unload - release resources
printf("scanner shutting down\n");
}
Callbacks are weak; define only the ones you need.
Raw ABI¶
Include espm_module.h and export module_entry yourself, switching on cmd. This is what the mod_hello template does:
#include "espm_module.h"
int module_entry(espm_sys_t *sys, int cmd, void *arg)
{
if (cmd == ESPM_CMD_INIT) { sys->printf("init OK\n"); return 0; }
if (cmd == ESPM_CMD_EXEC) {
uint32_t model, cores, rev;
sys->get_chip_info(&model, &cores, &rev);
sys->printf("heap=%lu\n", (unsigned long)sys->get_free_heap());
return 0;
}
if (cmd == ESPM_CMD_STOP) { sys->printf("stop\n"); return 0; }
return -1;
}
Registering commands and returning output¶
Inside espm_init, register named C2 commands:
A handler receives an espm_cmd_ctx_t:
typedef struct {
const char *args; // CLI args after the command name
const char *request_id; // C2 request id (may be NULL)
void *module_ctx; // your private state
} espm_cmd_ctx_t;
Return output to the operator with the C2 messaging syscalls. msg_result is the mandatory end-of-command marker:
| Syscall | Use |
|---|---|
msg_info(tag, msg, request_id) |
Human-readable progress |
msg_error(tag, msg, request_id) |
Error (follow with msg_result) |
msg_data(tag, data, len, eof, request_id) |
Binary data frame |
msg_result(tag, msg, request_id) |
Signal completion (call exactly once) |
Feeding the C3PO Data view¶
Modules that populate C3PO's Data tables emit pipe-delimited lines via msg_info, for example:
AP|<ssid>|<bssid>|<rssi>|<channel>|<auth> # Data > WiFi
BLE|<addr>|<name>|<rssi>|<type> # Data > BLE
HOST|<ip>|<mac>|<latency>|<ports> # Data > Network
A complete command handler¶
static int cmd_scan(espm_cmd_ctx_t *ctx)
{
espm_ap_record_t aps[16];
uint16_t count = 16;
wifi_scan_start();
wifi_scan_get(aps, &count);
for (uint16_t i = 0; i < count; i++) {
char buf[96];
snprintf(buf, sizeof(buf), "AP|%s|%02x..|%d|%d|%d",
(const char *)aps[i].ssid, aps[i].bssid[0],
aps[i].rssi, aps[i].channel, aps[i].authmode);
msg_info("scanner", buf, ctx->request_id);
}
msg_result("scanner", "done", ctx->request_id);
return 0;
}
The module.toml manifest¶
Every module directory has a module.toml that C3PO parses to build the catalog and drive compilation:
[module]
name = "mod_example" # defaults to the directory name
version = "1.0.0" # defaults to "0.0.0"
description = "what it does"
targets = ["esp32", "esp32c6"] # chip base names and/or board variants
source = "cmd_example.c" # entry .c to compile (defaults to "<name>.c")
[commands] # name -> help string, shown in the Modules view
example_cmd = "Do the thing <arg>"
[requires]
syscalls = ["socket", "wifi_scan"] # informational capability hints
min_heap = 32768 # bytes
A module supports a chip if a targets entry equals the chip or its base name (so esp32s3-cam-n16r8 matches a target of esp32s3).
Guidelines¶
- Long-running loops must call
watchdog_feed()regularly (default timeout 30 s). - Spawn background work with
task_create(...); a spawned task must calltask_exit()before returning. - Free everything in
espm_stop- close sockets, stop radios, delete queues and mutexes.
See also¶
- Building and Injecting - how to compile and sign
- Syscall Reference - the full API
- mod_hello - the minimal template