An open API service indexing awesome lists of open source software.

https://github.com/easyzoom/zoom-shell

Embedded C shell for MCUs: zero malloc, I/O callbacks, linker-section commands, subcommands, vars, users, optional extensions (ai bridge, hexdump, calc, games…). MIT.
https://github.com/easyzoom/zoom-shell

ai ai-agents c99 cli debugging embedded embedded-systems mcu shell zero-malloc

Last synced: 29 days ago
JSON representation

Embedded C shell for MCUs: zero malloc, I/O callbacks, linker-section commands, subcommands, vars, users, optional extensions (ai bridge, hexdump, calc, games…). MIT.

Awesome Lists containing this project

README

          

> 中文版 / Chinese version: [README.zh.md](./README.zh.md)

# Zoom Shell v1.2

**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/](docs/en/) for details):

| Document | Contents |
|------|------|
| [docs/README.md](docs/en/README.md) | Documentation index |
| [docs/getting_started.md](docs/en/getting_started.md) | Build, test, directory layout, and configuration entry points |
| [docs/extensions_overview.md](docs/en/extensions_overview.md) | Extension modules and macro reference |
| [docs/porting_general.md](docs/en/porting_general.md) | General porting checklist (linker script, tick, I/O) |
| [docs/porting_esp_idf.md](docs/en/porting_esp_idf.md) | Dedicated ESP-IDF porting guide |
| [docs/ai_bridge.md](docs/en/ai_bridge.md) | AI HTTP bridge: switches, callbacks, the `ai` command, gateway, and security |

## Three Distinguishing Features

### 1. Hierarchical subcommand tree

Other embedded shells only support flat commands. Zoom Shell supports unlimited hierarchical nesting of subcommands, and Tab completion walks the tree:

```c
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
```

### 2. Variable observation system

Inspect and modify any registered variable in real time over the serial port, similar to Simulink External Mode:

```c
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
```

### 3. Command execution timing

Inspired by USMART, automatically measures command execution time:

```
zoom> perf on
zoom> some_command
[OK] some_command (elapsed: 12 ms)
```

## Feature Comparison

| 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** |

## AI HTTP Bridge (optional extension)

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 ` sets the POST endpoint; `ai ask ` sends the whole text as the request body; `ai status` shows 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()`, call **`zoom_ai_bridge_set_post(shell, your_http_post)`**, and inside the callback use ESP-IDF `esp_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](docs/en/ai_bridge.md). See "Interoperating with the OpenClaw / Claw ecosystem" below for how this relates to the OpenClaw / Claw ecosystem.

## Quick Start

### Build and run the x86 demo

```bash
cd zoom-shell
make demo
./build/zoom_shell_demo
```

### Port to your MCU

**Step 1: create a configuration file** `zoom_shell_cfg_user.h`

```c
#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**

```c
#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**

```c
/* 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**

```c
static int cmd_led(zoom_shell_t *sh, int argc, char *argv[]) {
if (argc < 1) { zoom_error(sh, "Usage: led \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:

```ld
.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 = .;
} > FLASH
```

## Directory Layout

```
zoom-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
```

## Configuration System

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 |

## API Cheat Sheet

```c
/* 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);
```

## Design Principles

- **Zero malloc** — all memory is statically allocated or supplied by the user
- **Zero OS dependency** — depends only on `` `` `` `` ``
- **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

## Interoperating with the OpenClaw / Claw Ecosystem

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.

## CI / Releases (GitHub Actions)

Pushes to `main` / `master` and pull requests automatically run `make test` and `make demo`.

**Cut a new release** (run at the repository root):

```bash
git tag -a v1.2.0 -m "Zoom Shell 1.2.0"
git push origin v1.2.0
```

Pushing a tag that matches `v*.*.*` triggers the tests again, uses `git archive` to produce `zoom-shell-.tar.gz` / `.zip`, attaches `zoom-shell--demo-linux-x86_64` (the demo built on Ubuntu) and `SHA256SUMS`, and automatically creates and uploads the GitHub Release.

## License

MIT