Skip to main content

Networked serial console + USB HID keyboard injection for a fleet of headless machines — hub server, client SDK, and wire protocol.

Project description

Hub — the picotty package

The management side, packaged as the picotty Python distribution (uv). One process on one asyncio event loop runs two faces at once — a raw TCP server for the node swarm and a FastAPI app for the browser — over a shared in-memory registry and one SQLite database.

One distribution, three import surfaces:

Import What Needs
picotty.hub the server (registry + SQLite + :9000 TCP + FastAPI dashboard) [hub] extra
picotty.client the SDK: HubClient (REST) + HubEvents (WebSocket) base install
picotty.protocol wire framing, validation, PROTOCOL_VERSION base install

The lean base (httpx + websockets) is all a client needs; the server stack (FastAPI, uvicorn, aiosqlite, pydantic, pyyaml) comes from the [hub] extra, so a Pi Zero 2 W running only the Telegram sidecar stays small.

Layout

hub/
  pyproject.toml        # the picotty distribution (uv_build backend)
  uv.lock               # pinned, reproducible installs
  .python-version       # 3.11 floor
  src/picotty/
    __init__.py         # version, single-sourced from distribution metadata
    protocol.py         # public wire-protocol surface (re-exports hub/protocol.py)
    client/             # the SDK: HubClient (REST) + HubEvents (WS async iterator)
    sim.py              # the node simulator (picotty-sim console script)
    static/             # the dashboard, shipped inside the wheel (served at /)
    hub/                # the server
      main.py           # wires TCP + FastAPI + background tasks on one loop
      config.py         # process config (env) + operator-tunable defaults
      protocol.py       # length-prefixed JSON framing (authoritative)
      registry.py       # in-memory NodeState registry
      db.py             # aiosqlite: schema, queries, batched output, retention
      eventbus.py       # WebSocket fan-out with subscription filtering
      core.py           # the shared Hub: dispatch, ping/pong, audit, view-merge
      tcp_server.py     # swarm face on :9000
      telegram_setup.py # writes the sidecar .env for the dashboard's Telegram page
      tasks.py          # sweep, output flush, retention, stats, loop-lag
      api/{rest,ws,models}.py
  tests/                # test_db.py, test_integration.py, driver.py
  scripts/              # install / run / systemd unit

Run it (from source, with uv)

bash scripts/install.sh          # uv sync --extra hub (+ fetches terminal libs)
bash scripts/run.sh              # foreground (dev), loads private/hub-token.txt
# or install as a service that starts on boot:
bash scripts/install-service.sh  # renders + enables the systemd unit
journalctl -u swarm-hub -f       # follow logs

Manual equivalent:

cd hub
uv sync --extra hub
uv run --extra hub picotty-hub

Or install it as a tool (no repo checkout): uv tool install picotty, which puts picotty-hub and picotty-sim on PATH. See ../docs/packaging.md.

On first start the hub mints a shared node token and prints it once — put it in each node's settings.toml as NODE_TOKEN. It listens on :9000 (swarm) and :8080 (browser). Open http://localhost:8080 for the dashboard.

Configuration via environment (all optional): HUB_TCP_PORT, HUB_HTTP_PORT, HUB_DB_PATH, HUB_STATIC_DIR, HUB_TCP_HOST, HUB_HTTP_HOST, TELEGRAM_ENV_PATH. Runtime state (the SQLite DB) defaults to ~/.local/share/picotty/hub.db (honors XDG_DATA_HOME); the systemd unit uses /var/lib/picotty. Static assets ship inside the wheel. Operator-tunable settings (heartbeat, stale timeout, retention, confirm-dangerous, alerts) live in the DB and change via PATCH /api/settings.

Run under one uvicorn worker only. The single event loop is the design: a second worker would get its own registry and node sockets and the two would disagree about who is online.

Test without hardware

Start the hub, grab the printed token, then run one or more fake nodes with the packaged simulator:

uv run picotty-sim --id node-01 --token <TOKEN>
uv run picotty-sim --id node-02 --token <TOKEN>

Each simulator connects, heartbeats, answers commands, and streams fake serial output — enough to exercise the full dashboard. The suites:

uv run python tests/test_db.py           # offline db checks
uv run python tests/test_integration.py  # end-to-end over real TCP + REST

Using the client SDK

from picotty.client import HubClient

async with HubClient("http://hub:8080") as hub:
    print(await hub.health())
    async with hub.events_stream() as stream:   # the /ws feed
        await stream.subscribe("node-01")
        async for ev in stream:
            ...

This is what the Telegram sidecar imports; it needs only the base install.

Notes

  • Registry is disposable. On restart it starts empty and refills as nodes reconnect; the SQLite record survives.
  • Output is batched to SQLite (default every 500 ms) to protect SD-card write throughput; the live WebSocket stream is immediate and independent.
  • Auth is optional and off by default — the design assumes an isolated management VLAN reached through a VPN/tunnel, not port exposure.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

picotty-1.0.2.tar.gz (280.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

picotty-1.0.2-py3-none-any.whl (296.2 kB view details)

Uploaded Python 3

File details

Details for the file picotty-1.0.2.tar.gz.

File metadata

  • Download URL: picotty-1.0.2.tar.gz
  • Upload date:
  • Size: 280.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for picotty-1.0.2.tar.gz
Algorithm Hash digest
SHA256 4bb597c2ce501c57d821d8168d0e9b60970832af360f628196e54f267ff0092b
MD5 47cbd7bb655abdacbf4c81857004e64e
BLAKE2b-256 c827079d840d187631b7a77e0c5046e8d3c1c885630b911cc5c0c616bc6a7c34

See more details on using hashes here.

Provenance

The following attestation bundles were made for picotty-1.0.2.tar.gz:

Publisher: publish.yml on morpheuslord/PICOTTY

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file picotty-1.0.2-py3-none-any.whl.

File metadata

  • Download URL: picotty-1.0.2-py3-none-any.whl
  • Upload date:
  • Size: 296.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for picotty-1.0.2-py3-none-any.whl
Algorithm Hash digest
SHA256 64ba0d95c1355e2f258c2df73915e2b10984942691d3d25a85d35b6816e1e8d8
MD5 20d1c3f043644c3ae1e01a4c61f7f5e6
BLAKE2b-256 2a1457aa96bb728d7b53def2392b57c1f3406ab2eb42cfc7b567158021192907

See more details on using hashes here.

Provenance

The following attestation bundles were made for picotty-1.0.2-py3-none-any.whl:

Publisher: publish.yml on morpheuslord/PICOTTY

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page