Examples
Annotated examples showing xelp on different platforms and use cases. Full
source code is in the
examples/
directory.
| Example | Platform | What it demonstrates |
|---|---|---|
| Bare metal | Any MCU | Minimal porting template with all three modes |
| Multi-instance | Any MCU | Two independent CLIs on separate UARTs |
| Posix simple | Linux / macOS | Full interactive demo with ncurses |
| Arduino | Any Arduino board | LED control, token listing via CLI |
| Arduino C++ | Any Arduino board | C++ wrapper class for easy integration |
| ESP32 WiFi | ESP32 | WiFi config, time and weather fetch via CLI |
| Scripting | Linux / macOS | Batch scripting vs interactive mode |
| Pico CLI (C) | Raspberry Pi Pico | GPIO, ADC, PWM control over USB serial |
| Pico CLI (Arduino Easy API) | Raspberry Pi Pico | Tier 3 Easy API with lambdas |
Bare metal
File:
examples/bare-metal/bare-metal-example.c
The starting point for any new port. Shows the complete xelp integration pattern with no dependencies beyond a UART.
#include "xelp.h"
/* 1. Platform stubs -- replace with your hardware */
static void uart_putc(char c) { /* UART TX */ }
static void uart_bksp(void) { uart_putc('\b'); uart_putc(' '); uart_putc('\b'); }
static int uart_rx_ready(void) { return 0; }
static char uart_getc(void) { return 0; }
/* 2. KEY mode commands */
XELPKeyFuncMapEntry key_commands[] = {
{ &key_help, '?', "show help" },
{ &key_banner, 'b', "print banner" },
XELP_FUNC_ENTRY_LAST
};
/* 3. CLI mode commands */
XELPCLIFuncMapEntry cli_commands[] = {
{ &cmd_help, "help", "show help" },
{ &cmd_echo, "echo", "echo args back" },
{ &cmd_info, "info", "show system info" },
XELP_FUNC_ENTRY_LAST
};
/* 4. Wire everything up */
XELP cli;
void main(void) {
XelpInit(&cli, "My Device v1.0\n");
XELP_SET_FN_OUT(cli, &uart_putc);
XELP_SET_FN_BKSP(cli, &uart_bksp);
XELP_SET_FN_KEY(cli, key_commands);
XELP_SET_FN_CLI(cli, cli_commands);
XelpHelp(&cli);
XelpParseKey(&cli, '\n'); /* show initial prompt */
for (;;) {
if (uart_rx_ready())
XelpParseKey(&cli, uart_getc());
}
}
Key points
- 5 function pointers are the entire platform abstraction: output, error, backspace, pass-through, mode change. Only output is required.
- Command tables are NULL-terminated arrays. Use
XELP_FUNC_ENTRY_LASTas the sentinel. - The main loop just feeds characters. xelp handles line buffering, backspace, mode switching, and dispatch internally.
Multi-instance
File:
examples/multi-instance/multi-instance-example.c
Two completely independent CLIs on separate serial ports. Each has its own output function, command table, prompt, and internal state.
XELP debug_cli, field_cli;
XelpInit(&debug_cli, "Debug Console (UART0)");
XelpInit(&field_cli, "Service Port (UART1)");
XELP_SET_FN_OUT(debug_cli, &uart0_putc);
XELP_SET_FN_OUT(field_cli, &uart1_putc);
XELP_SET_FN_CLI(debug_cli, debug_commands);
XELP_SET_FN_CLI(field_cli, field_commands);
XELP_SET_VAL_CLI_PROMPT(debug_cli, "dbg>");
XELP_SET_VAL_CLI_PROMPT(field_cli, "svc>");
/* Poll both UARTs */
for (;;) {
if (uart0_rx_ready()) XelpParseKey(&debug_cli, uart0_getc());
if (uart1_rx_ready()) XelpParseKey(&field_cli, uart1_getc());
}
Use cases
- Debug console on one UART, production interface on another
- Multiple BLE characteristics each with their own CLI
- USB CDC + UART running independent command sets
Posix simple
File:
examples/posix-simple/xelp-example.c
Full interactive demo for Linux and macOS using ncurses for terminal handling.
cd examples/posix-simple
make
Requires ncurses: sudo apt-get install libncurses5-dev (Debian/Ubuntu).
Features demonstrated
- CLI commands:
echo,banner,help,numtoks,lt(list tokens), math (+,-,*,/),exit - KEY commands:
h(help),f(fooBar),p/w(print),b(banner),x(exit) - Mode switching: ESC → KEY, CTRL-P → CLI, CTRL-T → THR
- Backspace handling, mode change callbacks, token parsing, numeric arguments
Architecture
stdin (ncurses getch)
|
v
XelpParseKey(&example, char)
|
+--> CLI: line buffer -> tokenize -> dispatch
+--> KEY: immediate dispatch
+--> THR: pass-through function
|
v
gPutChar() -> ncurses addch() -> terminal
Arduino
File:
examples/arduino/arduino.ino
Basic example using the raw C API. Works on any Arduino board with a Serial port — LED control, token listing, and built-in help. No external library dependencies.
void writeChar(char c) { Serial.write(c); }
void setup() {
Serial.begin(115200);
XelpInit(&cli, "xelp Arduino example v1.0\n");
XELP_SET_FN_OUT(cli, &writeChar);
XELP_SET_FN_CLI(cli, gMyCLICommands);
}
void loop() {
if (Serial.available() > 0) {
char c = Serial.read();
XelpParseKey(&cli, c);
}
}
Commands
help— list all commandsbanner— print xelp ASCII artled <0|1>— toggle LEDlt <args>— list parsed tokens
Arduino C++
File:
examples/arduino-cpp/arduino-cpp.ino
Same idea as the arduino example but uses the XelpCLI
C++ wrapper class from src/XelpArduino.h. Eliminates boilerplate:
no manual XELP_SET_FN_* macros, no Serial.available()
loop — just begin(), setCommands(), and
poll(Serial).
ESP32 WiFi
File:
examples/esp32-wifi/esp32-wifi.ino
ESP32 WiFi example using the C++ wrapper. Configure WiFi credentials over the serial CLI, then fetch the current time and weather from free APIs (worldtimeapi.org and open-meteo.com). No API keys needed.
Commands
ssid <name>/pass <password>— set WiFi credentialsconnect/disconnect— manage WiFi connectionstatus— show WiFi status, IP, RSSItime— fetch current timeweather <lat> <lon>— fetch weather for coordinates
Scripting
File:
examples/scripting/scripting-example.c
Demonstrates the difference between scripting mode
(XelpParse / XelpParseXB — execute a buffer of
commands at once) and interactive mode (XelpParseKey
— character-by-character terminal input).
cd examples/scripting
make
Pico CLI (C)
Files:
examples/pico-cli/
Pure C example for Raspberry Pi Pico, Pico W, Pico 2, and Pico 2 W using the Pico SDK. Controls GPIO, ADC, and PWM over USB CDC serial. Automatically detects Pico W boards and uses the CYW43 driver for the wireless-chip LED.
mkdir build && cd build
cmake -DPICO_BOARD=pico .. # or pico_w, pico2, pico2_w
make
Commands
help— list all commandsled <0|1>— set on-board LEDpin <n> <in|out>— configure GPIO directionset <pin> <0|1>/get <pin>— write/read GPIOadc <0-3>— read ADC channelpwm <pin> <0-255>— set PWM duty
Key snippet
XELP cli;
int main(void) {
stdio_init_all();
gpio_init(PICO_DEFAULT_LED_PIN);
gpio_set_dir(PICO_DEFAULT_LED_PIN, GPIO_OUT);
XelpInit(&cli, "Pico CLI (xelp)\n");
XELP_SET_FN_OUT(cli, &uart_putc_fn);
XELP_SET_FN_CLI(cli, cli_commands);
for (;;) {
int c = getchar_timeout_us(0);
if (c >= 0) XelpParseKey(&cli, (char)c);
}
}
Pico CLI (Arduino Easy API)
Files:
examples/pico-cli-arduino/
Showcases the C++ Easy API on Raspberry Pi Pico using
the Arduino-Pico
core. Commands are registered with commands({...})
— no static tables, no raw XELP* pointers. Lambda callbacks
receive a XelpCLI& reference, argc, and
argv — similar to a standard main().
Key snippet
XelpCLI cli;
void setup() {
Serial.begin(115200);
cli.begin("Pico CLI Demo\n", nullptr);
cli.output(Serial);
cli.prompt("pico>");
cli.commands({
{"help", "show help", [](XelpCLI& c, int, const char**) {
c.help();
}},
{"led", "led <0|1>", [](XelpCLI& c, int argc, const char** argv) {
if (argc < 2) { c.print("usage: led <0|1>\n"); return; }
int v = argv[1][0] - '0';
digitalWrite(LED_BUILTIN, v ? HIGH : LOW);
c.print(v ? "LED ON\n" : "LED OFF\n");
}},
});
cli.keyCommands({
{'l', "toggle LED", [](XelpCLI& c, XELPKEYCODE) {
static bool on = false;
on = !on;
digitalWrite(LED_BUILTIN, on ? HIGH : LOW);
}},
});
}
void loop() { cli.poll(Serial); }
Two ways to register commands
- C way:
setCommands()with a staticXELPCLIFuncMapEntry[]array (see Arduino C++ example). - C++ way:
commands({...})with inline lambdas (this example).argv[0]is the command name,argv[1..argc-1]are the arguments.
Writing your own commands
All CLI commands have the same signature:
XELPRESULT my_command(XELP *ths, const char *args, int maxlen);
ths— pointer to the XELP instance that dispatched the commandargs— raw argument string (everything after the command name)maxlen— number of valid bytes inargs- Return
XELP_S_OK(0) for success, negative for error
Pattern: parsing arguments
XELPRESULT cmd_set(XELP *ths, const char *args, int len) {
XelpBuf b, tok;
int n;
XELP_XB_INIT(b, args, len);
XelpNumToks(&b, &n);
if (n < 3) {
XelpOut(ths, "usage: set <key> <value>\n", 0);
return XELP_E_ERR;
}
/* Token 0 = "set", Token 1 = key, Token 2 = value */
XELP_XB_TOP(b);
XelpTokN(&b, 1, &tok);
/* tok.s .. tok.p is the key string */
XELP_XB_TOP(b);
XelpTokN(&b, 2, &tok);
int value = XelpStr2Int(tok.s, tok.p - tok.s);
return XELP_S_OK;
}
Pattern: output without printf
xelp has no printf. Use XelpOut for strings. For numbers,
use your platform’s sprintf into a local buffer, or write
a simple helper:
void print_int(XELP *x, int val) {
char buf[12];
int i = 0;
if (val < 0) { XelpOut(x, "-", 1); val = -val; }
if (val == 0) { XelpOut(x, "0", 1); return; }
while (val > 0) { buf[i++] = '0' + (val % 10); val /= 10; }
while (i > 0) { XelpOut(x, &buf[--i], 1); }
}
Next steps
- Tutorial — step-by-step guide
- API Reference — complete function docs
- Configuration — compile-time options
- Porting Guide — platform-specific notes