micronetcode

A generic, minimal TCP command channel for apps: ConnectionManager, Channel, Codec, Message, Client. One thread per client, one channel per connection, one reply per command. App-agnostic — it moves bytes and routes replies; what a command means is yours.

Every action doable via clicks or keyboard should be doable over a socket. micronetcode is the boilerplate for that socket. On the app side the integration goes in the I/O handler, exactly where rl.IsKeyPressed / rl.IsMousePressed would sit. Every message must be responded to.

0.4.0 removed the ui_cli_manager package. The CLI layer (shlex quoting, typed argument validation, UICLIManager) was never used as-shipped: robosim drives ConnectionManager directly, and visual-graph-navigation forked it. Policy diverges per app; transport does not. It now lives at examples/1-ui-cli-manager/ui_cli_manager.py — copy that file into your project and own it. Nothing else in micronetcode changed.

Docs: meehai.gitlab.io/micronetcode — built by docs/build_docs.sh (pdoc; no sphinx/config). Build locally with bash docs/build_docs.sh and open the printed file:// link.

Try it

python3 examples/1-ui-cli-manager/app.py --headless   # run the app
printf 'set_text "speed: 12 m/s" 620 340\ndraw_circle 100 100 25\n' | ncat localhost 42069

Usage

Bring your own Codec (bytes ↔ Message) and poll the manager from your main loop:

from micronetcode import ConnectionManager

manager = ConnectionManager("0.0.0.0", port=42069, codec=MyCodec())
manager.start()                   # background thread: accept + serve TCP clients

while not rl.WindowShouldClose():
    # I/O handling: polls the channels like rl.IsKeyPressed, never blocks
    try:
        msg = manager.get_one_message()
        msg.client.channel.main2tcp.put(handle(msg), timeout=TIMEOUT_S)
    except Empty:
        pass

    rl.BeginDrawing()
    # ... draw
    rl.EndDrawing()

Every command gets exactly one reply — the client thread blocks until you answer, so never leave a message unresponded.

Examples

  • examples/1-ui-cli-manager/ — the CLI layer, meant to be copy-pasted: ASCIICodec + UICLIManager driven by a microspec protocol.json, so cli_cmd.args reaches the app already converted and range-checked. app.py is a raylib app whose entire surface is the protocol file. Start here.
  • examples/2-fast-handler-cli.py — raw ConnectionManager with a network_handler: cheap commands answered straight from the client thread, the rest handed to the main loop. Includes a minimal newline Codec.

Concurrency

  • Thread per client: the listener only accepts connections and hands each one to its own daemon thread. A slow/stalled client can never starve the listener or other clients.
  • max_connections cap (default 10): when every slot is taken, new connections are refused — the client receives Server is full and the connection is closed. 0 falls back to the default, <0 raises ValueError. Pass max_connections=N to bound the thread count.
  • One channel per slot: each connection owns a Channel (two 1-deep queues, micronetcode.channel). get_one_message() polls the channels in order; each reply routes back to the client that sent the command — interleaved clients never cross wires. Strict-channel semantics: a client can have at most one outstanding request; answer before sending it the next command.
  • Scripted commands (script_lines / --script, a UICLIManager feature): run first, in order, before any live client command; their responses are journaled, never sent to a client (they have no channel).

Protocol

The wire format is the Codec's business, not the library's. What the shipped examples do:

  • ASCII, newline-delimited; one line = one command.
  • Double-quoted arguments with spaces arrive as one argument (shlex, example 1).
  • Lines starting with # are comments (example 1).
  • Every command gets exactly one response; invalid input gets an error response.
  • Half-close your write side (Ctrl-D / pipe EOF) to disconnect.

Installation

Python 3.11+. pip install micronetcode, or pip install -e . from a checkout (add [dev] for pytest + the examples' raylib / microspec-py). The only runtime dependency is loggez.