LLM Tool Specification for Jumperless V5

A guide for LLMs with some tips to control the Jumperless V5.


Quick Reference

Category Key Tools
Connections connect(), disconnect(), nodes_clear(), save_slot(), load_slot()
Voltage dac_set(), adc_get()
Current ina_get_current(), ina_get_power()
GPIO gpio_set(), gpio_get(), gpio_set_dir(), pwm()
User Interaction oled_print(), probe_read_blocking(), probe_button()
Graphic Overlays overlay_set(), overlay_clear(), overlay_set_pixel()
State get_state(), set_state()

(Refer to the full Micropython API Reference)


Communication Methods

Method 1: Direct Python Commands (Main Serial Port)

Prefix single-line Python with > on the main serial port (Port 1). Best for: Single commands, status checks.

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

Method 2: ViperIDE / Raw REPL (3rd USB Port)

The third USB port provides a MicroPython Raw REPL. Best for: Complex logic, loops, automated testing scripts.

# Full scripts run on port 3
import time
for i in range(10):
    voltage = adc_get(0)
    print(f"Reading {i}: {voltage:.2f}V")
    time.sleep(0.5)

Method 3: Arduino Tags (via UART)

From an Arduino connected to the Jumperless. Best for: Hybrid Arduino/Python projects.

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

Method 4: Single-Character Commands (Main Serial Port)

Raw characters sent to Port 1 trigger immediate menu actions. Best for: Fast state dumps, clearing the board, or manual resets.

J  <-- Immediate JSON state dump
L  <-- Immediate JSON state load (paste JSON after L)
x  <-- Immediate board clear

USB Port Structure

macOS / Linux: | Port | Name | Function | |------|------|----------| | 1 (main) | JLV5port1 | Main terminal, menu, > Python commands | | 2 | JLV5port3 | Arduino UART passthrough | | 3 | JLV5port5 | MicroPython Raw REPL (ViperIDE) |

Windows: | Port | Name | Function | |------|------|----------| | 1 (main) | COM1 | Main terminal, menu, > Python commands | | 2 | COM2 | Arduino UART passthrough | | 3 | COM3 | MicroPython Raw REPL (ViperIDE) |


Hardware Overview

Physical Layout

  • 60 breadboard rows (1-60) with 5 RGB LEDs underneath each
  • Arduino Nano header with routable pins
  • OLED display (128x32, optional but recommended)
  • Probe with touch-sensing tip, 2 buttons, mode switch
  • Clickwheel rotary encoder with button
  • 12 CH446Q crossbar chips (A-L) for routing (~80Ω per path)

Power

  • TOP_RAIL / BOTTOM_RAIL: Main power rails (±8V, 300mA)
  • DAC0 / DAC1: Auxiliary voltage outputs (±8V, 300mA each)
  • DAC0 connects to Probe Tip & INA0
  • DAC0 and DAC1 are 0-3.3V native but amplified to ±8V
  • Current Limits: ~300mA per rail/DAC

Measurement

  • ADC0-3: 4 user analog inputs (±8V range)
  • INA0: High-side current monitor on DAC0 (Probe Tip)
  • INA1: High-side current monitor on TOP_RAIL (configurable)

GPIO

  • 10 GPIO pins (RP2350B, 3.3V logic) Defined as GPIO_1 - GPIO_8 (physical gpio 20-27 on RP2350B), and UART_TX (gpio 0 on RP2350B) and UART_RX (gpio 1 on RP2350B)
  • 5V Tolerant Inputs: Yes
  • PWM: Hardware PWM 0.1Hz-62.5MHz on all pins

Node Addressing

Breadboard Rows

1 through 60

Power Rails

Node Description
TOP_RAIL Top power rail (default 5V)
BOTTOM_RAIL Bottom rail (default GND)
GND Ground reference
DAC0 DAC0 (connected to probe tip and INA0)
DAC1 DAC1 (8V tolerant)

Arduino Pins

D0-D13, A0-A7, AREF, RESET

GPIO

GPIO_1-GPIO_8, UART_TX, UART_RX

ADC/Current Sense

ADC0-ADC3, ISENSE_PLUS, ISENSE_MINUS


Core Tool Definitions

Connections & Slots

