// every connector sits on the same fixed stack ┌────────────────────────────────────────────┐ │ Connector (workspace/connectors/*.js) │ what you write ├────────────────────────────────────────────┤ │ Driver / Protocol+schema / Engine │ what you pick ├────────────────────────────────────────────┤ │ Transport (serial / tcp / usbtmc) │ how bytes move └────────────────────────────────────────────┘
How it works
One server. Every device.
Muxit runs as a background service on your machine. Devices connect over a transport (serial, TCP, or USB) and speak their own protocol on top (SCPI, MQTT, ONVIF, or something custom). Muxit translates all of it into one clean JavaScript API that scripts, dashboards, and AI share.
1 — Devices
Anything with a port or an IP address.
Power supplies, oscilloscopes, multimeters, robots, spectrometers, temperature loggers, ESP32 boards, Arduinos, Raspberry Pis, IP cameras. If it speaks a protocol, Muxit can probably reach it.
A transport is just the channel that carries bytes between Muxit and a device: serial, TCP, or USB (USB-TMC). Those three built-in transports cover almost every bench. What the bytes actually mean is the job of the next layer.
2 — Drivers & protocols
Three ways to reach a device.
Whatever sits between a transport and Muxit exposes the same uniform interface
(get, set, execute), so the core never
references a specific device. You pick one of three approaches to match your
situation, and the rest of the setup follows:
Driver: a turnkey integration
A driver owns its own connection and knows one device or family. You supply only the connection details (an IP, a port path, credentials) and it just works. Examples: the built-in Webcam, ONVIF, and MqttBridge, or marketplace drivers like the Fairino robot arm. Drivers are commodities: one works for any instance of that device.
Protocol: declarative, you describe the device
A protocol is a reusable wire-format parser. It doesn't know your specific device;
you point it at a transport with a connection: block and describe what
the bytes mean in a short schema: block. No driver code. Muxit ships
three, and the AI can draft any of them from a manual or a few captured lines:
- Scpi for SCPI instruments: scopes, power supplies, multimeters, SMUs, signal generators, RF gear. Rides on TCP, serial, or USB-TMC.
- LineText for anything that prints text lines you could read in a serial terminal: Arduino sketches, hobby PSUs, dataloggers. Match each line by prefix or regex to a typed property.
- BinaryStream for fixed-format binary frames: Modbus-RTU sensors, custom embedded devices. Declare the framing and the byte offsets where your fields live.
// workspace/connectors/psu.js: a protocol, no driver code at all export default { protocol: "Scpi", connection: { type: "tcp", host: "192.168.1.50", port: 5025 }, config: { schema: { properties: { voltage: { cmd: "VOLT", type: "float", unit: "V" }, current: { cmd: "CURR", type: "float", unit: "A" }, output: { cmd: "OUTP", type: "bool" }, }, }, }, };
Engine: compute over another connector
An engine has no wire of its own. It reads from the output of another connector
and emits derived data, for example the Vision engine running object detection on
a webcam's frames. You give it a source: instead of a connection.
When no driver and no protocol fits: write a custom driver
Got a device with a quirky wire format, a stateful handshake, or a vendor SDK? Write a small custom driver. It's the same uniform interface underneath, in whichever form suits the device:
JavaScript (.driver.js)
For custom protocols, ESP32 firmware, vendor-specific serial formats. Plain
JavaScript with the transport factories (createSerialTransport,
createTcpTransport). Twenty lines is often enough.
// simple-psu.driver.js export default { meta: { name: "SimplePSU", properties: { voltage: { type: "number", access: "rw", unit: "V" }, current: { type: "number", access: "r", unit: "A" }, }, actions: { reset: { description: "Reset to defaults" } }, }, async init(config) { this.transport = createSerialTransport(config.port, { baudRate: 9600 }); }, async get(prop) { return await this.transport.query(`${prop}?`); }, async set(prop, val) { await this.transport.send(`${prop} ${val}`); }, async execute() { await this.transport.send("*RST"); }, };
Python (.py)
For anything that already has a Python library: vendor SDKs, numpy / scipy
analysis, ML models, instrument toolkits. Drop a plain .py file into
workspace/python/ and a connector calls its top-level functions; no
class, no boilerplate. A sibling requirements.txt auto-installs into a
per-script virtual environment on first activation, so torch / transformers /
pyserial just work. Each Python connector runs as an isolated subprocess, so a
misbehaving library can't take the server down.
Compiled plugin (.dll)
For vendors that ship closed-source C/C++ or .NET SDKs. We wrap these as C# DLL plugins, built and signed by the Muxit team. Full hardware integration with no licensing or distribution fuss for the end user.
Driver, protocol, or engine, they all present the same interface to Muxit. Scripts don't know or care which kind is underneath.
3 — Connectors
The driver, configured for your specific device.
A connector wraps a driver with your configuration, custom methods, and computed properties — giving each device a friendly name and a tailored API.
// workspace/connectors/psu.js export default { driver: "MockInstrument", config: { defaultVoltage: 0, defaultCurrent: 1.0 }, poll: ["power"], methods: { rampTo: [async (c, target) => { const current = await c.voltage(); const step = (target - current) / 10; for (let i = 1; i <= 10; i++) await c.voltage(current + step * i); return await c.voltage(); }, "Ramp voltage to target in 10 steps"], }, properties: { power: [async (c) => { const v = await c.measured_voltage(); const i = await c.measured_current(); return Math.round(v * i * 1000) / 1000; }, "Calculated power (W)"], }, };
The split is deliberate: drivers are commodities, connectors are personal. A driver works for any instance of a device; a connector is your specific configuration of it — your voltage limits, your safety methods, your computed power readout. You write connectors once and they live in your workspace folder, version-controlled with the rest of your code.
The internal slogan: you write connectors, the community builds drivers.
4 — Your code
Scripts, dashboards, AI — all the same API.
In a script, properties read like properties and write like assignments.
No await, no callbacks, no boilerplate.
const psu = connector("psu"); psu.voltage = 12; log.info(`Current draw: ${psu.measured_current}A`);
The same psu object is bound to dashboard widgets, exposed to the
AI as tool calls, and accessible to external clients over the WebSocket API.
One model, four ways to use it.
When you plug in something new
New instrument? The AI drafts the driver.
Driver coverage is the single biggest reason hobbyist and indie lab automation tools fail to adopt. Muxit turns that on its head: when you plug in a new device, the AI can draft a working connector or driver for you in minutes.
- Discover the device.
The AI sweeps baud rates, terminators, and flow-control modes, or lists USB
devices, and identifies what's there. SCPI instrument, text-line device, or
binary protocol? It picks the right protocol family and verifies identity
(
*IDN?) before drafting anything. - Reverse-engineer the unknowns.
For cheap meters and dataloggers that don't speak SCPI, the AI sweeps trigger
bytes to find which one makes the device respond, then brute-force-decodes the
byte layout, every plausible
(offset, width, type, endian, scale)combination plus ASCII and BCD. You typically get a paste-ready field map in one round-trip. - Draft, review, approve.
Muxit writes a JavaScript connector with a declarative
schemablock (SCPI, LineText, or BinaryStream as appropriate), hot-reloads it, and smoke-tests one property. Write gates are on by default, and every line is plain JavaScript you approve before it runs.
That binary-discovery path is what makes the no-manual case work. For the full device-by-device story, including the SCPI end-to-end flow and where the AI still needs a hand, see the compatibility guide →
The app
One window, three panels.
Muxit runs as a native background service and serves its UI over a local web server. Open it in any browser — or let it run headless on a lab PC and connect from your laptop across the network.
Scripts, drivers, and device communications all run inside the Muxit service, sandboxed. Your automation keeps running even when the browser is closed.
Hardware panel (left).
Every connector with its live properties. Read values update in real time; writable fields are editable inline. Drag any property, action, or stream onto a script or dashboard — Muxit inserts the right code or widget for you. No API lookup needed.
Scripts and dashboards (centre).
The main workspace holds both. Switch between code and drag-and-drop control panels in the same tab. Autocomplete knows every connected device.
AI chat (right).
Ask questions about your hardware or have the AI drive it directly. Tool calls are visible and gated — you stay in control.
Dashboards
JSON files, drag-and-drop widgets.
Dashboards are JSON files. Drag widgets onto a grid, bind them to device properties, and they update automatically.
Available widgets: gauge, chart, scope, slider, knob, button, toggle, indicator, terminal, video stream.
Drag a property from the hardware panel onto a dashboard to create a widget. Drag a script onto the grid for a start/stop control. Because everything is JSON, dashboards go in version control with the rest of your code.
How AI plugs in
Three ways, all on the same connector API.
Muxit's AI integration sits on top of the same connector API everything else uses. There are three ways to bring AI into the picture, and they coexist:
Built-in AI chat and voice.
Muxit ships with a chat panel and voice input. Routed through the Muxit AI proxy on paid tiers (no API keys to manage), or to a local LLM on Pro and above (Ollama, LM Studio).
MCP server (free on every tier).
Native Model Context Protocol integration. Bring your own AI client — Claude Desktop, Claude Code, ChatGPT, any MCP-aware tool — and it connects directly to your bench. No Muxit AI credits required.
The ai() function in scripts.
Call AI from inside automation scripts. Image inputs work — pass a webcam frame and ask a question about what's visible. Returns a string you can branch on.
const cam = connector("webcam"); if (ai("Is the LED still on?", cam.snapshot) === "no") { psu.output = false; }
In all three paths, the AI sees the same connector API your scripts see. Tool calls are visible and gated; write operations require explicit approval by default.
Safety, sandboxing, and what stays local
How Muxit treats your code, your data, your hardware.
Scripts run sandboxed.
Muxit uses an embedded V8 engine (ClearScript) — not Node.js. Scripts can't touch the filesystem, can't open network connections, can't spawn processes. They can only call connectors you've explicitly defined.
Connectors run in the same sandbox.
Custom connector methods are plain JavaScript with allowlisted globals:
connector(), log, console,
emit(), delay() (capped at 30 seconds), and the
transport factories (createSerialTransport,
createTcpTransport). No filesystem, no shell, no surprise
network calls.
Plugins are RSA-signed.
Compiled drivers (.dll plugins) are RSA-signed and verified at
load time. You can see who built every component before it runs.
Local-first by default.
The Muxit server runs on your machine. Your scripts, connectors, dashboards, and device communications stay on your machine. Nothing is uploaded unless you explicitly use the AI or voice features through the Muxit proxy — and even then, only the prompts and tool calls relevant to that AI call leave your network. Bring your own MCP client or run a local LLM and the entire AI flow stays inside your network.
Plain text end to end.
Connectors and scripts are readable JavaScript files in a folder you can put under version control. Diff them, review them, commit them. Drivers in JavaScript and Python, and declarative protocol configs, are also readable; only compiled plugins are opaque, and those are signed.
What's where on disk
For the curious or the cautious.
-
workspace/connectors/your connector configs (JavaScript files, one per device). -
workspace/scripts/your automation scripts (JavaScript files). -
workspace/dashboards/your dashboards (JSON files). -
workspace/drivers/community JavaScript and Python drivers, plus declarative protocol configs. Premium plugins live in a separate signed-plugin folder. -
workspace/python/your Python scripts that connectors expose as devices. A siblingrequirements.txtauto-installs into a per-script virtual environment on first use. -
workspace/config/server configuration, including license cache (HMAC-signed) and TLS settings.
The whole workspace/ directory is yours. Point it at a shared drive
(Dropbox, OneDrive, NAS) and multiple Muxit installations can read the same
connectors, scripts, and dashboards.
Out of scope
What Muxit isn't.
A few things Muxit is not trying to be, in case the framing matters:
Not a microcontroller IDE.
Muxit doesn't replace the Arduino IDE, PlatformIO, or ESP-IDF. It sits one layer above — once your microcontroller is running and speaking MQTT or serial, Muxit talks to it.
Not a CAD or simulation tool.
Muxit drives real hardware in real time. It doesn't simulate circuits, model physics, or design layouts.
Not a LIMS or ELN.
Muxit is for instrument control and automation, not for managing samples, users, batches, or regulatory document workflows. It can produce data for those systems, but it's not one.
Not a SaaS.
The Muxit server runs on your machine. There's a small cloud component for licensing, the AI proxy, and the extension marketplace — everything else is local.
See it work