smol is a platform for very small computers. One no_std Rust binary carries a whole registry of apps — a clock, a mesh-wide multiplayer snake, a roster radar, a living creature, and a 260K-parameter transformer that writes children’s stories with no WiFi and no cloud — and you move between them with the one button the board has. Underneath, the fleet services come free with the platform: boards find each other over ESP-NOW with no router in the middle, elect a gateway, publish themselves into Home Assistant, take every setting back from it without a reflash, and update themselves with signed firmware over the air — even the WiFi-less ones, over the mesh. It started on a $3 chip. Today it is five targets across four chip families — RISC-V and Xtensa, a 72×40 monochrome OLED and a 2.8″ colour touchscreen, all speaking one wire format to each other.
smol is not an app with some features bolted on; it is a small platform, and everything else on this page is something running on it. Across the whole fleet there is one firmware source, and for any given board one image — the chip and the tier are chosen together, per build. Inside it, every screen is a plugin — its own state struct implementing one trait — and a single table wires them into the menu you reach with the board’s one button. There is no allocator, no Box<dyn>, no vtable, and no dynamic loading: the whole registry is resolved when the binary is built, so a plugin costs you flash and nothing else. Which plugins are present is a build tier, not a runtime setting — and the tiers stack, so the cheapest board and the fleet gateway run the same code paths with a different slice compiled in.
smol began as one binary on one $3 chip. The roster is now five targets across four chip families, and the folder targets/ is the roster — one directory per board, each carrying a manifest that declares its chip, its firmware flavour and whether it produces a download. Adding a folder adds a target; there is no second list anywhere. Badges are honest, and the distinctions behind them are the ones that have cost us time: 🟢 glass-verified — a human watched it work · 🟡 mesh-proven — heard on the mesh, but the fleet firmware only compiles for it, which is a real claim and a much weaker one than builds.
Five boards, four chip families, two firmwares — so “does smol do X?” has stopped having one answer. This is the short version; the full matrix lives in docs/CAPABILITIES.md, where all 69 rows carry a reason on every cell that isn’t green. Three things worth knowing before you read it. Parity is judged per flavour, not per board: the fleet firmware and the touch GUI firmware are genuinely different machines, and the S3 runs both — so it gets two columns. The GUI flavours are a superset of the plain node, which is JP’s ruling and not our inference: every fleet feature is in scope on a touch board too, so a fleet feature the GUI firmware hasn’t implemented yet is a gap with an issue number against it, never “not applicable.” And a red cell never means “not done” — it means the hardware cannot: the $1 board has no screen because none is fitted, and the watch has no Bluetooth earbuds because that silicon has no ISO link layer at all, a question closed forensically rather than deferred. Every amber cell links the issue that closes it, and the cell is that issue’s acceptance test.
| capability | c3 | c3-oled | s3 · fleet | s3 · GUI | c6 · GUI | c5 · GUI |
|---|---|---|---|---|---|---|
| mesh & fleet services | ||||||
| SMOLv1 mesh membership | ✅ | ✅ | ✅ | ✅ | ✅ | ✅first non-C3 on the mesh |
| Crown election / gateway | ✅ | ✅ | ✅ | 🔶observe-only #278 | 🔶observe-only | 🔶 #488 |
| WiFi + NTP + MQTT → Home Assistant | ✅ | ✅ | ✅ | ✅ | ✅ | 🔶HA dark for now #488 |
| HA discovery + Update entity | ✅no _soc yet #498 | ✅ | ✅ | 🛠own topics #492 | 🛠own topics #492 | 🛠 #492 |
| Keyed config, no reflash | ✅11 keys | ✅ | ✅ | 🛠only S/U/R #490 | 🛠 #490 | 🛠 #490 |
| Custom screens | ✅ | ✅ | ✅ | 🛠not rendering #473 | 🛠 #473 | 🛠 #473 |
| over-the-air updates | ||||||
| Mesh-OTA receive | ✅ | ✅ | 🔶the 345→1405 roll was a WiFi self-fetch, not a mesh receive (#518) | 🔶compiled #495 | 🔶unproven on the C6 #495 | 🔶 #495 |
| Mesh-OTA relay / serve | ✅ | ✅ | 🔶client only | 🔶 #495 | 🔶 #495 | 🔶 #495 |
| HTTP / push OTA | ✅ | ✅ | ✅ | ✅ | ✅zero-touch | 🔶 #488 |
| Ed25519 signed-image verify | ✅both paths | ✅ | ✅ | 🔶mesh yes, HTTP no #489 | 🛠build-monotonicity only #489 | 🛠 #489 |
| display, input, output | ||||||
| Display | ❌no panel fitted | ✅SSD1306 72×40 | ✅ILI9341V, 4× scaled | ✅ILI9341V | ✅CO5300 AMOLED | ✅ST7789 |
| Touch | ❌none fitted | ❌ | 🛠no fleet driver | ✅FT6336U | ✅FT3168 | 🔶XPT2046, poll-only |
| Status LED | ✅ | ✅ | ✅WS2812 over RMT | 🛠pin only #491 | ❌none fitted | 🛠pin only #491 |
| smol Cast → WLED matrix | 🔶 | ✅ | 🔶mirror blank #483 | 🔶default-off #493 | 🔶 #493 | 🔶 #493 |
| apps, sensors, power | ||||||
| Mesh Snake (head-to-head) | ✅ | ✅ | ✅ | 🛠 #494 | 🛠 #494 | 🛠 #494 |
| The Bard (on-device transformer) | 🔶on glass, off-fleet — 6,720 B short #497 | 🔶same | 🔶14,400 B short #497 | 🔶opt-in | 🔶30,480 B short #497 | 🔶 |
| Audio out | ❌no codec | ❌ | 🛠codec ACKs, no path #476 | 🔶acoustic proof pending #477 | ✅ | 🛠header un-scoped #486 |
| Audio in / voice | ❌ | ❌ | 🛠mic fitted #476 | 📋phase 2 #478 | ✅ES7210 + speech-to-text | 🛠 #486 |
| Battery % | ❌ | ❌ | 🛠ADC fitted, unread #479 | 🛠declared, no reader #479 | ✅AXP2101 | 🛠 #486 |
| Motion sensor · die temperature | ❌·✅ | ❌·✅ | ❌·❌no S3 TSENS | ❌·❌ | ✅·✅ | ❌·❌ |
| radios | ||||||
| 802.15.4 (Zigbee / Thread) | ❌no radio | ❌ | ❌no radio | ❌ | 📋mutually exclusive with ESP-NOW | 📋back-burnered #399 |
| Bluetooth LE | 🔶silicon has it; refuted in practice | 🔶 | 🔶not built | 🔶 | ✅GATT server | 🔶 |
| build & release | ||||||
| CI builds a linked image | ✅ | ✅ | 🛠no Xtensa toolchain on runners #413 | 🛠 | 🛠needs a wider ROM region | 🛠no measured budget row #485 |
| Published download | ✅ | ✅by design: use the c3 image | ✅ | ✅ | ✅ | ✅ |
| Stack-floor provenance | ✅derived from a measured peak | ✅same | 🔶observed-sufficient #484 | 🔶 #484 | 🔶a boot assert, not a measurement | 🛠none of its own #485 |
Every number below was pulled from the chip over USB with esptool — not a datasheet guess. This is exactly what we're working with.
▚ the exact board — ESP32-C3 SuperMini + 0.42″ OLED on AliExpress ↗
The Bard is the app that proves the platform: it is a plugin like any other, it shows up in the same one-button menu as the clock, and it is the reason the rest of this page is not a toy claim. Press the button and a real transformer starts composing — 260,000 parameters, int8, from the same llama2.c/TinyStories lineage the big toy models come from — on a chip with 400 KB of RAM and no floating-point unit. The weights never load: the model is executed in place from flash, memory-mapped, so all 283 KB of it costs zero bytes of RAM. There is no WiFi, no cloud and no gateway in the loop — a board alone in a drawer can tell a tale. Every node gets its own protagonist, so id8 tells owl stories and its neighbour tells dragon ones.
The games are plugins in the same registry as everything else — a fistful of them sharing the glass with a clock, a story engine and live telemetry, all in the one binary. The headline act is mesh multiplayer: a shared world every board roams at once, over an ESP-NOW mesh, no server in sight. The Block Digger up top is running its actual render logic in your browser — that's the kind of thing that fits.
one board's viewport into the shared 256×256 world — peers dead-reckoned so a dropped frame never ghosts them
A single 256×256 world that every board's snake roams at once. Your viewport scrolls with your head — no walls, the world wraps — and rival boards slide into view drawn by their magical realm names. Score is length, and length is your seat on a mesh leaderboard every node agrees on. It runs on 18-byte state frames at 5 Hz; peers are dead-reckoned between frames, so lag never turns a snake into a ghost.
Flashed & running on all three bench boards (July 2026) (waves 3+4) · live N-board play is the last check · issue #5 ↗
In World Snake, rare treasures grant a single timed power. They spawn deterministically — every board computes the same treasure, in the same cell, from the shared mesh clock, so nobody negotiates — and each reads in a single bit of ink. All six are flashed + running on all three bench boards (July 2026) (waves 3+4, phasing tested 52/52). Names are drawn from the same realm-sigil corpus that names the boards.
One power at a time · effects expire on the shared clock · a lost frame can't leave you ghosted · player guide ↗
Everything below lives in the same binary — pick it from the BOOT-button menu. From the one that started it all to five games built for this exact board.
This is the rest of the registry. Every card is a plugin in the same binary, reached from the same button — no installs, no downloads, no second image. Some need no radio at all and run on a board with nothing but a battery; others are compiled in only at the mesh tier because they consume the roster. What decides is the build tier, not a setting. Each card links to its source.
A platform you have to re-flash to adjust is not much of a platform. smol boards take their settings over the air, from Home Assistant, on a keyed config channel (#56) — one retained MQTT message per knob, relayed on to the boards that have no WiFi of their own. Parsing is strict and panic-free by design: a malformed or unknown value is ignored and the board keeps what it had, because the failure mode of a config channel must never be a brick. Identity is the deliberate exception — a node’s id lives in NVS and OTA never touches it, which is exactly what lets one image serve the whole fleet.
The interesting part of a platform is rarely the API; it is what a program gets simply by being on it. A board that joins the smol mesh inherits a set of services it never has to implement: it is discovered, named, timed, watched, and updated. None of that is app code. All of it is the reason an app on a $1 board can be three hundred lines long.
Prebuilt images are on the releases page. You need a board, a USB cable and espflash. Per-target downloads have landed: the nightly now carries five images — the C3 fleet tier, both S3 flavours, the C6 watch and the C5 CYD's GUI — because the build walks the targets/*/target.toml manifests and packages every board that declares one, through the same reproducible-build path the over-the-air publisher uses. The two Xtensa images ride a job that installs their toolchain itself, which is why they exist despite CI being unable to compile that chip on a stock runner. Each image ships a NOTES.md beside it carrying its sha256 and that chip's stack-floor provenance in plain words — check the image against that number — or, if the release also carries a combined SHA256SUMS, against that with sha256sum -c --ignore-missing SHA256SUMS (the flag matters: you downloaded one image, not all of them). Which board gets which flavour is the capability matrix; the full release story — nightlies versus versioned releases, the reproducible-build ceremony, and the flashing traps — is in docs/RELEASES.md.
Read this first: published binaries carry placeholder credentials — including the mesh key. They are built from a clean checkout with throwaway values — good enough to compile, never good enough to ship — and the key is a fixed published placeholder on purpose, because a random per-build key would destroy the byte-reproducibility that makes an image's sha256 its identity. A board flashed from a download boots, drives the display and runs the menu, the games and the Bard. It will not join WiFi, reach a broker, or talk to an existing mesh.
🔑 So re-key before you trust your mesh. A key baked into a public binary is public: two boards flashed from the same download share a key anyone can extract. Placeholder-key boards can mesh only with other placeholder-key boards, and can never join a re-keyed fleet. To own yours, regenerate GROUP_KEY in rust/clock/src/secrets.rs — 32 random bytes, starting from secrets.rs.example — then rebuild and reflash. It is a rebuild, not a setting: the key is compile-time, and nothing on the dashboard can change it.
sha256sum <image>.bin # compare with the sha in its NOTES.md sha256sum -c --ignore-missing SHA256SUMS # or this, if the release carries one espflash erase-region --port /dev/ttyACM0 0xf000 0x2000 espflash write-bin --port /dev/ttyACM0 0x0 <image>.bin
The middle line clears otadata and is not optional on a board that has ever taken an update over the air: the board is running from the other slot, so a plain flash succeeds and then silently never runs. It spares nvs, so a provisioned node id survives. Afterwards, read the Loaded app from offset line — it names the slot that actually ran.
Boards already on the mesh do not update this way. They take ed25519-signed firmware over the air, one canary board at a time. Downloads here are for a board that does not have smol on it yet.
◇ The build ledger — 54 tasks and 35 agents, archived as of 2026-07-07. A record, not a live feed: the panels below show that snapshot, and each prints its own date.
This fleet updates itself. A team of agents built smol in the open — flashing firmware, wrangling git, and closing every wave behind an adversarial review gate — and it is now genuinely remarkable for $1–$3 boards: a shared-world MMO snake with treasure-powers; an ESP-NOW mesh with newest-wins time sync, realm-sigil names, and ~20-node-capable peer handling; an MQTT-native Home Assistant link (live battery, state-of-charge and grid on every OLED); remote per-node config straight from the dashboard (LED, units, plugin visibility, reboot, custom screens — even the MQTT broker leg and the OTA image host, and the physical pins: wire a button or lamp to a free GPIO and drive it from HA); per-node observability with config-drift detection and a live OLED mirror in HA; and — the headline — signed over-the-air updates that reach even the WiFi-less nodes over the mesh, plus The Mesh Familiar: one creature that lives on a board and hops to a neighbour when you unplug it. And tonight the fleet learned to heal its own updates: the WiFi-fetch deaf-window that stalled mesh-OTA was pinned at the packet level and answered two ways — proactive re-association (#217) and peer-sourced OTA (#237), where an updated board serves the next over the mesh so a single download seeds the whole fleet. And then the fleet learned to write: The Bard puts a real 260K-parameter transformer on the $3 chip, executed in place from flash, composing a fresh children’s story onto the 72×40 OLED at 202 ms per token — verified bit-for-bit against an independent reference before it was believed. And then it stopped being one chip: the roster is five targets across four chip families — RISC-V and Xtensa, monochrome and colour touch — with the ESP32-C6 watch in-tree as a subtree, an ESP32-C5 heard on the mesh, and an ESP32-S3 touchscreen glass-verified in August 2026 running both firmware flavours. Underneath it all, the async re-platform's first phase has merged. Hardware-verified on the fleet.
Ideas we're kicking around. Edit this list as the project grows — it's your design doc.
runq.c, was disqualified for silently dropping in-dimension elements of every w2 row. SHIPPED — on glass on the bench, then a canary soak that survived a hard power-cycle from the inactive OTA slot; the fleet-wide roll rides the next release train. Issue #300, PR #301.SMOLv1 CFG frame over the mesh. SHIPPED. Issues #21/#56/#48/#43/#55/#52/#45/#71/#100/#72.embassy-time's driver underneath it and bounded async waits replacing sends that used to spin on a callback that might never come. The reason is a measurement, not an aesthetic: on a two-board bench, a held WiFi window that left the mesh deaf for about 15 seconds under the blocking transport left it deaf for 169 ms under the async one — about 89×, against a 279 ms ambient floor. That number was taken during the port campaign with less running than the fleet image carries, and re-measuring it against the merged tree is still an open item — so it is the motivation, not a claim about today's image. Phase 1 merged (PR #391). STEP T — giving the seven transport pairs one declared owner — is the blast-radius step and is argued in full before its code is written, in docs/embassy/T-SCOPE.md. In progress. Issue #335.A vision for what a fleet of smols becomes at home: one ~$3 smol per room — an OLED panel showing who's home and what the light's doing, presence-driven. Scoped as issue #75, not yet built.
A fleet of agents scouted the ESP32-C3 landscape while smol came together. The full reports live in the repo — here's the map.
Projects that run on this exact ESP32-C3 + 0.42″ OLED (72×40). A couple need their art re-drawn for the tiny screen — noted inline.
smol didn't appear from nowhere. Its ESP-NOW → MQTT → Home Assistant plumbing is commodity — a well-trodden pattern many have built. What's actually novel is the whole: a no_std Rust game-console mesh on $3 boards, a deliberate single-radio burst time-share, and a retained-payload → mesh-rebroadcast downlink to display-only leaves. Below are the projects that shaped it, grouped by the thread each runs in — credited honestly, with what smol borrows and where it diverges. A few handed over portable ideas; most gave lineage, identity and nerve rather than code.
◇ BLE presence (the #22 basis) — smol's beacon-tracking idea would build on Bermuda (HACS trilateration that reuses existing bluetooth_proxy anchors) and ESPresense (dedicated MQTT anchors). Prior art and reference, not code smol borrows.
No printable pocket-watch case existed for this board, so an agent generated one from scratch — a parametric round shell in the classic orientation: the chain bail and a removable crown-style USB-C cover stack at the top, with the OLED window, BOOT/RESET plunger bores and two clear-PLA LED light-pipes below. Inside: pockets for the board + a 502030 LiPo, a TP4056 charge slot, and a press-fit back lid. Three printable parts — body, lid, crown — and the OLED is rotated 180° in firmware so it reads upright when hung. These are live renders of the actual STL.


Exact-fit 3D-printable cases for the 0.42″ OLED SuperMini. No dedicated handheld shell exists yet — so for a pocket player, start from an editable one and add a button perch at GPIO9. Board is ~24.8×20.45 mm, USB-C on the short edge opposite the OLED. Full ranked list in docs/cases.md.