connect(node1, node2, duplicates=-1)    # Create connection
disconnect(node1, node2)                 # Remove connection
nodes_clear()                            # Remove ALL connections
is_connected(node1, node2)               # Check if connected

# Slot Management
save_slot(slot_id)                       # Save current state to slot 0-7
load_slot(slot_id)                       # Load state from slot 0-7
get_current_slot()                       # Returns active slot number

# JSON State API (Recommended for LLMs)
get_state()                              # Get complete state as JSON string
set_state(json, clear_first=True)        # Apply state from JSON string

Voltage Control

dac_set(channel, voltage, save=True)   # Set voltage (-8V to +8V)
dac_get(channel)                       # Get current setting
# Channels: 0/DAC0, 1/DAC1, 2/TOP_RAIL, 3/BOTTOM_RAIL

Measurement

adc_get(channel)           # Read ADC voltage (channels 0-3)
ina_get_current(sensor)    # Read current in Amps (0=DAC0/Probe, 1=TOP_RAIL)
ina_get_voltage(sensor)    # Read INA bus voltage
ina_get_power(sensor)      # Read power in Watts

GPIO & PWM

gpio_set(pin, value)           # Set output (True=3.3V, False=0V)
gpio_get(pin)                  # Returns HIGH, LOW, or FLOATING
gpio_set_dir(pin, direction)   # True=OUTPUT, False=INPUT
gpio_set_pull(pin, pull)       # 1=PULLUP, -1=PULLDOWN, 0=NONE
pwm(pin, frequency, duty)      # Start PWM (duty: 0.0-1.0)
pwm_stop(pin)                  # Stop PWM

Waveform Generator (WaveGen)

# Setup
wavegen_set_output(channel)   # 0=DAC0, 1=DAC1 (Default)
wavegen_set_wave(type)        # 0=Sine, 1=Square, 2=Tri, 3=Saw
wavegen_set_freq(hz)          # Frequency in Hz
wavegen_set_amplitude(vpp)    # Peak-to-Peak Voltage (e.g. 3.3)
wavegen_set_offset(volts)     # DC Offset (e.g. 1.65)

# Control
wavegen_start(1)              # Start output
wavegen_stop()                # Stop output

User Interaction

oled_print(text, size=2)       # Display on OLED
oled_clear()                   # Clear display
probe_read_blocking()          # Wait for probe touch, return row
probe_read_nonblocking()       # Check without waiting (-1 if none)
probe_button()                 # Returns CONNECT, REMOVE, or NONE
clickwheel_get_direction()     # Returns UP, DOWN, or NONE
clickwheel_get_button()        # Returns PRESSED, HELD, RELEASED

Graphic Overlays (Breadboard LEDs)

The breadboard LEDs are addressed as a 10x30 grid (Row 1-10, Col 1-30). Rows 1-5 are top half (E-A), Rows 6-10 are bottom half (F-J).

# overlay_set(name, x, y, height, width, colors)
# Colors can be flat list or 2D list of 0xRRGGBB integers
overlay_set("box", 1, 1, 5, 5, [0x550000]*25)

overlay_clear("box")           # Remove overlay
overlay_clear_all()            # Remove all
overlay_set_pixel(x, y, color) # Set single pixel (1-30, 1-10)

System & Filesystem

# Standard Python I/O is supported!
with open('/config.txt', 'r') as f:
    print(f.read())

# List files
import os
os.listdir('/')
get_net_info(netNum)   # Get dict with name, color, nodes
get_num_nets()         # Count of active nets
get_num_bridges()      # Count of bridges
print_bridges()        # Print bridge table

Single-Character Command Reference (Port 1 Only)

These commands are processed immediately when sent as raw characters (no > prefix) to the main serial port.

Char Description Action
J Show JSON Dumps the complete board state as a JSON string.
L Load JSON Prepares the board to receive a JSON state. Paste JSON and end with an empty line.
x Clear All Removes all connections and resets paths.
n List Nets Prints a human-readable list of all active nets.
b Show Bridges Prints the internal bridge array.
~ Show Config Dumps the current config.txt settings.

| + | Add | Add connections (e.g., +1-5,10-12). | | - | Remove | Remove connections (e.g., -1-5). | | v | Read ADC | Follow with a channel (0-4) to get a quick voltage reading. |

