中文版 / Chinese version: README.zh.md
Embedded enhanced shell — zero malloc, zero OS dependency, runs on bare-metal microcontrollers.
Distills the best ideas from Zephyr Shell, RT-Thread msh, FreeRTOS CLI, USMART, nr_micro_shell, and letter-shell into three unique features.
Documentation (see docs/ for details):
| Document | Contents |
|---|---|
| docs/README.md | Documentation index |
| docs/getting_started.md | Build, test, directory layout, and configuration entry points |
| docs/extensions_overview.md | Extension modules and macro reference |
| docs/porting_general.md | General porting checklist (linker script, tick, I/O) |
| docs/porting_esp_idf.md | Dedicated ESP-IDF porting guide |
| docs/ai_bridge.md | AI HTTP bridge: switches, callbacks, the ai command, gateway, and security |
Other embedded shells only support flat commands. Zoom Shell supports unlimited hierarchical nesting of subcommands, and Tab completion walks the tree:
ZOOM_SUBCMD_SET(sub_gpio,
ZOOM_SUBCMD(set, cmd_gpio_set, "Set pin level"),
ZOOM_SUBCMD(get, cmd_gpio_get, "Get pin level"),
ZOOM_SUBCMD(toggle, cmd_gpio_toggle, "Toggle pin"),
);
ZOOM_EXPORT_CMD_WITH_SUB(gpio, sub_gpio, "GPIO operations",
ZOOM_ATTR_DEFAULT, ZOOM_USER_USER);zoom> gpio set 13 1
[OK] GPIO pin 13 set to 1
Inspect and modify any registered variable in real time over the serial port, similar to Simulink External Mode:
static int32_t pid_kp = 100;
ZOOM_EXPORT_VAR(pid_kp, pid_kp, ZOOM_VAR_INT32, "PID Kp x100", ZOOM_VAR_RW, ZOOM_USER_GUEST);zoom> var list
Name Type Value Attr Description
pid_kp int32 100 [RW] PID Kp x100
motor_speed float 0.000 [RO] Motor RPM
zoom> var set pid_kp 200
[OK] pid_kp = 200
Inspired by USMART, automatically measures command execution time:
zoom> perf on
zoom> some_command
[OK] some_command (elapsed: 12 ms)
| Feature | letter-shell | nr_micro | FreeRTOS CLI | Zephyr | msh | USMART | Zoom Shell |
|---|---|---|---|---|---|---|---|
| Subcommand tree | - | - | - | Y | - | - | Y |
| Variable observation | - | - | - | - | - | Partial | Y |
| Command timing | - | - | - | - | - | Y | Y |
| User permissions | Y | - | - | - | - | - | Y |
| Tab completion | Y | Y | - | Y | - | - | Y |
| History | Y | Y | - | Y | - | - | Y |
| Zero malloc | Y | Y | - | Y | Y | Y | Y |
| Linker-section registration | Y | - | - | Y | Y | - | Y |
| Conditionally-compiled commands | - | - | - | Y | - | - | Y |
| ANSI color output | - | - | - | Y | - | - | Y |
When the firmware side needs to forward a sentence from the serial port to a self-hosted HTTP gateway (which then talks to an LLM, an OpenClaw-compatible service, etc.), enable ZOOM_USING_AI_BRIDGE and compile and link extensions/ai_bridge/zoom_shell_ai_bridge.c.
- Shell commands:
ai url <http(s)://...>sets the POST endpoint;ai ask <text...>sends the whole text as the request body;ai statusshows the configuration and whether the callback is ready. - Port implementation: the library does not include a TLS/HTTP implementation. After a successful
zoom_shell_init(), callzoom_ai_bridge_set_post(shell, your_http_post), and inside the callback use ESP-IDFesp_http_client, mbedTLS, etc. to send the POST and write the response into a buffer for the shell to print. - Security: keep the API key in the gateway's environment variables and let the gateway access the cloud API; the firmware should only hold an internal or configurable URL.
Detailed documentation: docs/ai_bridge.md. See "Interoperating with the OpenClaw / Claw ecosystem" below for how this relates to the OpenClaw / Claw ecosystem.
cd zoom-shell
make demo
./build/zoom_shell_demoStep 1: create a configuration file zoom_shell_cfg_user.h
#define ZOOM_USING_HISTORY 1
#define ZOOM_USING_USER 1
#define ZOOM_USING_VAR 1
#define ZOOM_CMD_MAX_LENGTH 128
#define ZOOM_HISTORY_MAX_NUMBER 5
#define ZOOM_PRINT_BUFFER 128
#define ZOOM_GET_TICK() HAL_GetTick() /* your millisecond tick */Step 2: implement the I/O callbacks
#include "zoom_shell.h"
zoom_shell_t shell;
char shell_buffer[768]; /* 128 * (5+1) = 768 */
int16_t uart_read(char *data, uint16_t len) {
/* Read one byte from the UART */
(void)len;
if (HAL_UART_Receive(&huart1, (uint8_t *)data, 1, HAL_MAX_DELAY) == HAL_OK)
return 1;
return 0;
}
int16_t uart_write(const char *data, uint16_t len) {
HAL_UART_Transmit(&huart1, (uint8_t *)data, len, HAL_MAX_DELAY);
return len;
}Step 3: initialize and run
/* Blocking mode (inside an RTOS task) */
shell.read = uart_read;
shell.write = uart_write;
zoom_shell_init(&shell, shell_buffer, sizeof(shell_buffer));
zoom_shell_run(&shell); /* does not return */
/* Or: interrupt-driven mode (bare-metal main loop) */
shell.read = uart_read;
shell.write = uart_write;
zoom_shell_init(&shell, shell_buffer, sizeof(shell_buffer));
zoom_shell_print_welcome(&shell);
zoom_shell_show_prompt(&shell);
/* Inside the UART interrupt: */
void USART1_IRQHandler(void) {
char c;
if (HAL_UART_Receive(&huart1, (uint8_t *)&c, 1, 0) == HAL_OK) {
zoom_shell_input(&shell, c);
}
}Step 4: register commands and variables
static int cmd_led(zoom_shell_t *sh, int argc, char *argv[]) {
if (argc < 1) { zoom_error(sh, "Usage: led <on|off>\r\n"); return -1; }
if (argv[0][0] == 'o' && argv[0][1] == 'n') HAL_GPIO_WritePin(LED_GPIO, LED_PIN, 1);
else HAL_GPIO_WritePin(LED_GPIO, LED_PIN, 0);
return 0;
}
ZOOM_EXPORT_CMD(led, cmd_led, "Control LED", ZOOM_ATTR_DEFAULT, ZOOM_USER_GUEST);
static int32_t adc_value = 0;
ZOOM_EXPORT_VAR(adc, adc_value, ZOOM_VAR_INT32, "ADC reading", ZOOM_VAR_RO, ZOOM_USER_GUEST);Step 5: linker script — add to your .ld file:
.zoom_command ALIGN(4) : {
_zoom_cmd_start = .;
KEEP(*(zoomCommand))
_zoom_cmd_end = .;
} > FLASH
.zoom_var ALIGN(4) : {
_zoom_var_start = .;
KEEP(*(zoomVar))
_zoom_var_end = .;
} > FLASH
.zoom_user ALIGN(4) : {
_zoom_user_start = .;
KEEP(*(zoomUser))
_zoom_user_end = .;
} > FLASHzoom-shell/
├── include/
│ ├── zoom_shell.h # Main header (data structures + API + export macros)
│ └── zoom_shell_cfg.h # Default configuration macros
├── src/
│ ├── zoom_shell_core.c # Core implementation
│ ├── zoom_shell_cmds.c # Built-in commands
│ └── zoom_shell_var.c # Variable observation system
├── demo/
│ └── x86-gcc/ # x86 platform demo
├── Makefile
└── README.md
All configuration knobs use #ifndef to provide defaults; override them at compile time with -DZOOM_SHELL_CFG_USER='"your_cfg.h"':
| Macro | Default | Description |
|---|---|---|
ZOOM_USING_CMD_EXPORT |
1 | 1 = linker section, 0 = static array |
ZOOM_USING_HISTORY |
1 | Command history |
ZOOM_USING_USER |
1 | User / permission system |
ZOOM_USING_VAR |
1 | Variable observation |
ZOOM_USING_PERF |
1 | Command timing |
ZOOM_USING_ANSI |
1 | ANSI escape codes / colors |
ZOOM_USING_LOCK |
0 | Mutex lock |
ZOOM_CMD_MAX_LENGTH |
128 | Maximum command-line length |
ZOOM_CMD_MAX_ARGS |
8 | Maximum number of arguments |
ZOOM_HISTORY_MAX_NUMBER |
5 | Number of history entries |
ZOOM_MAX_USERS |
4 | Maximum number of users |
ZOOM_PRINT_BUFFER |
128 | Formatted-output buffer |
ZOOM_GET_TICK() |
0 | Millisecond-tick accessor function |
/* Core */
int zoom_shell_init(zoom_shell_t *shell, char *buffer, uint16_t size);
int zoom_shell_run(zoom_shell_t *shell);
void zoom_shell_input(zoom_shell_t *shell, char data);
int zoom_shell_exec(zoom_shell_t *shell, const char *cmd_line);
/* Output */
zoom_printf(shell, fmt, ...); /* plain output */
zoom_info(shell, fmt, ...); /* [INFO] blue */
zoom_warn(shell, fmt, ...); /* [WARN] yellow */
zoom_error(shell, fmt, ...); /* [ERR] red */
zoom_ok(shell, fmt, ...); /* [OK] green */
/* Export macros */
ZOOM_EXPORT_CMD(name, func, desc, attr, level);
ZOOM_EXPORT_CMD_WITH_SUB(name, subcmd_set, desc, attr, level);
ZOOM_SUBCMD_SET(name, ...);
ZOOM_SUBCMD(name, func, desc);
ZOOM_EXPORT_VAR(name, var, type, desc, rw, level);
ZOOM_EXPORT_USER(name, password, level);- Zero malloc — all memory is statically allocated or supplied by the user
- Zero OS dependency — depends only on
<stdint.h><stdbool.h><stddef.h><string.h><stdarg.h> - Pure-callback I/O — read/write can be wired to UART/USB/SPI/BLE or any other interface
- Conditional-compilation trimming — every functional module can be toggled independently; minimal configuration is around 2 KB of ROM
The firmware-side ai command and HTTP callback are described above under "AI HTTP Bridge (optional extension)"; this section covers how responsibilities are split with host-side tooling.
OpenClaw, NullClaw, ZeroClaw, Mimiclaw and similar projects belong to the "self-hosted AI assistant / multi-channel orchestration" category (typically Node, Rust, or Zig, or MCU firmware with a network stack). They have different responsibilities from Zoom Shell (a serial debugging command line) and will not be merged into this repository's core as submodules.
Recommended ways to combine them:
| Approach | Description |
|---|---|
| Serial bridge (host side) | The device runs Zoom Shell while OpenClaw or similar tools run on the PC; text is sent over the serial port to the device, and the device side still goes through the read/write callbacks. No library changes needed — just agree on the baud rate and line protocol in documentation or scripts. |
Optional ai extension (firmware side) |
With ZOOM_USING_AI_BRIDGE enabled, you provide an HTTP POST callback (implemented by you on top of ESP-IDF / mbedTLS / etc.) and can use ai url / ai ask to POST a line of text to a self-hosted gateway, which then forwards it to any LLM or OpenClaw-compatible service. The library does not include a TLS/HTTP implementation, avoiding a hard dependency on any network stack. |
Relationship to projects like Mimiclaw ("MCU + WiFi + LLM"): conceptually complementary — you can borrow their approach to networking and key management; if you need similar capabilities, integrate the platform SDK yourself on top of this extension, and do not claim compatibility with their upstream APIs.
Pushes to main / master and pull requests automatically run make test and make demo.
Cut a new release (run at the repository root):
git tag -a v1.2.0 -m "Zoom Shell 1.2.0"
git push origin v1.2.0Pushing a tag that matches v*.*.* triggers the tests again, uses git archive to produce zoom-shell-<version>.tar.gz / .zip, attaches zoom-shell-<version>-demo-linux-x86_64 (the demo built on Ubuntu) and SHA256SUMS, and automatically creates and uploads the GitHub Release.
MIT