PICOTTY

Pico · TeleTYpewriter

PICOTTY

an archaic telex reborn as a networked serial console

>

A star-topology serial console for a fleet of Raspberry Pi Pico nodes. Each node plugs into a headless machine's USB port and becomes a networked serial console with keyboard injection — it reads the target's serial line over the network and types keystrokes back into it. One hub coordinates the swarm and gives you a single browser dashboard to drive every node.

This is a serial console, not a KVM. There is no video capture. Each node reads what the target emits on its serial line and types back into it — so reading output depends on the target actually having a serial console configured.

Why it exists

Lights-out management a rack of mini PCs never shipped with

Consumer mini PCs expose no accessible serial port — no BMC/IPMI, no DB9/RJ45 console, often not even a usable header. There's no out-of-band way to see a stuck boot, poke a frozen box, catch a kernel panic, or drive the BIOS without walking over with a keyboard and monitor.

PICOTTY gives every headless machine exactly that, over the network: a tiny Pico on its USB port acts as a USB keyboard — so you can type at BIOS, GRUB, initramfs, and the OS — and reads back its serial console, all surfaced in one dashboard.

The topology

One hub, a swarm of nodes, one dashboard

A shared Raspberry Pi Zero 2 W runs the hub. Each node is a Pico on a target's USB port, reachable over your management network. You drive the whole fleet from a browser — or from your phone via the Telegram sidecar.

Browser dashboard Telegram sidecar HUB Pi Zero 2 W FastAPI · SQLite Pico node target Pico node target Pico node target
HTTP · WebSocket & wire protocol (Ethernet) USB HID keyboard + serial console

What you get

A full operator console, not a wire

⌁Networked serial consoles

Read each target's serial console live and type into its serial login, all from the browser over the network — with a real terminal renderer and ANSI cleanup.

⌨Keyboard injection

Each node is a USB HID keyboard, so it drives the target everywhere a keyboard works: BIOS, GRUB, initramfs, the OS. Per-node keyboard layouts.

❏Macros & quick chords

Reusable HID sequences built from type / keys / wait steps, saved in a library and replayable on any node. Custom quick chords you save and fire.

↻Runbooks & expect engine

A wait-for-output expect engine and YAML runbooks you author, view, and edit in the browser — then run across a whole group.

⬆OTA firmware updates

Push bundles over the wire: chunked, checksummed, .zip-upload, with canary rollout and watchdog-revert. Firmware-only or settings-only pushes.

♥Machine liveness

Prompt-state badges and a machine up/dead badge that tells you whether the target is alive — not just the node — plus a three-method reboot menu.

⎘Durable history & replay

Every node, command, result, and output chunk recorded in SQLite. Timestamped event log, asciicast session replay, and a raw serial bridge for minicom / PuTTY.

⇄Dual-hub failover

A primary + backup hub per node with a shared node token, runtime steering (move / set-home / pin), offline command queue, and webhook / ntfy alerts.

On PyPI

One wheel, three import surfaces

The hub ships as the picotty package, built with the uv build backend. The base install is a lean client; the server and Telegram stacks live behind extras so a client stays small.

picottyv1.2.0
GPL-3.0-or-later Python ≥ 3.11 pip · uv FastAPI CircuitPython node
picotty.protocol

The wire protocol — version, frame encode/read, send validation.

base install

picotty.client

The SDK — HubClient (async REST) + HubEvents (WebSocket).

base install

picotty.hub

The FastAPI server behind picotty-hub.

[hub] extra

# drive a running hub from the lean client SDK — no server stack needed
import asyncio
from picotty.client import HubClient

async def main():
    async with HubClient("http://hub:8080") as hub:
        print(await hub.health())
        async with hub.events_stream() as stream:
            await stream.subscribe("node-01")
            async for ev in stream:
                print(ev["event"], ev)

asyncio.run(main())

Quick start

Three phases: hub, node, target

Your node ids, hub address, and token live only under private/ (gitignored) — nothing machine-specific is committed.

1

Install the hub

on the Pi Zero 2 W
uv tool install 'picotty[hub]'   # server stack + both CLIs
picotty-hub                     # → dashboard at http://<hub-ip>:8080
2

Build & flash a node

on your flashing machine
bash firmware/scripts/install-deps.sh
bash firmware/scripts/build.sh --node <id> \
     --drive /run/media/$USER/CIRCUITPY
3

Wire up the target

on each managed machine

Point the target's serial console at the node (example for Proxmox):

sudo bash target-setup/proxmox-serial.sh
No hardware yet? Run a fake node against the hub with uv run picotty-sim --id demo --token <TOK> and explore the whole dashboard before you solder anything.

Hardware

A Pico, an Ethernet HAT, one cable

Per target machine: a Raspberry Pi Pico (or Pico 2) + a WIZnet W5100S Ethernet HAT + a data-capable USB cable + an Ethernet drop. One shared Pi Zero 2 W runs the hub. The all-in-one W5100S-EVB-Pico replaces the Pico + HAT.

Pico + HAT

SPI Ethernet over the WIZnet W5100S HAT, leaving USB free for the target.

Pi Zero 2 W

One shared hub coordinates the whole swarm and serves the dashboard.

Enclosure

A 3D-printable case — STL, STEP, and editable Fusion 360 source in the repo.

→ Full bill of materials & topology

Documentation

The full technical reference

architecture.md

Component roles, the single-loop hub, the one-cable USB design, the wire protocol, data model.

hardware.md

Physical topology, bill of materials, component tree, sizing.

deployment.md

The three build phases, the end-to-end workflow, the full script reference.

firmware.md

Firmware lifecycle, LED codes, keyboard layout, OTA capability, hardening.

dual-hub.md

Dual-hub failover, runtime steering, the independent-peers model, the shared node token.

operations.md

Observability, prompt-state badges, HID vs serial input, session recording, alerting.

automation.md

Prompt-state detection, the expect engine, offline command queue, YAML runbooks.

ota.md

Over-the-wire firmware updates: the safety model and rollout posture.

packaging.md

The picotty package: import surfaces, extras, entry points, the client SDK, publishing.

telegram.md

The Telegram bot sidecar: stats / alerts / gated terminal over chat.

considerations.md

Security boundary, target requirements, power, roadmap.