Automation & LLM Tools

The Jumperless V5 is built to be driven by software — by your own scripts, by desktop apps, and by LLM agents (Cursor, Claude Desktop/Code, etc.). This page is the hub for every way to control the board from outside the menu.

The full hardware/API surface is documented in the MicroPython API Reference — this page is about transport and tooling, not the individual calls.


USB port structure

The Jumperless enumerates as four USB CDC serial interfaces. On macOS they are named JLV5portN; on Linux they are sequential /dev/ttyACMx; on Windows they are sequential COMx.

Port macOS suffix Role
1 JLV5port1 Main terminal, menu, > one-liner Python, single-char commands
2 JLV5port3 Arduino UART passthrough
3 JLV5port5 MicroPython Raw REPL — primary transport for scripts and the Agent Skill
4 JLV5port7 USBSer3 machine backchannel — :help (YAML), :cmds, read-only status verbs

Tip

The host helper scripts/jumperless.py detect (in the Agent Skill repo) auto-finds the Raw REPL port and caches it, so you rarely need to name ports by hand.

Note

I will refer to these ports by their index (1st, 2nd, 3rd, 4th). macOS names them by USB interface index, and each serial port uses two interfaces (control and data), so the suffixes step by two: 1, 3, 5, 7. Linux and Windows number them sequentially.


Communication methods

1. > Python one-liners (1st / main port)

Prefix a single line of Python with > on the main terminal. Best for quick checks and single commands.

> connect(1, 5)
> v = adc_get(0)
> print(f"V = {v:.2f}")

2. MicroPython Raw REPL (3rd port)

The canonical transport for anything beyond a one-liner: full scripts, loops, guided flows. This is what the Agent Skill's jumperless.py and the MCP server both speak under the hood.

import time
for i in range(10):
    print(i, adc_get(0))
    time.sleep(0.5)

3. Arduino tags (2nd port, via UART)

From an Arduino connected to the Jumperless headers:

Serial.print("<p>connect(1, 5)</p>");   // run Python
Serial.print("<j>n</j>");                // menu command

4. Single-character commands (1st port)

Raw characters sent to port 1 trigger immediate menu actions — fast state dumps, board clears, etc. (J = JSON state, x = clear, n = list nets, …).

5. USBSer3 backchannel (4th port) — read-only telemetry

A dedicated machine port that never blocks and never changes state. It speaks two command forms:

  • Single characters dispatch instantly: A full status, V ADC+current, G GPIO, N nets, K YAML.
  • : verbs read a newline-terminated line: :gpio:s, :leds, :crossbar, :adc, :yaml, :every:1ms:100:gpio:s, …

Every command is gated by an access tag; only read-only queries run here, so an agent can poll the board freely without risk of mutating it.

Self-describing :help (YAML)

The backchannel describes itself. Send :help (or a bare :) and it emits a YAML document — readable straight in a serial terminal, and parseable with yaml.safe_load():

---YAML_HELP_START---
ser3_help: 1
protocol:
  single_char: "Instant dispatch; no line collection. Multi-char modifiers use ':' verbs."
  ...
verbs:
  - name: "gpio:s"
    summary: "48-pin GPIO state snapshot"
    args: "[float]"
    output: "s{<48 0/1[/f]>}"
    example: ":gpio:s"
    min_us: 20
  ...
---YAML_HELP_END---

Targeted help is available too:

Command Returns
:help Full YAML help document
:help:<verb> One verb's details (e.g. :help:gpio:s)
:help:<char> A single-char command's long help (e.g. :help:c)
:help:json The same content in JSON
:cmds / :cmds_all Machine command list (JSON) — :cmds_all includes blocked commands and their access tags

A host harness for the backchannel ships with the firmware repo at scripts/bench_backchannel.py (drives :bench, times verbs).


MCP server

Jumperless-mcp by LesbianVelociraptor

For MCP-native clients (Claude Desktop, Cursor MCP, …), the Jumperless exposes a Model Context Protocol server: a Rust MCP front-end plus a resident Python library on the device. It presents the board's capabilities as typed tools (connections, power, measurement, GPIO/PWM, OLED, overlays, state, …).

It lives as the mcp/ git submodule inside the canonical automation repo, so one checkout gives you both the Agent Skill and the MCP server:

git clone --recurse-submodules https://github.com/Architeuthis-Flux/Large-Breadboard-Model.git
cd Large-Breadboard-Model/mcp
cargo install --path .        # or use a release binary

Point your MCP client at the built binary:

{
  "mcpServers": {
    "jumperless": {
      "command": "/path/to/Large-Breadboard-Model/mcp/target/release/jumperless-mcp"
    }
  }
}

Exclusive port: the MCP server holds the USB serial port while running. Don't run jumperless.py (the Agent Skill transport) against the same board at the same time.


Agent Skill

If your agent supports the Agent Skills standard (Cursor, Claude Code), install the Jumperless V5 skill. It teaches the agent the device's capabilities and drives it through scripts/jumperless.py over the Raw REPL, with progressive-disclosure reference docs loaded only as needed.

mkdir -p ~/.cursor/skills          # or ~/.claude/skills
cd ~/.cursor/skills
git clone --recurse-submodules \
  https://github.com/Architeuthis-Flux/Large-Breadboard-Model.git jumperless-v5

Node Map

Use this to translate between human‑readable names (like D13 or TOP_RAIL) and the internal node numbers.

All constants are available in the MicroPython REPL; you normally do not need the numbers directly, but the mapping is useful for mental models and debugging.

Breadboard rows (1–60)

Rows 1–60 represent the breadboard rows along the main area:

  • 1–30 — upper half
  • 31–60 — lower half

Each row corresponds to a vertical strip of 5 holes on the physical board.

Example:

connect(1, 10)   # connect top row 1 to top row 10
connect(35, GND) # connect lower half row 35 to ground

Node names and IDs

Every node name, alias and ID lives in Node Names and Constants in the MicroPython API Reference.

ISENSE_PLUS and ISENSE_MINUS are the two ends of a 2 Ω shunt resistor and are internally shorted through that shunt; always use them in series with the circuit being measured.

Caution

Avoid using DAC 0 and ROUTABLE_BUFFER_IN because they're used for probe switch sensing


Breadboard layout (high‑level)

The board follows a standard half‑size breadboard geometry:

  • Nano header at the top:

    • Top row: D12 D11 D10 D9 D8 D7 D6 D5 D4 D3 D2 GND RST1 D0 D1
    • Second row: D13 3V3 REF A0 A1 A2 A3 A4 A5 A6 A7 5V RST0 GND VIN
  • Top power rail:

    • TOP_RAIL
    • GND
  • Breadboard rows:

    • Rows 1–30 — upper half, between the top rails and the center gap
    • Rows 31–60 — lower half, between the center gap and the bottom rails
  • Bottom power rail:

    • BOTTOM_RAIL
    • GND

An ASCII diagram (from the docs) approximates the layout; the agent should treat rows and rails as in any typical solderless breadboard, with the addition that every hole has an individually controllable RGB LED.

DIP ICs straddling the center gap

A DIP chip (555, op-amp, logic, …) sits across the center gap, with each side in a different breadboard half. The two halves are independent rows, so the two sides of the chip map to numerically distant nodes. For a chip whose first pin sits at upper-half row N, the upper-side pins run N, N+1, … and the lower-side pins run from the row directly across the gap (N+30).

Worked example — an 8-pin DIP (e.g. a 555) with pin 1 at row 1:

Pin Node Pin Node
1 1 8 31
2 2 7 32
3 3 6 33
4 4 5 34

Pins 1–4 are on the upper side (rows 1–4); pins 5–8 are across the gap on the lower side (rows 34→31, counting back toward pin 1). Adjust the base row to wherever the chip is actually placed; verify with the probe or a measurement when unsure.


How to use this map

  • Prefer named constants like D13, TOP_RAIL, GPIO_1, ADC0, ISENSE_PLUS when generating code.
  • Use the numeric IDs only when inspecting low‑level debugging output or when matching numeric IDs in get_state() / get_net_info() results.
  • When the user describes physical positions (“top rail”, “bottom left rail”, “row 25”), translate them into these node names/IDs before constructing connect(...) commands.

JSON State Format

The function get_state() returns a JSON string describing the entire board state: power rails, nets, GPIOs, and overlays. set_state(json_str) applies such a state. Two optional flags follow it, and they have to be positional — set_state(json_str, clear_first, from_wokwi), where clear_first defaults to true and from_wokwi to false. Keyword arguments raise a TypeError.

Note

The entire immediate state of the board is defined in this JSON and can be shared to another Jumperless, saved as a backup, included in tutorials, etc. Slots themselves are stored as YAML, which is what K and :yaml dump.

This file explains the structure so agents can safely inspect, modify, and re‑apply state.


Top-level structure

A typical get_state() JSON (pretty‑printed) looks like:

{
  "power": {
    "top_rail": 3.8,
    "bottom_rail": 3.8,
    "dac0": 3.25,
    "dac1": 0.0
  },
  "nets": [
    {
      "index": 1,
      "name": "GND",
      "nodes": ["GND", "21"],
      "special": "none"
    },
    {
      "index": 2,
      "name": "Top Rail",
      "nodes": ["TOP_R", "23"],
      "special": "RAIL",
      "voltage": 3.8
    }
    // ...
  ],
  "gpio": [
    {
      "pin": 1,
      "net": null,
      "function": "SIO",
      "direction": "INPUT",
      "pull": "down",
      "reading": "unknown",
      "floating_read": 1
    },
    {
      "pin": "TX",
      "net": 7,
      "function": "UART",
      "direction": "OUTPUT",
      "pull": "down",
      "reading": "unknown",
      "floating_read": 0
    }
  ],
  "overlays": [
    {
      "name": "border",
      "row": 1,
      "col": 1,
      "width": 30,
      "height": 10,
      "colors": ["003333", "003333", "..."]
    }
  ]
}

There may be additional keys in future firmware versions; agents should not assume the set of keys is fixed and should preserve unknown keys when round‑tripping.


power section

"power": {
  "top_rail": 3.8,
  "bottom_rail": 3.8,
  "dac0": 3.25,
  "dac1": 0.0
}

Fields:

  • top_rail — voltage (float) configured for TOP_RAIL
  • bottom_rail — voltage configured for BOTTOM_RAIL
  • dac0, dac1 — DAC channel setpoints

When using set_state, changing these values drives the corresponding channels and is saved into the current slot. Any value outside -8 V to 8 V is rejected.


nets section

Each item describes a net (set of nodes that are electrically connected inside the crossbar).

Example:

{
  "index": 4,
  "name": "DAC 0",
  "nodes": ["DAC_0", "BUF_IN"],
  "special": "DAC",
  "voltage": 3.25
}

Fields:

  • index — numeric net index
  • name — human‑readable net name (may be auto‑generated or user‑defined)
  • nodes — array of node names (strings), such as "GND", "TOP_R", "21", "D13", etc.
  • These names correspond to the mapping in Node Names and Constants.
  • special — annotation for special nets; the values are "none", "RAIL", "DAC", "ADC", "GPIO", "UART_TX", "UART_RX" and "FAKE_GPIO_IN"
  • voltage — present on ADC, DAC, RAIL and FAKE_GPIO_IN nets
  • logic — present on GPIO, UART_TX and UART_RX nets; the logic level (0 or 1)

Important: nets describes logical groupings of nodes. It is not a direct list of bridges; bridges are derived from the crossbar configuration internally.


gpio section

Each entry describes one logical GPIO:

{
  "pin": 1,
  "net": null,
  "function": "SIO",
  "direction": "INPUT",
  "pull": "down",
  "reading": "unknown",
  "floating_read": 1
}

Fields:

  • pin — either:
  • An integer 1–8 for GPIO_1–GPIO_8
  • "TX" or "RX" for the UART pins
  • net — net index this pin is connected to (or null if unconnected)
  • function — textual description of function:
  • "SIO" — standard GPIO
  • "UART" — UART TX/RX
  • Other firmware‑defined roles
  • direction — "INPUT" or "OUTPUT"
  • pull — "up", "down", or "none"
  • reading — textual summary of the last known logic state ("high", "low", "unknown", etc.)
  • floating_read — boolean (0/1) indicating whether floating detection is enabled

Agents can inspect this section to understand logical GPIO usage, but should generally change GPIO configuration via the MicroPython APIs (gpio_set_dir, gpio_set_pull, etc.) rather than editing this JSON directly.


overlays section

Overlays describe additional graphics drawn on the breadboard RGB LEDs. They are rendered on top of the firmware’s net coloring.

Example:

{
  "name": "border",
  "row": 1,
  "col": 1,
  "width": 30,
  "height": 10,
  "colors": [
    "003333", "003333", "...",
    "003333", "000000", "...",
    "... more hex color strings ..."
  ]
}

Fields:

  • name — overlay name (string)
  • row — top row (1–10)
  • col — left column (1–30)
  • width — width in columns
  • height — height in rows
  • colors — flat array of hex color strings without 0x prefix, e.g. "003333", "FF2020"
  • Length = width * height
  • Order is row‑major: top row left→right, then next row down, etc.
  • "000000" means “transparent” — do not override underlying LED color

Brightness recommendation:

  • Avoid full‑scale white FFFFFF.
  • Prefer saturated colors with reduced magnitude (~25%): e.g. 2020FF, 20FF20, FF2020, 404040.

When editing overlays via JSON:

  • Always maintain the correct width * height number of color entries.
  • Preserve unknown fields if present.
  • Ensure that row, col, width, and height keep the overlay within the canvas, which is 10 rows by 30 columns: row 1 is the top outer edge, rows 5 and 6 straddle the center gap, and row 10 is the bottom outer edge.

Often it is easier to use the MicroPython overlay APIs (overlay_set, overlay_clear, etc.) directly instead of editing overlays in the state JSON manually. The JSON is still useful for inspection and for bulk export/import of complex scenes.


Editing state safely

General guidelines:

  1. Fetch once, apply locally.
  2. Call get_state() and parse the JSON in the host environment.
  3. Keep this as your “baseline” snapshot.

  4. Work on a copy.

  5. Clone the baseline object before modifying it.
  6. Preserve unknown fields.

  7. Modify only what you care about.

  8. For power: update power.top_rail, power.dac0, etc.
  9. For overlays: add or replace specific entries in overlays.
  10. Avoid rewriting nets unless you fully understand the implications; it is often better to use connect()/disconnect() APIs to modify connectivity.

  11. Re‑serialize and apply.

  12. Use a standard JSON encoder.
  13. Call set_state(json_string, True) when applying a complete, authoritative state.
  14. If you are only adding overlays or adjusting power, send a JSON with just those sections. set_state accepts any subset of power, nets, gpio and overlays and only applies the sections that are there. clear_first matters only when a nets section is present — that is when it clears every existing connection first.

  15. Rollback on problems.

  16. If set_state leads to unexpected behavior, re‑apply the saved baseline snapshot.

Example: adjusting top rail voltage via state

Instead of:

dac_set(TOP_RAIL, 5.0)

You can do:

import json
state = json.loads(get_state())
state["power"]["top_rail"] = 5.0
set_state(json.dumps(state))

This approach is useful when batching multiple changes (e.g. updating both rail voltages and overlays) in a single atomic operation.


Example: augmenting overlays

Suppose get_state() returns a state with existing overlays, and you want to add a new “place‑here” marker at row 3, column 10:

import json

state = json.loads(get_state())

# Create a 1×1 overlay with a bright but not full‑scale green
place_here = {
    "name": "place_here_row3_col10",
    "row": 3,
    "col": 10,
    "width": 1,
    "height": 1,
    "colors": ["20FF20"]
}

state.setdefault("overlays", [])
state["overlays"] = [
    o for o in state["overlays"] if o.get("name") != place_here["name"]
] + [place_here]

set_state(json.dumps(state))

This pattern:

  • Preserves existing overlays.
  • Replaces any prior overlay with the same name.
  • Leaves other state fields untouched.

Wokwi import (from_wokwi=True)

When from_wokwi=True, set_state() expects json_str to be a Wokwi diagram.json rather than a Jumperless state JSON. The firmware converts the Wokwi representation into bridges, nets, and colors.

Agents can:

  • Ask the user to paste a Wokwi JSON.
  • Pass it directly to set_state(wokwi_json, True, True).

After that, a subsequent get_state() will return the standard Jumperless state JSON.


Robustness tips for agents

  • Treat get_state() as authoritative for the current internal configuration, but always cross‑check against the user’s description of external wiring and components.
  • Always preserve unknown keys when editing state; future firmware versions may add fields.
  • Prefer using high‑level MicroPython APIs (connect, disconnect, dac_set, overlay_set, etc.) for incremental changes, and use state JSON editing for bulk or offline transformations (import/export, heavy refactoring, or pre‑computed overlays).

MicroPython tool reference (subset)

The complete, authoritative list is the MicroPython API Reference. The most common calls for automation:

Category Key tools
Connections connect(), disconnect(), nodes_clear(), is_connected(), fast_connect()
Slots nodes_save(slot), switch_slot(slot), CURRENT_SLOT()
State get_state(), set_state(json)
Voltage dac_set(ch, v), dac_get(ch)
Measurement adc_get(ch), ina_get_current(s), ina_get_voltage(s), ina_get_power(s)
GPIO / PWM gpio_set(), gpio_get(), gpio_set_dir(), gpio_set_pull(), pwm(), pwm_stop()
WaveGen wavegen_set_wave(), wavegen_set_freq(), wavegen_set_amplitude(), wavegen_start()
UI oled_print(), oled_clear(), probe_read_blocking(), probe_button(), clickwheel_get_button()
LED overlays overlay_set(), overlay_set_pixel(), overlay_clear(), overlay_clear_all()
Net inspection get_net_info(), get_num_nets(), print_nets(), print_bridges()

Node addressing (quick form)

  • Breadboard rows: 1–60
  • Rails / supplies: TOP_RAIL, BOTTOM_RAIL, GND, DAC0, DAC1
  • Arduino: D0–D13, A0–A7, AREF, RESET
  • GPIO: GPIO_1–GPIO_8, UART_TX, UART_RX
  • Measurement: ADC0–ADC3, ISENSE_PLUS, ISENSE_MINUS

A mental model for LLM agents

The Jumperless prevents internal shorts, but cannot see external wires or components physically on the breadboard. An agent should maintain a model of what it believes is on the board and raise confidence through measurement and user confirmation.

A lightweight per-component record works well:

{
  "id": "comp_001",
  "type": "resistor",
  "value": 1000,
  "pins": [5, 10],
  "confidence": 0.95,
  "detection_method": "measured",
  "notes": "Measured 987Ω between rows 5-10"
}
Confidence Source Meaning
1.0 measured Electrically verified
0.9 user_confirmed User explicitly confirmed
0.7 user_stated User mentioned it casually
0.5 inferred Guessed from context
0.3 assumed Default assumption

Workflows

Safe power-up. Before energizing an unknown board, ask what's on it, build a low-confidence model, then verify key rows with an ADC before connecting power — a hidden user wire between two rows can turn TOP_RAIL + GND into a dead short.

Identify with the probe. Use probe_read_blocking() to let the user point at a node, then route an ADC to it and measure.

Characterize as a black box. Drive a node with the WaveGen / DAC and sweep while reading an ADC.

Batch edits via state. Fetch get_state(), mutate the JSON in Python, and re-apply with set_state() — one atomic round-trip instead of many calls.

Undo. The firmware keeps a history ring, so you don't need to burn a slot before destructive ops like nodes_clear(). Call undo() and redo() from MicroPython (^ and & in the terminal), or keep a get_state() snapshot to re‑apply.


Safety guidelines

  1. Voltage range. ADC0–ADC3 are buffered for ±8V; the board is ±9V tolerant overall.
  2. Short protection. The firmware refuses dangerous internal connections (e.g. TOP_RAIL directly to GND), but cannot see external wiring.
  3. Confirm power. The board is USB-powered (no barrel jack); ask if unsure.
  4. Crossbar resistance. ~ 20–40Ω per default connect() path (~ 80Ω with a single path). Measure voltage at the destination to compensate for drop.
  5. Settling time. Allow a few ms after routing/DAC changes (50–100 ms after nodes_clear() / set_state()) before measuring.

Implementation status

Feature Status
connect(), disconnect(), is_connected() Implemented
dac_set(), adc_get(), ina_*() Implemented
gpio_*(), pwm() Implemented
oled_print(), probe_*() Implemented
WaveGen tools Implemented
Slot management Implemented
get_state() / set_state() snapshot Implemented
adc_get_stats() (averaged sampling) Available via the MCP server
undo() / redo() history Implemented
USBSer3 :help self-describing backchannel Implemented (firmware)