| @ | I2C Scan | Scans for I2C devices on a row (e.g., @10). |

| r | Reset Arduino | Follow with t or b to reset the Top or Bottom Arduino. | | A | Connect Arduino UART | Connects Jumperless's UART to the Arduino D0 and D1 pins (a to disconnect). |

| m | Menu | Displays the help menu (e to show more options). |

| [command]? | Help | Displays the help menu for the specified command. | | help | Help Menu | Displays the help menu. |


LLM Mental Model File Format

LLMs should maintain a persistent JSON model of what they believe is physically on the breadboard. This model has confidence values that increase through user confirmation or automated testing.

Mental Model Schema

{
  "version": "1.0",
  "last_updated": "2026-02-06T22:00:00Z",
  "nano_header": {
    "device": "arduino_nano",  // "arduino_nano", "rp2040", "rpi_40pin_adapter", "oled_only", "empty"
    "confidence": 0.9,
    "notes": "User confirmed Arduino Nano Every"
  },
  "power_rails": {
    "TOP_RAIL": {"voltage": 5.0, "confidence": 1.0, "source": "measured"},
    "BOTTOM_RAIL": {"voltage": 0.0, "confidence": 1.0, "source": "measured"}
  },
  "components": [
    {
      "id": "comp_001",
      "type": "resistor",
      "value": 1000,
      "unit": "ohms",
      "tolerance": 0.05,
      "pins": [5, 10],
      "confidence": 0.95,
      "detection_method": "measured",
      "notes": "Measured 987Ω between rows 5-10"
    },
    {
      "id": "comp_002",
      "type": "led",
      "color": "red",
      "forward_voltage": 1.8,
      "pins": {"anode": 15, "cathode": 16},
      "confidence": 0.7,
      "detection_method": "user_stated",
      "notes": "User said 'red LED on rows 15-16'"
    },
    {
      "id": "comp_003",
      "type": "module",
      "name": "SSD1306 OLED",
      "pins": {
        "GND": 20, "VCC": 21, "SCL": 22, "SDA": 23
      },
      "confidence": 0.6,
      "detection_method": "inferred",
      "notes": "Searched pinout, user confirmed row 20"
    }
  ],
  "wires": [
    {"from": 1, "to": 30, "confidence": 0.8, "detection_method": "continuity_test"}
  ],
  "unknowns": [
    {"rows": [40, 41, 42], "notes": "Something detected but not identified"}
  ]
}

Nano Header Device Types

Device Description
arduino_nano Arduino Nano/Every/RP2040 etc.
rp2040 Bare RP2040 board
rpi_40pin_adapter RPi GPIO adapter board
oled_only SBC/SMD/OLED adapter for just the OLED
empty Nothing plugged in

Confidence Levels

Level 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 LLM guessed from context
0.3 assumed Default assumption

Increasing Confidence

Method Confidence Boost
User confirms "yes that's right" +0.2
Electrical measurement matches +0.3
Multiple consistent tests +0.1 per test
User provides datasheet Set to 0.95

Workflows for LLMs

1. Circuit Safe-Check (Mental Model)

CRITICAL: The Jumperless prevents internal shorts, but cannot see external wires or components on the breadboard. If a user has a physical wire connecting Row 5 to Row 10, connecting TOP_RAIL->5 and GND->10 will cause a dead short through the user's wire.

Workflow: 1. Ask: "What is currently on the breadboard? (Wires, components, etc)" 2. Model: Build the mental model JSON with low confidence 3. Test: Run detect_component() on key rows to verify 4. Update: Increase confidence as tests confirm the model

# Safe Power-Up Sequence
oled_print("Checking for shorts...")
# Find an unused ADC
unused_adc = None
for i in range(4):
    name = f"ADC{i}"
    is_used = False
    for net in get_all_nets():
        if name in net['nodes']:
            is_used = True
            break
    if not is_used:
        unused_adc = i
        break

if unused_adc is not None:
    connect(5, f"ADC{unused_adc}")
    connect(10, DAC1)
    # ...
model["wires"].append({"from": 5, "to": 10, "confidence": 0.95}) else: connect(5, TOP_RAIL) connect(10, GND)
### 2. Identifying User Circuits
Use the Probe to "see" the board.
```jython
oled_print("Touch the input of your circuit")
input_row = probe_read_blocking()
oled_print(f"Input is at row {input_row}")

3. Automated Characterization

Use WaveGen and ADC to treat the circuit as a black box.

connect(DAC1, 1)
wavegen_set_output(1)
wavegen_set_freq(100)
wavegen_set_wave(0)  # Sine
wavegen_start(1)

connect(ADC0, 10)
for i in range(5):
    val = adc_get(0)
    print(f"Sample {i}: {val}V")
    time.sleep(0.01)


Component Library (Dynamic Lookup)

When a user mentions a hardware module, search the web for its pinout and build a JSON definition on the fly.

Workflow: 1. User mentions: "I have an SSD1306 OLED on the breadboard" 2. LLM searches: "SSD1306 OLED pinout" 3. LLM builds JSON from search results and adds it to mental_model["components"]

Example: NeoPixel Stick

{
  "name": "NeoPixel Stick 8",
  "pins": {
    "GND": {"default": "GND", "offset": 0},
    "5V": {"default": "TOP_RAIL", "offset": 1},
    "DIN": {"default": "GPIO_1", "offset": 2},
    "DOUT": {"default": "NC", "offset": 7}
  },
  "width": 8,
  "voltage": "5V",
  "notes": "3.3V GPIO works for most NeoPixels. Data on offset 2."
}

Key: The offset field defines pin position relative to pin 1. When user says "pin 1 is on row X", calculate absolute rows as row = X + offset.


Safety Guidelines for LLMs

  1. Voltage Check: ADCs are buffered for ±8V. The board is ±9V tolerant overall.
  2. Short Circuit Prevention: The firmware will ignore requests to connect TOP_RAIL directly to BOTTOM_RAIL or GND.
  3. Confirm Power: Ask: "Is the board powered via USB?" (No barrel jack exists).
  4. Crossbar Resistance: Remember ~80Ω per connection. High current paths will have voltage drop. Measure voltage at the destination with an ADC to compensate.

LLM Preferences (Claude's Additions)

1. Structured State Snapshot

What I want: A single command that returns the complete board state as structured data (JSON/dict), not just printed text. This lets me reason about the state programmatically.

# REQUESTED: get_state() -> dict
# Returns something like:
{
  "slot": 0,
  "bridges": [[1, 5], [5, "TOP_RAIL"], [10, "GND"]],
  "rails": {"TOP_RAIL": 5.0, "BOTTOM_RAIL": 0.0, "DAC0": 3.3, "DAC1": 0.0},
  "gpio": [
    {"pin": 1, "dir": "OUTPUT", "value": True, "pull": "NONE"},
    {"pin": 2, "dir": "INPUT", "value": False, "pull": "PULLUP"}
  ],
  "adc_snapshot": [3.28, 0.01, 5.02, -0.03]  # Quick reading of all 4
}

Why: Currently I have to call get_num_bridges(), get_net_info() for each net, etc. A single snapshot is faster and less error-prone for building my mental model.

2. Return Values, Not Just Prints

For debugging, I prefer return values over print statements:

Instead of... I prefer...
print_nets() → prints to serial get_nets() → returns list of net dicts
print_bridges() → prints to serial get_bridges() → returns list of bridge tuples
print_paths_compact() → prints get_paths() → returns routing info

Why: When I call a tool, I want to capture the result and reason about it. Print output goes to the user's terminal but isn't easily parsed by my next step.

3. Error Return Conventions

Consistent error handling helps me recover:

# Good: Returns None or raises exception with message
result = connect(999, 5)  # Invalid node
# Returns: None (or {"error": "Invalid node: 999"})

# Good: Returns success/failure boolean with reason
success, msg = disconnect(1, 5)  
# Returns: (True, "Disconnected") or (False, "No such connection")

4. Undo via Slot Backup

For destructive operations like nodes_clear(), the existing slot system provides an undo mechanism:

# Before destructive operation, save current state to a backup slot
save_slot(7)           # Save to slot 7 as backup
nodes_clear()          # Now safe to clear

# If user wants to undo:
load_slot(7)           # Restore from backup

Pattern: Always save the current slot to an unused slot (7 is a good "scratch" slot) before any destructive action. This provides a built-in undo without needing special confirm flags.

5. Measurement with Context

When measuring, I often want multiple samples or statistics:

# REQUESTED: adc_get_stats(channel, samples=10)
# Returns: {"mean": 3.28, "min": 3.25, "max": 3.31, "stddev": 0.02}

Why: A single ADC reading might be noisy. Having built-in averaging/stats means I don't have to write loops for every measurement.

6. Interactive Conversation Patterns

When helping users debug, I find these patterns effective:

Explore First, Act Later:

User: "My LED isn't lighting up"
Me: 
  1. "Where is your LED connected? (Touch the anode with the probe)"
  2. [probe_read_blocking()  row 15]
  3. "I see row 15. Let me check the voltage there..."
  4. [connect(ADC0, 15), adc_get(0)  0.02V]
  5. "Row 15 is at 0V. Is it supposed to be connected to power?"

Show, Don't Just Do:

# Before making a connection, describe it:
oled_print("Connecting row 5 to 5V...")
connect(5, TOP_RAIL)
oled_print("Done! LED should light now")

Verify After Acting:

# After connecting power, verify it worked:
connect(5, TOP_RAIL)
connect(ADC0, 5)
v = adc_get(0)
if abs(v - 5.0) < 0.5:
    oled_print(f"✓ Row 5 at {v:.1f}V")
else:
    oled_print(f"⚠ Expected 5V, got {v:.1f}V")
disconnect(ADC0, 5)


LLM Best Practices & Future Tools

This section consolidates recommendations for reliable, high-context hardware interaction.

1. Explicit State Verification

Trust but verify. Confirm hardware state after critical operations.

# Goal: Set DAC0 to 3.3V
current = dac_get(0)
if abs(current - 3.3) > 0.1:
    dac_set(0, 3.3)
    time.sleep(0.01) # Allow settling
    new_val = dac_get(0)
    if abs(new_val - 3.3) > 0.1:
        print(f"Error: DAC0 failed to set. Got {new_val}V")

2. Structured State Snapshot

A single command to return the complete board state as a formatted JSON string, enabling detailed programmatic reasoning and full state management.

# Get the complete current state as a formatted JSON string
snapshot = get_state()

# The snapshot includes:
# - power: Settings for TOP_RAIL, BOTTOM_RAIL, DAC0, DAC1
# - nets: All active connections, names, colors, and voltage assignments
# - gpio: Current configuration and state of all GPIO pins

# Apply a state back to the hardware
# set_state(json_string, clear_first=True)
# If clear_first=True (default), it resets the board before applying
set_state(snapshot)

3. Search-First Component Handling

Ground knowledge by searching for pinouts before asking the user.

Workflow: 1. User: "I have a BME280." 2. Agent: search_web("BME280 pinout SPI I2C") 3. Agent: "I see the BME280 supports both SPI and I2C. Which one are you using?"

4. Batch Operations

Process information efficiently in large chunks to reduce round-trips and ensure atomic updates. The recommended way to perform complex batch reconfigurations is to fetch the current state, modify it in Python, and re-apply it.

# Recommended Batch Workflow:
state_json = get_state()
state = json.loads(state_json)

# 1. Modify connections
state['nets'].append({"index": 10, "name": "SIGNAL", "nodes": [5, 12, "D7"]})

# 2. Update power settings
state['power']['top_rail'] = 3.3

# 3. Configure GPIO
state['gpio'][0]['dir'] = "OUTPUT"
state['gpio'][0]['value'] = True

# 4. Apply all changes at once
set_state(json.dumps(state))

5. Return Values Over Prints

Tools should return data structures (lists, dicts) for programmatic use, not just print to stdout.

6. undo via Slot Backup

Always save the current state to a scratch slot (e.g. slot 7) before destructive operations like nodes_clear().

7. Context-Aware Error Recovery

Error messages should include suggested_fix fields to allow self-correction without user intervention.

8. Interactive Conversation Patterns

Explore First, Act Later: Probe and measure before applying power to unknown circuits.

Show, Don't Just Do: Explain actions via oled_print() and print() before executing them to keep the user informed.


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
Slot backup for undo ✅ Implemented (use save_slot(7) before destructive ops)