Skip to content

AmeNote-Michael/mux-serial

Repository files navigation

I know, I know, there is TMUX - this was a fun experiment with AI.

mux_serial

Serial-over-TCP multiplexer for macOS and Linux. Lets multiple processes — AI test agents, interactive consoles, loggers — share a single physical serial port simultaneously. Servers advertise themselves via mDNS so clients discover them by friendly name across the local network without knowing the IP or port.

Copyright (c) 2026 DBML Group Inc. — MIT License
Author: Michael Loh (with Claude)


Capabilities

  • Multi-client fan-out — one serial port, unlimited simultaneous readers. Every byte from the device is broadcast to all connected clients at once.
  • LAN-wide discovery — mDNS advertisement means mux_client -n "my-device" works from any machine on the network, no IP or port configuration needed.
  • Named presets — save device/baud pairs by friendly name; start a server instantly with mux_server -n "my-device".
  • Multiple servers — run one server per serial port from the same interactive menu. Each runs as its own OS process (true isolation, no GIL contention).
  • Device resilience — server survives USB unplug and embedded system reboots. TCP clients stay connected; serial port reconnects automatically when the device reappears.
  • Password auth — optional per-server password. Clients must send the correct password during the AUTH handshake or are rejected.
  • Identity — username advertised in mDNS so other developers on the LAN can see who is running which server.
  • Electron desktop app — native Mac app (Mux Serial.app) with multi-server management, live terminal, client activity indicators, dark/light mode, and font size controls.
  • Interactive consolemux_client provides a terminal session to the device. Exit with ctrl+].
  • Scripting API — import MuxClient into test agents for programmatic send/expect via the full pexpect API.
  • Timestamped loggermux_logger captures everything on the wire with timestamps, to stdout or a file.

Requirements

  • Python 3.8+ (tested on 3.14)
  • macOS or Linux (Windows not supported — uses select.poll() and Unix fd semantics)
  • Dependencies: pip3 install pyserial pexpect zeroconf

Installation

Python CLI tools

# Install dependencies
pip3 install pyserial pexpect zeroconf

# Symlink scripts to /usr/local/bin (or anywhere on PATH)
ln -s "$(pwd)/mux_server.py" /usr/local/bin/mux_server
ln -s "$(pwd)/mux_client.py" /usr/local/bin/mux_client
ln -s "$(pwd)/mux_logger.py" /usr/local/bin/mux_logger
chmod +x mux_server.py mux_client.py mux_logger.py

On Linux, add yourself to the dialout group for serial port access:

sudo usermod -aG dialout $USER   # log out and back in to take effect

Electron desktop app (macOS)

Install the .dmg from mux-serial-app/dist/. The app provides a GUI alternative to the Python server — start/stop multiple serial port servers, watch the live terminal, and manage presets. Settings and presets are shared with the Python CLI (~/.mux_serial_presets.json, ~/.mux_serial_settings.json).

Remote machine (client only)

Only mux_client is needed on machines that connect to a server running elsewhere:

pip3 install pexpect zeroconf
# copy mux_client.py to PATH

Usage

Server — interactive menu

mux_server

The menu lists running servers (each as its own OS process) and saved presets. From here you can:

  • Enter a preset number to start that server immediately
  • [n] — configure and start a new server (prompts for device, baud, name, password)
  • [m] / [d] — modify or delete a saved preset
  • [x] — stop a running server
  • [q] — quit cleanly (stops all running servers)

Server — headless (non-interactive)

# Start instantly from a saved preset
mux_server -n "my-device"

# Override a preset value (offers to update the saved preset)
mux_server -n "my-device" -b 460800

# Fully headless — no prompts, no preset interaction
mux_server -d /dev/cu.usbserial-1420 -b 115200 -n "my-device"

# With password and username
mux_server -d /dev/cu.usbserial-1420 -b 115200 -n "my-device" -P secret -U alice

# All options
mux_server [-d DEVICE] [-b BAUD] [-n NAME] [-P PASSWORD] [-U USERNAME] [-p TCP_PORT] [-H BIND_ADDR]

Multiple serial ports on one machine: Start the server multiple times in separate terminals (each invocation is its own OS process), or use the interactive menu which manages multiple processes from a single session.

Client — interactive console

mux_client                          # browse mDNS, pick from list
mux_client -n "my-device"          # connect by friendly name (any machine on LAN)
mux_client -H 192.168.1.10 -p 5000 # direct connect, skip mDNS
mux_client -U alice                 # identify yourself as alice

Exit with ctrl+].

Client — scripting API

from mux_client import MuxClient

# Connect by mDNS friendly name (works across the LAN)
c = MuxClient.from_name("my-device")
c.connect()

# Full pexpect API via c.term
c.term.sendline("status")
c.term.expect("OK", timeout=5)
print(c.term.before)

c.close()

# Or connect directly by host/port
c = MuxClient(host="192.168.1.10", port=5000)
c.connect()

Logger

mux_logger -H 192.168.1.10 -p 5000        # stdout with timestamps
mux_logger -H 192.168.1.10 -p 5000 -f run.log  # also write to file
mux_logger -H 192.168.1.10 -p 5000 -l     # line-buffered mode

Settings

~/.mux_serial_settings.json stores your default username (shared between Python CLI and Electron app):

{ "username": "alice", "theme": "dark", "fontSize": 12 }

~/.mux_serial_presets.json stores named device/baud configurations (shared between Python CLI and Electron app):

{ "presets": [{ "name": "my-device", "device": "/dev/cu.usbserial-1420", "baud": 115200 }] }

Architecture

Physical device (/dev/cu.* or /dev/ttyACM*)
        │
        │  pyserial (exclusive open)
        │
  mux_server  ──── mDNS (_mux-serial._tcp.local.)
        │           TXT: name, device, baud, user, auth
        │  TCP (OS-assigned port, 0.0.0.0)
        │  AUTH handshake: AUTH <user> [<password>]\r\n → OK\r\n / ERR\r\n
        ├─── mux_client     (interactive console or scripting API)
        ├─── mux_client     (second agent on same or different machine)
        ├─── Mux Serial.app (Electron desktop terminal)
        └─── mux_logger     (timestamped capture)

All reads from the serial device are broadcast to every connected client simultaneously. All writes from any client are forwarded to the device immediately. The server does not arbitrate concurrent writes — agents that need exclusive access must coordinate among themselves.


Known Limitations

  • No write arbitration — two clients writing simultaneously may interleave bytes. Planned: in-band token/lock protocol (\x00LOCK\n / \x00UNLOCK\n).
  • mux_logger does not yet support mDNS discovery (-n name); connect by host/port directly.
  • Client reconnect — if the server process restarts, connected clients exit and must be restarted manually. Planned: auto-reconnect with backoff.

License

MIT License — see LICENSE.

Copyright (c) 2026 DBML Group Inc.
Author: Michael Loh (with Claude)

About

Serial-over-TCP multiplexer for macOS and Linux — share one serial port across multiple AI agents, consoles, and loggers via mDNS

Resources

License

Stars

0 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors