A Unix-like shell for microcontrollers, with users, networking and SSH,
that also runs on a PC. TinyDesk Shell (tdsh) is the
shell inside TinyDesk, the
terminal desktop for microcontrollers, and works on its own too.
Actual serial-console capture of the standalone firmware.
- Parser with variables, quoting, redirection, pipelines, command
substitution, arithmetic and conditions; scripts (
.tdsh, uScript 1.1.1). - Line editor: Tab completion, history, arrows, Home/End, Ctrl+A/E/U/K/L/C; long lines wrap over several rows and stay editable.
- Files on LittleFS (
/fs),nano,write; per-user homes with a sandbox. - Users in NVS with salted, hashed passwords;
login,passwd, boot user, physical-console recovery. - Wi-Fi (per-user saved networks), W6100 Ethernet, LAN/Wi-Fi policy,
ping, SNTP time, time zones. - SSH/SFTP server (wolfSSH, per-device host key), FTP server, SMB2/3 mounts,
the SD card at
/sd(sd mount, FAT over SPI, optional mount at boot). - Board configuration: pins for RS-485, Ethernet and SD come from a
key = valuefile (boardcommand,tdsh_board.h), not from the code. - Hardware loopback tests (
hwtest), heap and task information. - A portable core with POSIX (Linux) and Windows host ports and regression
tests;
tdsh.exeruns the shell natively on Windows.
The C API uses the prefix tdsh_ (tdsh.h, tdsh_espidf.h); scripts end in .tdsh.
This is a developer preview.
- From the browser: the TinyDesk web installer flashes TinyDesk Shell onto an ESP32-C6 or ESP32 (Chrome or Edge), and the web terminal opens the board afterwards.
- Files: every release has a factory image
per board (flash at offset 0 with esptool), the Linux program, the Windows
program (
tdsh.exe, run it in Windows Terminal) andSHA256SUMS.txt. On a PC the shell keeps its files apart from yours: in~/.local/share/tdsh/rootfson Linux,%LOCALAPPDATA%\tdsh\rootfson Windows.
A release is made by pushing a tag v<VERSION>: .github/workflows/release.yml
runs the host tests, builds both firmware projects and the Linux and Windows
programs, and
creates a draft pre-release with the files (tools/make_release.py packages
them; it runs locally too).
git clone https://github.com/tinydesk-project/tinydesk-shell.git
cd tinydesk-shell| Board | Project | Console |
|---|---|---|
| ESP32-C6, 8 MB flash | the repository root | built-in USB Serial/JTAG |
| classic ESP32, 4 MB flash or more, PSRAM optional | projects/esp32 |
UART0 (the USB-UART chip), 115200 baud |
idf.py build # in the root, or in projects/esp32
idf.py -p PORT flash monitorOn Windows, build_windows.cmd runs an environment check first
(build_windows.cmd -p COM7 flash monitor). Keep the checkout in a short
folder such as C:\src\tinydesk-shell: ESP-IDF's nested build folders
otherwise run past Windows' 260-character path limit (ninja: error: mkdir(...): No such file or directory). The ESP-IDF component manager
downloads the dependencies declared in
ports/esp_idf/components/tdsh/idf_component.yml.
You start as root; the factory root password (for SSH, FTP and login) is
TinyDesk: change it locally with passwd before remote access. SSH and FTP
refuse remote authentication while the factory root password remains. Then, for example:
wifiadd MyNetwork mypassword # save a network (per user)
wificonnect
ssh start
hello # the example application command (main/main.c)
RS-485 lines, a W6100 Ethernet chip and an SD card are configured with
key = value settings, not in the code. Copy the project's
board.example.conf to board.conf (ignored by git) and edit it before
building, or set them on the running board as root:
board set eth.chip w6100
board set eth.miso 2
...
reboot
board show lists the settings. See the annotated
board.example.conf for keys and defaults.
The partition tables: ESP32-C6: application at
0x10000(3 MB), LittleFSstorageat0x310000; classic ESP32: application at0x10000(2.5 MB), LittleFS at0x290000(1.4 MB). The file system is formatted if it cannot be mounted. Do not useerase-flashas a routine step.
Add ports/esp_idf/components to EXTRA_COMPONENT_DIRS, then:
#include "tdsh_espidf.h"
extern const char board_conf[] asm("_binary_board_conf_start"); /* optional, EMBED_TXTFILES */
tdsh_espidf_config_t cfg = TDSH_ESP_IDF_CONFIG_DEFAULT();
cfg.hostname = "mydevice";
cfg.board_config = board_conf; /* built-in pins; /fs/etc/board.conf overrides them */
ESP_ERROR_CHECK(tdsh_espidf_init(&cfg));
ESP_ERROR_CHECK(tdsh_espidf_start()); /* the ESP-IDF console: USB Serial/JTAG or UART */Register your own commands with tdsh_register_commands(). See
docs/EMBEDDING.md and examples/esp_idf/embed_in_app.
TinyDesk embeds the shell this way and runs its console in a desktop window.
An integration that shares this console with a remote transport must call
tdsh_console_mark_remote() before accepting remote input and refuse the
connection if it returns false. This prevents remote use of physical password
recovery; successful takeover revokes physical trust until reboot.
cmake -S . -B build-host -G Ninja -DTDSH_BUILD_HOST=ON
cmake --build build-host
ctest --test-dir build-host --output-on-failureOn Windows add -DCMAKE_C_COMPILER=gcc (MinGW-w64); the program is
build-host/tdsh_host.exe. docs/POSIX_HOST.md and docs/WINDOWS_HOST.md
explain the host ports.
TDSH_MAX_VARS, TDSH_VAR_NAME_MAX, TDSH_VAR_VALUE_MAX and
TDSH_SCRIPT_TASK_STACK in tdsh.h can be set by the build. Their defaults
are 64 variables, 32-byte name buffers, 256-byte value buffers and 32768
bytes of stack requested per script worker. The buffers include the final
NUL. TDSH_VAR_NAME_MAX also limits the length of function names in
scripts. Each script keeps its own copy of the session and its variables.
Apply the same overrides to every component that includes tdsh.h,
including the shell core, the platform port and consumers such as TinyDesk.
The variable limits change the layout of tdsh_session_t; mixing defaults
and overrides between components is incompatible. Set the definitions
from the build system rather than editing the header. For example, a host
build with smaller variable tables can use:
cmake -S . -B build-small -DTDSH_BUILD_HOST=ON \
-DCMAKE_C_FLAGS="-DTDSH_MAX_VARS=48 -DTDSH_VAR_VALUE_MAX=128"
cmake --build build-small
ctest --test-dir build-small --output-on-failureFor consumers with C++ sources, pass the same definitions to their C++ compilation too. Choose the worker stack size for the target and verify its margin while running the intended scripts.
On a port that lists memory regions (docs/PORTING.md), root can read and write them:
peek -l the regions: first and last address, widths, access
peek [-w 8|16|32] <address> one value, printed alone: 0x00001234
peek [-w 8|16|32] <address> <count> count values, 16 bytes per line
poke [-w 8|16|32] <address> <value> write one value
Accesses are 32-bit unless -w says otherwise. Addresses, counts and
values are decimal or 0x hexadecimal. A misaligned address, an access
outside the regions or across a region's end, a width the region does not
allow, a poke to a read-only region and a value too wide for the width
are refused with exit status 1. One value prints alone, so a script can
use it: V=$(peek 0x2000c000).
# peek -l
REGION START END WIDTHS ACCESS
sram 0x62fc0000 0x62ffffff 8 16 32 read/write
gpio 0x2000c000 0x2000c0ff 32 read/write
# peek -w 8 0x62fc0000 20
0x62fc0000: 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f
0x62fc0010: 10 11 12 13
# poke 0x2000c004 0x1
# peek 0x2000c004
0x00000001
The host and ESP32 builds list no regions, so they have neither command.
Put commands in a .tdsh file and run it with tdsh run. The script
language (uScript 1.1.1) has variables, single and double quotes, $(…),
integer arithmetic $((…)), if/elseif/else/endif,
while/endwhile, for … in/endfor (with * and ? globs),
break/continue, functions with arguments and return status
(function/end, $1, $#, $?), test and [ … ], pipes,
>, >> and < redirection, and &&, || and ;.
#!/bin/tdsh
function check
if test -f "$1"
echo "$1: $(cat $1)"
return 0
endif
echo "$1 is missing"
return 1
end
for F in ~/logs/*.txt
check $F || echo "problem with $F"
endfor
N=$((3 * 4 + 1))
if $N >= 10
echo "N is $N"
endiftdsh run ~/check.tdsh # run and wait; $? is its status
tdsh run ~/tools # a folder runs its main.tdsh
tdsh run ~/logger.tdsh --bg # in the background
A script runs in a copy of the session, so its variables and cd do not
leak back. On the ESP32 each user's ~/.tdshrc.tdsh runs when their local
console starts (for example wificonnect). The full reference, with every
condition form, the limits and a tested example: docs/SCRIPTING.md.
tests/scripts/ holds self-checking test scripts.
Maintained by their authors, not built or tested here. Each port has its own prefix for its names, commands and board keys (STANDARDS.md):
| Port | Platform | Prefix | Notes |
|---|---|---|---|
| TinyTang by @aquasock | BL616 (FreeRTOS), Sipeed Tang Console 138K | not chosen yet | Shell over USB CDC with the SD card as filesystem; adds commands to load FPGA cores and ROMs, mirror the session to the core's on-screen display, and self-update the firmware |
| File | Content |
|---|---|
docs/SCRIPTING.md |
the .tdsh script language: syntax, conditions, loops, functions, pipes, limits |
docs/FUNCTIONS.md |
API and command inventory |
docs/ARCHITECTURE.md, docs/PORTING.md |
structure, porting to another platform |
docs/DEPENDENCIES.md |
component versions |
CHANGELOG.md |
changes per version |
TinyDesk Shell is released under the MIT licence.
Third-party components keep their own licences: see THIRD_PARTY_LICENSES.md and NOTICE. Firmware built with the ESP-IDF component includes wolfSSH and wolfSSL (GPL-3.0) and is therefore distributed under the GPL-3.0 as a whole.
Issues and pull requests are welcome; see CONTRIBUTING.md. Run the host tests and build the standalone firmware before sending a change, and keep board-specific pins out of the code (use board configuration keys).
Set up formatting once:
pip install pre-commit
pre-commit installAfter this, every commit formats the C files you changed with the right
clang-format version automatically. To format manually instead:
pip install clang-format==16.0.6, then clang-format -i <files>. If the
format check fails on your pull request, don't worry: I can fix it before
merging.
