SolarOS

ESP32 operating environment · 4.10.12

Small system.
Real tools.

SolarOS lets makers build embedded devices without starting from bare metal. It combines a shell, scripting, applications, networking, and hardware access into a single lightweight operating environment.

solar@pocket:/ apps
agent   aplay   arecord
recorder   funcgen   player
calc   chat   clock
com   curl   webradio
help   edit   hexedit
files   ftp   flash
contacts   inbox   email
io   invaders   gameboy
less   logic   lua
notes   plot   playground
python   reader   writer
scp   sheet   ssh
synth   telnet   view
sketch   web

solar@pocket:/ _

Everything you need. Nothing you don't.

Inside the system

A compact, capable runtime.

The same shell, apps, and jobs work across handheld and headless boards.

01

Local shell

History, aliases, scripts, completion, storage, and resumable sessions.

02

Foreground apps

Edit, read, inspect data, connect remotely, browse, and capture logic.

03

Background jobs

Logging, acquisition, NTP, HTTP, SLIP, bridges, and battery monitoring.

04

Python & Lua

Automate services and hardware with embedded scripting APIs.

05

Connected hardware

Wi-Fi, BLE, SD, RTC, GPIO, ADC, PWM, I²C, SPI, audio, and sensors.

06

Firmware flavors

Build focused or full images from capability-aware packages.

What's new

Recently added.

01

MeshCore virtual serial streams

Create trusted peer-bound byte-stream ports between SolarOS devices over encrypted MeshCore direct packets, then use them for shell sessions, serial connections, or bridges across standard MeshCore repeaters.

Explore MeshCore streams →
02

FTP file transfer and server

Copy and manage local or remote files with the resumable two-pane ftp app, export a bounded folder with the ftpd job, or automate FTP transfers from Python and Lua on trusted networks.

Explore FTP →
03

Smoother Game Boy emulation

Run original Game Boy games at native emulation timing with bounded 25–30 Hz presentation, direct four-color output on TFTs, ordered dithering on one-bit displays, and synchronized audio.

Explore Game Boy →
04

Better Web browsing

Browse with a visible Back, Reload, and Forward toolbar, restored reading positions, pointer or touch activation, five reflowing zoom levels, shared text search, and color images on supported TFTs.

Explore Web →
05

Adaptive color graphics

Use indexed-color canvases on color displays, preserve image colors in Sketch, View, and Web, and draw explicit RGB colors from Python or Lua while one-bit displays retain their dithered path.

Explore color graphics →
06

Sketch paint application

Draw with pen, line, shape, fill, and eraser tools in a pointer-driven Paint-style interface, then save indexed-color PNG files or import PNG, JPEG, and GIF images.

Explore Sketch →
07

Board hardware as expansion drivers

Discover built-in displays, audio, storage, touch, battery, RTC, and sensors as fixed expansion devices, and use the same target-aware drivers with compatible external hardware at runtime.

Explore exposed drivers →
08

Board profile configurator

Create a custom ESP32 or ESP32-S3 target with an MCU-first desktop TUI that selects compatible drivers, buses, pins, and fixed devices, then writes one inherited TOML board profile.

Build a board profile →

SolarOS documentation

Reliable virtual serial ports

A MeshCore stream is a normal SolarOS byte-stream port carried in encrypted MeshCore direct-request packets. Standard MeshCore repeaters can route those opaque packets between two SolarOS endpoints; the repeaters do not need SolarOS support. Create the stream explicitly on both devices and bind each side to the other device's trusted MeshCore endpoint.

First start MeshCore on both devices. Use flood adverts when the endpoints are not in direct radio range, then review and trust the discovered endpoint on each side:

job start meshcore radio0 meshcore-eu868
meshcore advert flood
contacts list
contacts show <contact-id>
contacts trust <contact-id> <endpoint-id>
meshcore stream create mser0 <endpoint-id>
meshcore stream status mser0

Endpoint IDs are local database IDs and can differ between the two devices. The endpoint must be a trusted MeshCore endpoint with the exact peer public key. A discovered, blocked, group, or changed identity is rejected. Configure only one stream for a peer identity.

