Skip to content

easyzoom/zoom-shell

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

26 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

中文版 / Chinese version: 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/ 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

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:

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:

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 <http(s)://...> sets the POST endpoint; ai ask <text...> 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. 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

cd zoom-shell
make demo
./build/zoom_shell_demo

Port to your MCU

Step 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 = .;
} > 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

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

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):

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

License

MIT

About

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

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors