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, and a decision guide for picking the right one.

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 and Linux claim 2 ports per endpoint (one for each direction) so the numbering is 1,3,5,7


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

This file documents the numeric IDs and common names for all key nodes on the Jumperless V5, based on modules/jumperless/modjumperless.c. Use it 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:

  • 130 — upper half
  • 3160 — 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

DACs and ADCs

Special nodes and supplies

Name Aliases ID Notes
GND GROUND 100 Global ground net
TOP_RAIL T_R, TOP_R 101 Adjustable top rail
BOTTOM_RAIL BOT_RAIL, B_R 102 Adjustable bottom rail
DAC0 DAC_0 106 DAC channel 0 *
DAC1 DAC_1 107 DAC channel 1

[!CAUTION] * Avoid using DAC 0 and ROUTABLE_BUFFER_IN because they're used for probe switch sensing

Current sense

Name Aliases ID
ISENSE_PLUS ISENSE_POS, ISENSE_P, INA_P, I_P, CURRENT_SENSE_PLUS, I_POS 108
ISENSE_MINUS ISENSE_NEG, ISENSE_N, INA_N, I_N, CURRENT_SENSE_MINUS, I_NEG 109

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

ADC nodes

Name Aliases ID Notes
ADC0 ADC_0, ADC0_8V 110 ADC channel 0 (±8 V)
ADC1 ADC_1, ADC1_8V 111 ADC channel 1 (±8 V)
ADC2 ADC_2, ADC2_8V 112 ADC channel 2 (±8 V)
ADC3 ADC_3, ADC3_8V 113 ADC channel 3 (±8 V)
ADC4 ADC_4, ADC4_5V 114 ADC channel 4 (5 V range)
ADC7 ADC_7, ADC7_PROBE, PROBE 115 Probe ADC channel

User GPIO pins

These are the main user‑accessible GPIOs.

Name Aliases ID RP2350B Pin Notes
GPIO_1 RP_GPIO_1, GPIO1, GP_1, GP1 131 20 I'm sorry they're 1-indexed like this
GPIO_2 RP_GPIO_2, GPIO2, GP_2, GP2 132 21
GPIO_3 RP_GPIO_3, GPIO3, GP_3, GP3 133 22
GPIO_4 RP_GPIO_4, GPIO4, GP_4, GP4 134 23
GPIO_5 RP_GPIO_5, GPIO5, GP_5, GP5 135 24
GPIO_6 RP_GPIO_6, GPIO6, GP_6, GP6 136 25
GPIO_7 RP_GPIO_7, GPIO7, GP_7, GP7 137 25
GPIO_8 RP_GPIO_8, GPIO8, GP_8, GP8 138 27
UART_TX RP_UART_TX, TX 116 0 2nd serial port passthrough
UART_RX RP_UART_RX, RX 117 1

When working at the MicroPython level you will usually use GPIO_1GPIO_8 and the higher‑level GPIO APIs.


Buffer nodes

These correspond to a routable buffer block:

Name Aliases ID
ROUTABLE_BUFFER_IN BUFFER_IN, BUF_IN, BUFF_IN, BUFFIN 139
ROUTABLE_BUFFER_OUT BUFFER_OUT, BUF_OUT, BUFF_OUT, BUFFOUT 140

[!CAUTION] * Avoid using DAC 0 and ROUTABLE_BUFFER_IN because they're used for probe switch sensing


Arduino Nano header

The Nano header pins are mapped as follows:

Name Aliases ID Routable? Notes
NANO_VIN VIN 69 N
NANO_D0 D0 70 Y
NANO_D1 D1 71 Y
NANO_D2 D2 72 Y
NANO_D3 D3 73 Y
NANO_D4 D4 74 Y
NANO_D5 D5 75 Y
NANO_D6 D6 76 Y
NANO_D7 D7 77 Y
NANO_D9 D9 79 Y
NANO_D10 D10 80 Y
NANO_D11 D11 81 Y
NANO_D12 D12 82 Y
NANO_D13 D13 83 Y
NANO_AREF AREF 85 Y
NANO_A0 A0 86 Y
NANO_A1 A1 87 Y
NANO_A2 A2 88 Y
NANO_A3 A3 89 Y
NANO_A4 A4 90 Y
NANO_A5 A5 91 Y
NANO_A6 A6 92 Y
NANO_A7 A7 93 Y
NANO_RESET_0 RST0 94 N Wired directly to RP2350B pin 18
NANO_RESET_1 RST1 95 N PSRAM Mod kits use this as CS (pin 19)
NANO_GND_1 N_GND1 96 N
NANO_GND_0 N_GND0 97 N
NANO_3V3 98 N
NANO_5V 99 N

These nodes let you route signals between the RP2350, the breadboard, and any compatible Nano‑footprint dev board (classic Nano, Nano ESP32, Nano RP2040 Connect, etc.).


Rail pads

Special nodes representing where the probe can tap the rails:

Name Aliases ID
TOP_RAIL TOP_RAIL_PAD 101
BOTTOM_RAIL BOTTOM_RAIL_PAD 102
TOP_RAIL_GND TOP_GND_PAD 104
BOTTOM_RAIL_GND BOT_RAIL_GND, BOTTOM_GND_PAD 126

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 130 — upper half, between the top rails and the center gap
    • Rows 3160 — 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, clear_first=True, from_wokwi=False) applies such a state.

[!NOTE] The entire immediate state of the board is defined in this JSON (or YAML) slot file and can be shared to another Jumperless, saved as a backups, included in tutorials, etc.

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 is equivalent to calling dac_set() on the corresponding channels.


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 reference/node-map.md.
  • special — optional annotation for special nets; typical values:
  • "none" — normal net
  • "RAIL" — power rail (top or bottom)
  • "DAC" — DAC output net
  • "UART_TX" / "UART_RX" — UART nets
  • Other firmware‑defined markers
  • voltage — optional; expected or configured voltage on this net (when known)
  • Other fields (like color, anim, etc.) may be present and should be preserved.

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 18 for GPIO_1GPIO_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 5×60 canvas (rows 1–10, columns 1–30).

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, clear_first=True) when applying a complete, authoritative state.
  14. If you are only adding overlays or adjusting power but want to preserve dynamic routing changes, you may choose clear_first=False (if supported and appropriate) or modify only overlays via MicroPython functions.

  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, clear_first=True, from_wokwi=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), get_current_slot()
State get_state(), set_state(json, clear_first=True)
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: 160
  • Rails / supplies: TOP_RAIL, BOTTOM_RAIL, GND, DAC0, DAC1
  • Arduino: D0D13, A0A7, AREF, RESET
  • GPIO: GPIO_1GPIO_8, UART_TX, UART_RX
  • Measurement: ADC0ADC3, 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 via a scratch slot. Before destructive ops like nodes_clear(), save to an unused slot (e.g. nodes_save(7)); restore with switch_slot(7).


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
Slot backup for undo Implemented (nodes_save(7) before destructive ops)
USBSer3 :help self-describing backchannel Implemented (firmware)