For example, expose a shell on the internet-connected ground station:

session create shell mser0 --term dumb

On the pocket terminal, open it as a serial connection:

com mser0

To expose another machine instead, connect its serial interface to the ground station and bridge the UART port:

job start bridge uart0 mser0

The stream is a slow, stop-and-wait serial link, not an IP or Telnet tunnel. It carries up to 82 data bytes per MeshCore packet, uses 2048-byte transmit and receive queues, retries after 12 to 16 seconds, and treats a peer as absent after 180 seconds. Interactive command shells are practical; full-screen TUIs, bulk transfers, and chatty terminal negotiation are poor fits. Use the dumb terminal mode where possible.

At most two Link-backed streams can exist at once, including MeshCore streams. Stream definitions remain in RAM if the MeshCore job stops and reconnect after it starts again, but they do not persist across a reboot. Remove an unclaimed port with meshcore stream remove <port>.

SolarOS documentation

ftp

Two-pane FTP file manager. The left pane is local mounted storage. The right pane is a remote FTP server. The app uses unencrypted IPv4 FTP and passive data connections.

Usage:

ftp HOST [PORT] [--user USER --password PASSWORD] [--remote PATH] [--local PATH]

Anonymous login is the default (anonymous with solaros@ as its password). Specify both --user and --password for an authenticated server. Passwords are plaintext on the network and remain in shell history when entered on the command line. Use FTP only on a trusted network.

Controls:

  • Arrows navigate the active pane. Tab, Left, and Right switch panes.
  • Enter opens a directory. r refreshes the remote and local listings.
  • F3 views a local file. For a remote file, FTP downloads a temporary hidden
  • copy into the current local folder, opens its registered viewer, and removes the copy when control returns to FTP.

  • F5 copies the current file or directory to the other pane. Directory
  • transfers are recursive. Copy and move show the same preparing/transfer progress popup used by Files.

  • F6 moves the current file or directory to the other pane by completing the
  • copy before deleting the source.

  • F7 creates a directory in the active pane.
  • F8 recursively deletes the current item after confirmation.
  • q, F10, or the app-exit key closes the connection and exits.
  • After an operation refreshes the current directories, both panes retain their
  • cursor and scroll positions. Opening another directory starts at its top.

FTP is a resumable TUI on the display shell and VT100-compatible port shells. One operation runs at a time in its foreground worker. Existing destination files are replaced. A failed download removes its staging file and preserves an existing destination; a move does not delete its source unless the complete copy succeeds.

SolarOS documentation

gameboy

Original Game Boy (DMG) emulator selected by the gameboy group on boards with PSRAM, SD storage, graphics, and a streaming display. Current integrated targets are Waveshare RLCD, Freenove IPS, ODROID-GO, Freenove PAL, and TTGO VGA32. The application loads a user-supplied ROM into PSRAM and writes battery-backed cartridge RAM beside it as a .sav file. Game Boy Color-only ROMs and ROMs larger than 4 MiB are rejected.

The emulator runs Peanut-GB at its fixed native frame frequency in a dedicated worker. Peanut-GB renders alternate LCD frames, producing a compact 160x144 INDEX2 raster at about 30 frames per second while still emulating every frame. A shared frame presenter owns display cadence, retains the newest submitted frame, and replaces stale pending frames instead of blocking emulation. TFT targets scale the four-color raster directly to RGB565. One-bit targets convert the latest frame through ordered dithering, then use their scan-synchronized mono path. The target selects a bounded presentation rate of 25 or 30 frames per second and reduces output scale when the driver's pixel-rate budget cannot sustain the larger raster. Runtime logs report emulation, presentation, replacement, timing rebase, audio block, peak level, and transfer timing.

Audio rendering runs in its own bounded worker, applies a Game Boy-local bounded gain for low-resolution DAC outputs, and holds exclusive speaker output while Game Boy is active. The Waveshare presenter temporarily requests the panel's 25.5 Hz HPM profile. Pausing, suspending, or exiting stops the workers and restores the previous display and audio policies. PAL builds run without audio because composite scanout owns I2S0; both the 384x288 raster and the centered 320x200 safe-area mode can fit the scaled Game Boy frame.

Usage:

gameboy <file.gb>

Controls:

  • Arrows control the D-pad.
  • The physical US-Z key position is A; on a German QWERTZ keyboard this key is
  • labeled Y. The physical X key is B.

  • Enter is Start; Backspace or Delete is Select.
  • On ODROID-GO, A and B are the Game Boy A and B buttons, Menu is Start,
  • Select is Select, and Menu+Select exits.

  • p pauses and r resets.
  • q, Esc, or the app-exit key exits.

Game Boy reads the generic SolarOS held-key state. BLE/PS/2 keyboards, fixed board buttons, gpio-keys, and ADC D-pads therefore remain active until release, and combinations such as diagonal movement or direction plus A/B work simultaneously. USB HID usages preserve the physical Z/X positions across keyboard layouts. Character-only inputs such as port-shell input retain the short button-pulse fallback. Analog joystick axes do not become Game Boy keys.

SolarOS documentation

web

Simple graphical web browser for lightweight HTML pages. It shares document and image rendering infrastructure with reader where possible. Embedded and direct PNG, JPEG, GIF, and WebP images retain color on indexed-color displays; one-bit displays keep the grayscale decode and dither path.

Usage:

web http://host/
web https://host/path

Controls:

  • The top toolbar provides Back, Reload, and Forward. Pointer or touch presses
  • activate the toolbar and open links or form controls directly.

  • Left or b goes back, Right or f goes forward, and r reloads while
  • retaining the current reading position.

  • Up/Down or k/j scroll one rendered line. Page Up/Page Down, Home,
  • and End provide larger movement.

  • n or Tab selects the next link or form control; p selects the previous
  • one. Selection stops at each end, and Enter activates it.

  • Links and plain page text use the same font size. Links remain underlined,
  • headings remain bold, and +/- or Ctrl++/Ctrl+- reflow page text through the same five zoom levels as Reader.

  • Ctrl+F opens Find and F3 jumps to the next case-insensitive match in
  • rendered page text, wrapping at the end.

  • Esc or app-exit key exits.

SolarOS documentation

Colors and dimensions

Use gfx.WHITE, gfx.LIGHT, gfx.DARK, gfx.BLACK, gfx.gray(level), or gfx.rgb(red, green, blue). RGB components are 0..255. On color TFTs, the named colors and gray(level) span the setterm foreground and background theme, while rgb(...) stays literal in the lazily allocated indexed canvas. One-bit targets keep the existing luminance and dither path. Do not use color-name strings or guessed integer values. Read dimensions with width(), height(), or size() rather than assuming a panel size.

SolarOS documentation

sketch

Pointer-driven graphical paint application. Its layout follows classic desktop paint programs: Save, Open, Import, and the sidebar controls share one aligned, equal-sized button grid; color and pattern choices are in the bottom bar. Sketch uses a compact four-color canvas and stores finished documents as interoperable indexed-color PNG files. Color TFTs show the native palette; one-bit displays use the existing dithered rendering path.

Usage:

sketch
sketch file.png

Sketch is intentionally not gated on a pointer hardware capability. If no ready absolute or relative pointer is registered when the app starts, a popup explains that a pointer can be attached at any time. Press Enter or Esc to dismiss it; the first attached pointer is accepted without restarting the app.

Controls:

  • Click Save to update the current PNG. For an untitled or imported image, the
  • first save selects the next free Sketches/sketchNNN.png path on persistent storage. Saves use a synced staging file, backup rename, atomic replacement, and rollback.

  • Click Open to select a PNG as the current document. Click Import to flatten a
  • PNG, JPEG, or GIF into the current four-color canvas; the next save creates a new PNG instead of overwriting the imported source.

  • The sidebar selects pen, straight-line, rectangle, ellipse, bucket fill,
  • or eraser drawing. The weight control cycles through 1, 2, 4, and 8 pixels. Eraser uses the selected weight and restores white pixels. Clear immediately resets the complete canvas to white.

  • Line, rectangle, and ellipse show an exact non-destructive preview during the
  • drag, including the selected color, pattern, and stroke weight. Releasing the pointer commits that preview to the canvas.

  • The first four bottom swatches select white, red, blue, or black. The next
  • four select solid, checker, dot, or diagonal-hatch application.

  • Keyboard fallbacks are S Save, O Open, I Import, P Pen, L Line,
  • R Rectangle, E Ellipse, B bucket fill, X eraser, and C clear. Arrows and Enter operate the file browser.

  • Q, Esc, or the app-exit key exits. Suspending and resuming retains the
  • current cold-allocated canvas; closing the app releases it and all browser or image resources.

SolarOS documentation

Expansion drivers and attached devices

Expansion drivers turn named buses and safe GPIO slots into active displays, radios, sensors, or manual resource profiles. Drivers are package-gated, so the available list depends on the firmware and board.

Integrated hardware uses the same composition model. A board profile declares its fixed buses and default attachments, which are created at boot, shown by expansion devices, and cannot be detached. For example, Freenove touch0 is an ft6336 attachment; Waveshare rtc0 and environment0 use pcf85063 and shtc3; and the supported battery boards expose battery0 through battery-adc. TTGO VGA32 keyboard0 is a ps2-keyboard attachment. Built-in audio also appears as audio0: Waveshare uses es8311-es7210, Freenove uses es8311-duplex, and classic ESP32 audio boards use esp32-dac. Generic input, time, sensor, battery, and audio services consume the same runtime providers whether the attachment came from the board profile or the shell.

Built-in displays follow the same rule and appear as fixed display0 attachments: Waveshare uses st7305, Freenove uses st7796, ODROID-GO uses ili9341, Elecrow CrowPanel uses ssd1683, ESP32-WROVER v3.0 uses cvbs-pal, and TTGO VGA32 uses vga32. They attach before the splash and primary display service start.

Built-in SDMMC slots also appear as fixed storage0 attachments. Waveshare and ESP32-WROVER v3.0 use one-bit bindings; Freenove uses four-bit bindings. The attachment claims and configures the pins early, while the normal storage phase still probes and mounts the card.

Named MIDI connections are created as buses rather than attached drivers. Use expansion bus create midi <name> tx=<gpio> rx=<gpio>; SolarOS chooses the UART backend and the midi background job owns the connection while it runs.

Discover what is present

expansion drivers
expansion devices
display list

expansion drivers uses compact aligned columns for the compiled driver name, probe support, bus type, and summary. expansion devices prints each attached device in a separate block, with its name in bold followed by origin, readiness, startup mode, attachment policy, and bindings.

From a script, inspect solaros.expansion.drivers() and solaros.expansion.devices(). A driver existing in firmware does not mean a physical device is attached.

Attach deliberately

Use real bus and pin names returned by discovery:

expansion attach pcd8544 lcd0 spi=spi0 cs=gpio10 dc=gpio4 reset=gpio5
display test lcd0
expansion detach lcd0

Detaching releases the resources. Do not invent a target name or copy bindings from a different board.

A Waveshare 4.2-inch V2 monochrome e-paper module uses the SSD1683 expansion driver and registers a 400x300 display target:

expansion attach ssd1683 epd0 spi=spi0 cs=gpio10 dc=gpio17 reset=gpio16 busy=gpio15
display test epd0
display mode epd0 refresh=fast
expansion detach epd0

The runtime defaults are the Waveshare V2 panel profile, 2 MHz SPI, and rotation 0. Optional power=<gpio>, clock=<khz>, rotation=<0..3>, and panel=<0..3> bindings adapt the same driver to integrated panels. Panel 0 selects Elecrow BUSY-based revision detection and defaults to rotation 2; panels 1, 2, and 3 select the legacy Elecrow, green-sticker Elecrow, and Waveshare V2 profiles.

Use the module's eight-wire SPI connector and power it from the same 3.3 V logic domain as the ESP32. The module keeps its last image after detach. If SolarOS creates a display shell on epd0, run sessions, close that session with session close <id>, and then detach the expansion.

An M5Stack Unit CardKB attaches at its fixed I2C address and becomes a shared keyboard source for the shell and foreground apps:

expansion attach cardkb cardkb0 i2c=i2c0 addr=0x5f
input test cardkb0
expansion detach cardkb0

The CardKB firmware produces characters after key release. SolarOS maps its four navigation values to the same logical arrow keys used by PS/2 and BLE keyboards. The module's Fn combinations are device-specific and are ignored.

RTC and environmental sensor modules use the same named-I2C lifecycle. Only one provider of each service type can be active at a time:

expansion attach pcf85063 rtc0 i2c=i2c0 addr=0x51
expansion attach shtc3 environment0 i2c=i2c0 addr=0x70
date
temperature
humidity
expansion detach environment0
expansion detach rtc0

The PCF85063 irq=<gpio> binding is optional. The Waveshare board profile reserves its routed RTC interrupt on GPIO15; external modules can omit irq when only clock and calendar access is required. The generic RTC service discovers alarm, countdown, and interrupt-status support from the attached chip adapter, so applications do not depend on the PCF85063 register interface.

battery-adc takes an ADC pin and a divider ratio in thousandths. For a 2:1 resistive divider, use divider=2000:

expansion attach battery-adc battery0 adc=gpio4 divider=2000
battery
expansion detach battery0

Use a high-impedance divider suitable for the expected battery voltage. The divided voltage must remain inside the ESP32 ADC input range.

Other input devices follow the same lifecycle:

expansion attach gpio-keys keys0 key:UP=gpio17 key:ENTER=gpio2
expansion bus create ps2 ps2mouse clock=gpio17 data=gpio18
expansion attach ps2-mouse mouse0 ps2=ps2mouse
expansion attach analog-joystick joystick0 x=adc2 y=adc4 min=0 center=1650 max=3300 deadzone=100
input status

Use only bindings listed by expansion drivers and resources shown on the running board. A PS/2 mouse publishes relative pointer events. An analog joystick consumes two scalar streams and publishes axes, never keys. Foreground Python and Lua applications receive those pointer and axis events through solaros.input; use solaros.tui.getch() for keyboard characters.

On a board without built-in SD hardware, an SPI microSD adapter can provide removable storage. The SPI bus must include MISO and declare the selected CS pin. Attaching mounts the detected FAT volume at /sdcard. Unmount it before detaching the adapter:

expansion attach sdspi card0 spi=spi0 cs=gpio4
disk lsblk
disk umount
expansion detach card0

The sdspi attach command prints the card probe result to the invoking shell. The report includes the card identity, type, negotiated speed, capacity, CSD/SSR details, mount point, and any underlying block-I/O or FatFs mount error.

An SDMMC adapter uses direct clock, command, and data bindings. On ESP32-S3 the signals can use the GPIO matrix. Classic ESP32 accepts only the native slot-1 pinout: CLK GPIO14, CMD GPIO15, D0 GPIO2, and optionally D1 GPIO4, D2 GPIO12, D3 GPIO13.

expansion attach sdmmc card0 clk=gpio14 cmd=gpio15 d0=gpio2
disk lsblk
disk umount
expansion detach card0

An RFM95W wired to the ESP32-S3-DevKitC-1 spi0 bus with NSS on GPIO4 and reset on GPIO5 attaches as a multimode packet radio:

expansion attach rfm95 radio0 spi=spi0 cs=gpio4 reset=gpio5
radio status radio0
radio profile apply radio0 lora-eu868

Connect an antenna suitable for the module band before transmitting. See the expansion reference for the complete wiring and modulation configuration.

Use rfm69 for the 13 dBm RFM69W/CW modules and rfm69h for the 20 dBm RFM69HW/HCW variants. For example:

expansion attach rfm69h radio0 spi=spi0 cs=gpio4 reset=gpio5
radio config radio0 power 20

Both variants are 3.3 V devices. High-power transmission requires a supply that can sustain the module's transmit-current peak and an antenna appropriate for the selected band.

A WS2812/NeoPixel strip uses one runtime-safe GPIO and a declared pixel count:

expansion attach neopixel pixels0 data=gpio1 count=8
neopixel fill pixels0 16 0 0
neopixel set pixels0 3 0 16 0
neopixel clear pixels0
expansion detach pixels0

SolarOS stores colors in RGB form and transmits the strip's standard GRB wire order. Attach clears all declared pixels. set and fill refresh immediately in the shell; scripting APIs buffer changes until show().

An LEDC PWM audio output uses one runtime-safe PWM pin and appears as a normal mono playback device:

expansion attach audio-pwm pwm0 pwm=gpio1
audio devices
audio default pwm0
aplay /audio/example.mp3
audio default auto
expansion detach pwm0

The output is 8-bit PWM with a 78.125 kHz carrier and a 16 kHz native PCM rate. It is a signal output, not a speaker driver. Put a reconstruction low-pass filter, DC-blocking/coupling stage, and suitable amplifier between the GPIO and a speaker. A bare speaker can overload and damage the GPIO. Only one LEDC PWM audio device can be attached at a time.

A PCM5102A module uses three runtime-safe GPIOs and appears as a normal stereo playback device:

expansion attach pcm5102 dac0 bck=gpio1 din=gpio2 rck=gpio3
audio devices
audio default dac0
aplay /audio/example.mp3
audio default auto
expansion detach dac0

The example pins are valid on the Waveshare board. Connect the module's SCK pin to ground; the driver emits 64 BCK cycles per stereo frame so the PCM5102A can derive its system clock with the internal PLL. The driver registers dac0.playback as an exclusive 16 kHz, 16-bit PCM sink. Mono input is duplicated to both channels and device volume is applied in software. Current dual-I2S boards use I2S1, leaving onboard audio or composite video on I2S0. PCM5102A modules provide line-level output; connect an amplifier or powered input rather than a passive speaker.

A PCM1808 module uses four runtime-safe GPIOs and appears as a stereo capture device:

expansion attach pcm1808 adc0 mclk=gpio1 bck=gpio2 ws=gpio3 dout=gpio17
audio devices
arecord -d 5 -i adc0.capture /sdcard/pcm1808.wav
expansion detach adc0

The example consumes every runtime-safe GPIO on the Waveshare expansion header. Before applying power, configure the module for slave I2S mode: MD1=0, MD0=0, and FMT=0. Connect MCLK to the module's SCKI or SCK pin, WS to LRCK, and DOUT to DOUT. The driver supplies a 4.096 MHz system clock and 64 BCK cycles per 16 kHz stereo frame, receives the PCM1808's 24-bit samples, and registers adc0.capture as an exclusive signed 16-bit PCM source. Use the module-rated supply and a common ground; raw PCM1808 circuits need separate analog and digital supplies as specified by the manufacturer. On modules that expose both rails, connect +5V for the analog supply and 3.3V for the digital supply; powering only 3.3V leaves the converter's analog section unpowered.

The integrated codec and classic ESP32 DAC backends are also attachable when the target MCU and board resources support them. Codec attachments need a named I2C bus, an I2S controller, and all six audio GPIO signals:

expansion attach es8311-es7210 audio0 i2c=i2c0 i2s=i2s0 mclk=gpio38 bck=gpio14 ws=gpio13 din=gpio12 dout=gpio45 pa=gpio46
expansion attach es8311-duplex audio0 i2c=i2c0 i2s=i2s0 mclk=gpio42 bck=gpio10 ws=gpio11 din=gpio12 dout=gpio9 pa=gpio46

es8311-es7210 and es8311-duplex are ESP32-S3 drivers. esp32-dac is for the classic ESP32 internal DAC on GPIO25 or GPIO26. Its optional neg binding enables differential output, and amp plus active=0|1 controls an amplifier enable pin:

expansion attach esp32-dac audio0 pos=gpio26 amp=gpio25 active=1

Only one of these primary audio backends can be attached at a time. A fixed board-default audio0 cannot be detached.

The integrated display controller drivers can also create auxiliary display targets. Use a name other than the reserved primary name display0 for a runtime attachment. Each controller driver supports one attached instance, so its board-integrated display0 and an auxiliary instance cannot coexist:

expansion attach st7305 lcd0 spi=spi0 cs=gpio10 dc=gpio4 reset=gpio5
expansion attach ili9341 lcd0 spi=spi0 cs=gpio10 dc=gpio4 reset=gpio5 bl=gpio6 pwm=1 active=1
expansion attach st7796 lcd0 spi=spi0 cs=gpio10 dc=gpio4 reset=gpio5 bl=gpio6 pwm=1 active=1
display test lcd0
expansion detach lcd0

st7305, st7796, and ili9341 are available on ESP32 and ESP32-S3 firmware. The I2S-based cvbs-pal and vga32 drivers are available on classic ESP32. Composite PAL uses I2S0 and GPIO25. VGA32 uses I2S1 plus its six RGB and two sync GPIOs; see the expansion reference for its full binding list. A fixed board-default display0 cannot be detached.

Quick reference

solaros.expansion.drivers() lists compiled drivers and devices() lists currently attached devices with normalized bindings. attach(driver, name, bindings) and detach(name) manage them. Never assume an example name such as lcd0 or oled0 exists; inspect devices() or use a name explicitly supplied by the user. Foreground scripts consume attached pointer and axis sources through solaros.input.

SolarOS documentation

Creating A Custom Board With The TUI

Run the tool from the SolarOS source directory:

python3 scripts/board_config.py

The workflow is MCU-first:

  1. Select ESP32-S3 or Classic ESP32.
  2. Select a compatible neutral base board.
  3. Enter the board identity.
  4. Select the permanently wired expansion drivers.
  5. Select existing buses or create new buses, then enter addresses, pins, chip
  6. selects, ports, and optional driver parameters.

  7. Confirm the generated profile.

The tool rejects unsupported MCU/driver combinations, duplicate device names, invalid addresses and parameters, unavailable chip-select pins, and GPIO conflicts. Pins used by a fixed device or a new bus are removed from the runtime user-pin surface automatically. The output is one inherited TOML file under boards/manifests/.

For example, devkitc1_epaper_workbench.toml extends the S3 DevKitC profile and declares CardKB, SSD1683, and SDSPI as fixed devices. It also declares the second SPI bus used by the SD card. Build an inherited profile through its base environment:

SOLAR_OS_BOARD=devkitc1_epaper_workbench \
  pio run -e esp32_s3_devkitc1_n16r8

The configurator creates a SolarOS board profile, not a new PlatformIO environment. Use the base environment shown by the configurator, and keep SOLAR_OS_BOARD set for every build or upload invocation. To build and flash the example profile in one command:

SOLAR_OS_BOARD=devkitc1_epaper_workbench \
  pio run -e esp32_s3_devkitc1_n16r8 -t upload

The upload target builds first when necessary. Add --upload-port <serial-device> if PlatformIO does not select the correct port. If you select a firmware flavor explicitly, keep it on the upload invocation as well:

SOLAR_OS_BOARD=devkitc1_epaper_workbench SOLAR_OS_FLAVOR=core \
  pio run -e esp32_s3_devkitc1_n16r8 -t upload

You can edit the generated TOML after leaving the TUI. Validate it before a build:

python3 scripts/generate_board_profile.py \
  --manifest boards/manifests/devkitc1_epaper_workbench.toml \
  --manifest-dir boards/manifests \
  --drivers boards/expansion_drivers.toml \
  --validate-only

Fixed devices attach automatically and report origin=board. They cannot be detached at runtime. Use the established names when the device provides a primary board service:

Hardware roleFixed device name
Primary displaydisplay0
Removable storagestorage0
Audio input/outputaudio0
Primary pointertouch0
Battery monitorbattery0
Real-time clockrtc0
Environmental sensorenvironment0

The profile declares hardware, not policy. Keep job autostart commands in .shell/startup. Do not keep expansion attach, expansion bus create, or session create commands there for hardware that is now fixed in the profile.

After flashing, verify the automatic devices and their services:

status
pkg
port list
expansion drivers
expansion devices

A successful manifest validation and build do not prove wiring, polarity, power control, or peripheral behavior. Test the generated profile on its physical target.

Join us on: