# Jumperless V5 - Complete Documentation > This file contains the full text of all Jumperless V5 documentation, > intended for use by LLMs and AI assistants. > > Source: https://docs.jumperless.org > Generated from MkDocs source files. --- # Home --- ## What is it? Jumperless V5 lets you prototype like a nerdy wizard who can see electricity and conjure jumpers with a magic wand. It’s an Integrated Development Environment (IDE) for hardware, with an analog-by-nature RP2350B dev board, a drawer full of wires, and a workbench full of test equipment (including a power supply, a multimeter, an oscilloscope, a function generator, and a logic analyzer) all crammed inside a breadboard. You can connect any point to any other using software-defined jumpers, so the four individually programmable ±8 V power supplies; ten GPIO; and seven management channels for voltage, current, and resistance can all be connected anywhere on the breadboard or the Arduino Nano header. RGB LEDs under each hole turn the breadboard itself into a display that provides real-time information about whatever’s happening in your circuit. It's not just about being too lazy to plug in some jumpers. With software controlled wiring, the circuit *itself* is now [***scriptable***](08-micropython.md), which opens up a world of infinite crazy new things you could never do on a regular breadboard. Have a script try out every combination of parts until it does what you want (*à la* [evolvable hardware](https://evolvablehardware.org/)), automatically switch around audio effects on the fly, characterize some unknown chip with the part numbers sanded off, or don't bother with any of that and just [play Doom on it](https://www.youtube.com/watch?v=xWYWruUO0F4). But more likely, you'll be using it to get circuits from your brain into hardware with so little friction it feels like you're just thinking them into existence. So yeah, wizard shit. These are the docs where you will learn how to wield your new powers --- ## If you don't already have one ### [Get the new Jumperless V5 rev 7](https://shop.jumperless.org/products/jumperless-v5-rev-7) Or if you want to save some money and get a refurbished one, ### [Jumperless V5 offcuts](https://shop.jumperless.org/products/jumperless-v5) ### [Get a Jumperless V5 on Crowd Supply](https://www.crowdsupply.com/architeuthis-flux/jumperless-v5) ### [Preorder the ALASKAN BULL WORM! PSRAM Mod Kit](https://shop.jumperless.org/products/alaskan-bull-worm-jumperless-v5-psram-mod-kit) --- ## Getting Started [Image: guide-42] ## Documentation Sections - **[Basic Controls](01-basic-controls.md)** - Learn how to use the probe, click wheel, and slot system - **[The App](03-app.md)** - For talking to your Jumperless, importing from Wokwi, and flashing Arduino sketches - **[OLED](04-oled.md)** - Add a better display - **[Arduino](05-arduino.md)** - UART passthrough and automatic flashing - **[Configuration](06-config.md)** - Persistent settings - **[Debugging](07-debugging.md)** - Crossbar, bridge, and net list views - **[File Manager](08-file-manager.md)** - Filesystem access, YAML slot file editing, and text editor - **[MicroPython](08-micropython.md)** - Use the onboard MicroPython interpreter - **[MicroPython API Reference](09.5-micropythonAPIreference.md)** - All the Jumperless-specific hardware calls - **[Odds and Ends](09.8-odds-and-ends.md)** - Stuff I couldn't think of a good category for - **[3D Printable Stand](10-3d-stand.md)** - Print your own stand - **[Glossary](99-glossary.md)** - Key terms including slots, nodes, bridges, and the W command (You should turn off [Dark Reader](https://darkreader.org/) for this site if you have it, it messes up the sidebar colors) --- ## Find Me On The Internet Join the [Discord](https://discord.gg/bvacV7r3FP) for pretty much instant answers to your questions --- See what Zack Freedman had to say about it: Apparently he didn't find this site, which makes sense, he was an early backer and at the time, the QR code on the box didn't take you here. --- Watch [Alex Glow](https://alexglow.com/) and [David Groom](https://exposed.ishotjr.com/) screw around with their Jumperlesses for a few hours: This also serves as a cautionary tale for what happens if you don't skim these docs first (which is a completely valid approach, you do you.) --- Me talking to [Ian Buckley](https://www.linkedin.com/in/ianmbuckley/) about Jumperless V5: The hardware was so rudimentary at this point that I would barely even call the thing I was showing there a "V5", it was revision 1 and a *lot* has been done since then. --- Chillin' with [Alex Lynd](https://alexlynd.com/) We're talking about an even earlier set of prototypes, this was shot at both Teardown 2024 and Hackaday Supercon 2024 so it's rev 1 and 2 shown here. --- The OG Jumperless video: Most of the stuff here is relevant as a subset of what Jumperless V5 does. --- [Image: dymo-6s] --- **For AI/LLM tools:** This documentation is available as [llms.txt](/llms.txt) (index) and [llms-full.txt](/llms-full.txt) (complete text) for easy ingestion. --- --- # Basic Controls [Image: guide-42] ---- ## The Probe First, keep the switch on the probe set to `Select` [Image: ProbeSelect] ## Connecting Rows Click the `Connect` button on the probe [Image: connectButton] The logo should turn blue and the LEDs on the probe should also change [Image: connect] Now any pair of nodes you tap should get connected as you make them. In connect mode, you're creating `bridges` (see the [glossary](99-glossary.md)), so connections are made in pairs. When you've tapped the first `node` in a pair, the `logo` and `Connect` text on the probe will brighten to show that you're "`holding`" a connection, and the next thing you tap will connect to that first `node`. If you make a mistake while `holding` a connection, click the `Connect` button and it will clear it and take you back to the first `node`. If you click the `Connect` button while you're not `holding` a `node`, it will leave `probe mode` and bring you back into `idle mode` (rainbowy`logo`, all 3 `probe LED`s on.) To get out of `Connect` mode, press the button again. ### Encoder Connections You can also make connections using just the clickwheel, without needing to touch the probe to the breadboard: **To activate:** - Navigate to: `Click` > `Connect` > `Add` (or `Remove`) - OR just turn the clickwheel while already in probe mode **How it works:** 1. Turn the clickwheel to scroll through all available nodes: - Breadboard rows (1-60) - Nano header pins (D0-A7) - Rails (Top, Bottom, GND) - DAC (0, 1) - ADC (0-4, Probe) - GPIO (1-8) - UART (TX, RX) - Current sense (I+, I-) 2. Click the encoder button to select the highlighted node 3. Hold the encoder button to exit The cursor will automatically hide after 5 seconds of inactivity. This is especially useful when you need precise control or want to access special functions without tapping pads. ## Removing Rows Click the `Remove` button [Image: removeButton] and the logo should turn reddish [Image: remove] Now you can swipe along the `pad`s or tap them one at a time. Remember it only disconnects that `node` and anything connected to it directly, not *everything* on the `net`. So tapping say, `row 25` that's connected to `GND` won't clear everything connected to `GND`, but tapping the `-` on the rails (for `GND`) would. The special functions work the same way, tap the pad, pick one, and it will remove it. Click the button again to get out. ## Probe Notes **Remember the probe is read by a resistive voltage divider**, so putting your fingers on the pads (or the back sides of the 4 risers that connect those `probe sense` boards to the main board), or anything causing the probe tip not to be at a steady 3.3V will give you weird readings. If you can't seem to stop playing with the switch on the probe, run the app `probe calib` and tap around on the board while turning the clickwheel until the place you tapped is always spot on (do this with the switch in both modes), and hold the clickwheel button to save. This adjusts the nominal 3.3V `measure` mode puts out should be fairly accurate enough for probing. ## The Click Wheel [Image: wheel copy] There are two kinds of presses, `click` (short press) and `hold` (long press). In general, a `click` (short) is a `yes`, and a `hold` (long) is a `no`/`back`/`exit`/`whatever`. When I say `click`, it's more of a diagonal slide toward the center of the board ([these encoders](https://lcsc.com/product-detail/Rotary-Encoders_Mitsumi-Electric-SIQ-02FVS3_C2925423.html) were meant to poke out just a little bit from the side of a tablet or whatever.) To get to the menu, `click` the button and scroll through the menus, `click` will bring you into that menu, `hold` will take you back one level. If you have trouble reading stuff on the breadboard LEDs, everything is copied to the Serial terminal and the OLED (talked about in [OLED Section](04-oled.md)), and adjusting the brightness may help; in the menus, it's `Display Options` > `Bright` > `Menu` and then scroll around until you find a level you like, then `click` to confirm. ## Special Functions To connect to `special functions`, tap the corresponding `pad` near the logo, it will show you a menu on the breadboard and terminal to choose them. [Image: gpioTapped] You can think of `special functions` just like any other `node`, the only difference is they're in a sort of "folder" so I didn't need to put a dedicated pad for each of them. ```jython DAC Pad └─ 0 1 [Tap pads below selection]¹ └─ -8V !:.:! +8V [Tap bottom pads or use clickwheel to select a voltage] > [click probe Connect button to confirm]² └─ [Tap a row to connect it to] (or if you were already "holding" a node, it'll connect there)³ ``` [Image: This is what prints in the terminal] (This is an ASCII version of what will show on the breadboard LEDs) ¹[Image: You can press R in the main menu to toggle this view] ²[Image: You can press R in the main menu to toggle this view] ³[Image: You can press R in the main menu to toggle this view] ```jython GPIO Pad └─ ⁱ1⁰ ⁱ2⁰ ⁱ3⁰ ⁱ4⁰ ₁5₀ ₁6₀ ₁7₀ ₁8₀ [Tap pads to choose which `GPIO` (left side for input, right side for output)] └─ [Tap a row to connect it to] (or if you were already "holding" a node, it'll connect there) ``` ¹[Image: You can press R in the main menu to toggle this view] The 4 `user pads` will be remappable in the future, but for now, `top_guy` is `routable UART Tx` and `bottom_guy` is `routable UART Rx`, and `building` pads are `Current sense` + and -. The **building pads** have multiple functions: - In `idle mode`: Override colors for net highlighting (see [Idle Mode Interactions](#idle-mode-net-highlighting)) - In `connect`/`remove` mode: Access **Current Sense (I+/I-)** with marching ants visualization! [Image: userPads] ### Current Sensing with Marching Ants When you tap either building pad in connect or remove mode, you'll get access to the current sense inputs (I+ and I-). When both I+ and I- are connected to different nets in your circuit: 1. A virtual wire appears between the two nets containing the `I Sense` nodes 2. Animated "marching ants" flow along this wire showing current direction The animation automatically picks the where to put the virtual "wire". It will search other nodes on the same nets that `I sense +` and `I sense -` are on it prefers places where they're on the same level so it can actually draw a connecting wire and not just be vertical lines. Your browser does not support the video tag. !!! warning `I Sense +` and `I Sense -` go on different nets but they're shorted internally They're two ends of a 2Ω shunt resistor, so remember that these will be shorted together. You measure current in series so this is expected, but it's super easy to forget. Take this warning as the equivalent of your multimeter yelling at you when you have the probes in the current holes and have it set to voltage. --- ## Idle Mode Net Highlighting The main thing is that there's a lot more interaction that can be done outside of any particular mode (like not probing and the logo is rainbowy, I'm gonna call this idle mode here until I think of a good name) [Image: idle] Here's what's new (all of this is in idle mode): ### Basic Interactions - **Tapping nets highlights them** as before, but there's a slightly different animation on the `row` you have selected from the whole `net` - **The click wheel scrolls through highlighting `rows`** as if you tapped each one ### Row Selection Actions With a `row` selected, here's what you can do: #### Connect Button - `connect` button will bring you into probing mode with the highlighted row already selected and then spit you back out to `idle` mode once you've made a connection to another row, or click `connect` again to exit #### Remove Button - `remove` will remove the highlighted `node` ### Measurement Display - if the highlighted row is a `measurement` (`gpio input` or `adc`) it will print the state to serial and the oled ### Output Toggle - if the highlighted row is an `output` (`gpio output`, I'll eventually do `dacs` too) clicking the `connect` button will toggle it `high` / `low`. The `remove` button will *just* unhighlight the net (there were some choices here, like make each button assigned to high / low or allow removing them, but this felt like the best way after trying them all). I will eventually add a setting for the toggle repeat rate (set to 500ms now) and a way to set it freewheeling as a clock. --- # The App ## Installation guide ### The Jumperless App is now on PyPi! The easiest way to get started is with pip: ```bash pip install jumperless ``` Then run it with: ```bash jumperless ``` **Note:** If the app version shows less than the latest release, `pip` defaults to a local version if it's available. In that case, run: ```bash pip install --no-cache-dir --upgrade jumperless ``` to make sure it grabs the latest version. The app repo is at [https://github.com/Architeuthis-Flux/Jumperless-App](https://github.com/Architeuthis-Flux/Jumperless-App) ### Alternative: Download Pre-built Binaries #### Find the latest release [https://github.com/Architeuthis-Flux/JumperlessV5/releases/latest](https://github.com/Architeuthis-Flux/JumperlessV5/releases/latest) The link above will magically lead you to the latest version, and will look something like `https://github.com/Architeuthis-Flux/JumperlessV5/releases/tag/5.2.0.0` **At the bottom under Assets, download the Jumperless App for your OS** ### Windows - `Jumperless.exe` - `Jumperless-Windows-x64.zip` ### macOS - `Jumperless_Installer.dmg` - `Jumperless_macOS.zip` ### Linux - x86 `Jumperless-linux-x86_64.tar.gz` (if you're not sure which flavor of Linux, use this one) - arm64 `Jumperless-linux-arm64.tar.gz` ### Python 1. download `JumperlessWokwiBridge.py` and `requirements.txt` 2. open your favorite terminal, navigate to the folder where you downloaded the two files above. 3. `pip install -r requirements.txt` # run this command to install the needed Python libraries 4. `python3 JumperlessWokwiBridge.py` # open the app, will update firmware if there's a newer version --- Now that I've lifted my self-imposed ban on VT100 commands (for compatibility and me-spending-too-much-time-on-them reasons, but, YOLO), we've got colors now! But that's like the *least* cool thing the new app can do, here's a list of what's new: ## What It Does - **Firmware updating** should be pretty reliable when there's a new version (falls back to instructions for how to do it manually) - **Command history and tab completion**, up arrows will go through past commands and are persistent after closing - **Properly detects** which port is the main Jumperless Serial and which is routable UART - **Arduino flashing from [Wokwi](https://wokwi.com/)** works once again and is a lot more solid - It installs [arduino-cli](https://github.com/arduino/arduino-cli) on first startup and uses it pull in libraries, compile, and flash an arduino Nano in the header - If the routable UART lines aren't connected when the app detects a change in the sketch file, it will connect them to flash the new code and then return them to how they were - [avrdude](https://github.com/avrdudes/avrdude) output is shown in real time (you'd be amazed how difficult this was) - **Direct Wokwi circuit import** - Copy diagram.json from Wokwi and import it with the `W` command (see below) - **No longer a janky pile of garbage** ## Local Arduino Sketch Support **You can set a `slot` to point to a local Arduino sketch.ino file and it will flash if it detects a change** - If you don't like using Arduino IDE or Wokwi and prefer using `vim` or `emacs` or whatever, now you can let the app handle the flashing stuff and just edit an .ino file. - In the app, type `menu` then `slots` and instead of entering a link to a Wokwi project, just give it a path to a file (this will be saved so you can unassign it and pick it later by name) - (This one is so fucking sick) ## Launch Scripts - Launch scripts included to easily run it from your favorite terminal emulator and not just the system default (terminal.app on macOS, Powershell on Windows, idk on Linux), just go to the directory in a terminal and run the script in [tabby](https://tabby.sh/) or whatever - The launcher *should* kill other instances (and close their windows) that happen to be open because it's such a common issue for me at least - Linux people are no longer red-headed stepchildren, there are proper tar.gz packages now for you nerds --- ## Importing Circuits from Wokwi You can design circuits in the [Wokwi online simulator](https://wokwi.com) and import them directly to your Jumperless with the `W` command, or use the Jumperless App and it'll pull it from your project automatically and live update. ### Direct Link Import You can now just dump a Wokwi link into the app at any time and it'll work: ``` Menu ~~~~~ x = clear all connections + = add connections - = remove connections https://wokwi.com/projects/424432011346848769 Enter a name for this new project: cool project zone ✓ Saved 'cool project zone' to project library ✓ 'cool project zone' assigned to active slot 0 URL: https://wokwi.com/projects/424432011346848769 The project will start updating automatically ``` ### How to manually Import from Wokwi 1. **Design your circuit** on [wokwi.com](https://wokwi.com) 2. **Click on the `diagram.json` tab** in the Wokwi editor 3. **Copy all the JSON content** (Ctrl+A, Ctrl+C or Cmd+A, Cmd+C) 4. **In Jumperless, type `W`** and press Enter 5. **Paste the JSON** (Ctrl+V or right-click → Paste) 6. The parser automatically detects when the JSON is complete and imports it! ### Supported Wokwi Components - **Half breadboard** - Wokwi's breadboard maps directly to Jumperless rows - **Arduino Nano** - All pins (D0-D13, A0-A7) (GND, 5V, 3.3V, and RST pins are hardwired and don't do anything) - **Logic Analyzer** - Channels map to GPIO: D0-7 → GPIO 1-8 - **Wire colors** - Wokwi wire colors preserved - **Rail voltages** - Detected from text labels in Wokwi - **VCC and GND Nodes** - VCC maps to the `TOP_RAIL` [Image: LogicAnalyzerMappingV5] **Note:** The app still works with the OG Jumperless and those original mappings remain the same. ### Wire Color Mapping **Wire colors will match the ones you set in Wokwi!** The new Wokwi parser sends the entire `diagram.json` from Wokwi and parses it on the Jumperless, which means color information gets preserved. [Image: wokwiColor-2] [Image: wokwiColor-1] All Wokwi wire colors are preserved and displayed on the breadboard LEDs: `red`, `orange`, `yellow`, `green`, `blue`, `violet`, `purple`, `magenta`, `cyan`, `white`, `gray`, `black`, `brown`, `limegreen`, `gold` **Note:** Black wires let the Jumperless auto-assign a color. If you leave all the wires green (the default in Wokwi) or make a wire black, it'll just auto assign colors. **About color assignment:** There is some weirdness because colors in Wokwi are applied to `bridges` (a pair of `nodes`) while color in the Jumperless gets assigned to `nets` (a collection of connected `nodes`). So if you have a bunch of things electrically connected together with different wire colors, it'll just pick one. It tries to pick unique colors first (no other nets with that same color), but if it can't, it'll shift the hue a bit so it's still that color but you can hopefully tell them apart. ### Rail Voltage Detection Add a text label in your Wokwi diagram to specify rail voltages: ``` top rail 5.5V bottom rail 3.5V ``` The Jumperless parser will automatically detect these values and set the rails accordingly ### Command Variants ``` W # Paste JSON, save to active slot W 5 # Paste JSON, save to slot 5 W /file.json # Load from file, save to active slot ``` ### After Import Use `<` to cycle through slots to activate your imported circuit, or it will be active immediately if imported to the current slot. ## Terminal Compatibility Or you can use any terminal emulator you like, [iTerm2](https://iterm2.com/), [xTerm](https://invisible-island.net/xterm/), [Tabby](https://github.com/Eugeny/tabby), [Arduino IDE](https://www.arduino.cc/en/software/)'s Serial Monitor, whatever. The TUI is all handled from the Jumperless itself so it just needs something to print text. --- # OLED Support First, get yourself one of these bad boys (literally any of these are fine.) [[Image: oled Medium]](https://www.amazon.com/dp/B0CDWQ2RWY/) https://www.amazon.com/MakerFocus-Display-SSD1306-3-3V-5V-Arduino/dp/B079BN2J8V [Image: batchone-11] Ignore the really cool LEDs. ## Installation They should friction fit into the SBC/SMD/OLED board included with your Jumperless V5. [Image: SBCBP-4 copy] Yo This should copy basically any text printed on the breadboard, some people have trouble reading text on the breadboard LEDs, which is why I added all this. ## Connection To connect the data lines to the Jumperless' GPIO 7 and 8, just use the menu option `.` (that's a period). It will try to find the OLED on the I2C bus, after a few failed attempts, it'll automatically disconnect to free up GPIO 7 and 8. ## Auto-Connect on Boot If you want to use this all the time, there's a config option to connect the OLED on startup. You can just paste this into the main menu: ``` `[top_oled] connect_on_boot = true; ``` ## Lock Connection Locking the connection to the OLED ensures that it stays connected even when you enter a complete `node` list. So if you're using Wokwi or manually adding connections in a file, you don't need to add `GPIO_7 - D2` and `GPIO_8 - D3` to keep the I2C connected to the OLED. ``` `[top_oled] lock_connection = true; ``` ## Custom Startup Message You can customize what appears on the OLED when your Jumperless boots up. There are two options: text messages or custom bitmap images. ### Text Message Set a custom text message to display on the OLED at startup (max 32 characters): ```jython `[top_oled] startup_message = Your Message Here; ``` This message will appear after the Jumperless logo on boot. ### Bitmap Image Display a custom bitmap image at startup by just giving it a path on the filesystem. ```jython `[top_oled] startup_message = /images/mylogo.bin; ``` **Requirements:** - Image must be a bitmap file (`.bin` format) with 4-byte header - Recommended size: 128×32 pixels (standard OLED size) - Use the built-in [Bitmap Editor](#bitmap-editor) to create or edit images - Store images in the `/images/` directory on the Jumperless filesystem ## Display Dimensions If you have a different sized OLED (like 128x64), you can set the dimensions: ```jython `[top_oled] width = 128; `[top_oled] height = 64; ``` ## Advanced GPIO Configuration You can change both the GPIO used for the display or the rows it connects to with the config options: ```jython `[top_oled] sda_pin = 26; `[top_oled] scl_pin = 27; `[top_oled] gpio_sda = GP_7; `[top_oled] gpio_scl = GP_8; `[top_oled] sda_row = D2; `[top_oled] scl_row = D3; ``` ## Connection type ```jython `[top_oled] connection_type = rp6_rp7; ``` ## Bitmap Editor The built-in bitmap editor lets you create and edit OLED images directly on your Jumperless using your terminal and the clickwheel. Your browser does not support the video tag. ## Accessing the Bitmap Editor ### From File Manager 1. Open the file manager from the main menu 2. Navigate to a `.bin` bitmap file 3. Select the file to open it in the editor ### Creating a New Image You can create a new bitmap file from the file manager: 1. Navigate to where you want to create the file (e.g., `/images/`) 2. Press `n` for "new file" 3. Name it with a `.bin` extension (e.g., `mylogo.bin`) 4. The editor will automatically create a blank 128×32 bitmap (or whatever your OLED dimensions are set to in config) ## Editor Interface The bitmap editor displays your image in the terminal and on the OLED (if connected). You'll see: - **Main canvas**: Your bitmap rendered using block characters - **Status bar**: Filename, dimensions, cursor position, and save status - **Menu bar**: Quick access to View, Encoder, Draw modes, Save, and Quit - **Help panel**: Keyboard shortcuts and hardware control reference ### View Modes Press `m` to cycle through three display modes: 1. **Full Block Mode** (1:1 pixel mapping) - Each character = 1 pixel 2. **Half Block Mode** (2:1 vertical compression) - Each character = 2 pixels vertically - Fits 128×32 images on smaller terminals 3. **Quarter Block Mode** (2×2 compression) - Each character = 2×2 pixels (4 pixels total) - Fits larger images on screen ## Navigation ### Moving the Cursor **Keyboard:** - Arrow keys or `W/A/S/D` keys - Vim keys: `j` (down), `k` (up), `l` (right) **Hardware:** - **Clickwheel encoder**: Rotate to move cursor - **Probe switch**: - `Select` position → Horizontal movement - `Measure` position → Vertical movement - Press `/` to toggle encoder direction (H/V) independently ## Editing Pixels ### Editing Methods The editor has three draw modes (cycle with `.` key) to pick what happens when you press `enter`/`space`/`encoder click`: 1. **Toggle Mode** (default): Flips pixel state (ON↔OFF) 2. **Set Mode**: Always turns pixels ON (draw) 3. **Clear Mode**: Always turns pixels OFF (erase) Or just use these keys to do it directly and not worry about the mode: - `z` = Set pixel (turn ON) - `x` = Clear pixel (turn OFF) - `c` = Toggle pixel ## Menu Bar Navigation When the cursor reaches the bottom edge and you press down, you enter the menu bar: **Navigation:** - Left/Right arrows: Move between menu items - Enter/Space: Activate selected item - Up: Exit menu bar **Menu Items:** - **View**: Cycle display modes (Full/Half/Quarter) - **Enc**: Toggle encoder direction (H/V) - **Draw**: Cycle draw modes (Toggle/Set/Clear) - **«Save»**: Save file and return to editing - **«Quit»**: Exit editor (prompts if modified) ## Saving and Quitting - **Ctrl+S**: Quick save - **Ctrl+Q**: Quit (prompts to save if modified) - **h or ?**: Show help screen The editor automatically adds the 4-byte header (width and height) when saving, making the file compatible as a startup image. ## Example Workflow: Creating a Startup Logo 1. Open file manager, navigate to `/images/` 2. Create new file: `mylogo.bin` 3. Editor opens with blank 128×32 canvas 4. Switch to Half Block mode (`m`) for better overview 5. Use clickwheel to navigate, Connect button to draw 6. Save with Ctrl+S 7. Set as startup image: - By editing the config file: ``` `[top_oled] startup_image = /images/mylogo.bin``` - Or use the click menus `OLED` > `Startup message` > `image` > (scroll through all the images and `click` to select) 8. Reboot or enter/exit the click menu to see your custom logo ## Editor Screenshots Full size view (1:1 pixel mapping): ```jython ███████ ██████ ███████ ██ ██ ██████ █████ ███ ██ ██ ██ █ █ ██████ ██████ ████ ██ ██ ██ ██ █ █ ███ ██ █████ ███ ████ ████ ██ ████ █ █ ██ █ █ █ ██ ██ █ ██ █ ████ █ ██ ███ ██ ███ ███ █ ██ ██ █ ██ █ ██ █ ██ ██ ██ █ ███ ██ █ █ ███ █ ██ ██ ██ ██ ██ █ ████ █ ██ █ ██ ██ █ █ █ █ █ █████ █ ██ █ ████ ████ █ ████ ██ ██ █ ██ █ █ █ █ ████ █ ██ █████ █ ██ █ █████ ██ ████ ██ ████████ ██ █ ██ █ █ ██ █ █████ █ ███████ █████ █ ██ █ ████ █ █████████ █████ ██ █ ██ █ ██ █ ██████ █ █████ █ █ ██ █ ███ █ ██ █ ████ ████ ███ ██ █ ███ █ ██ █ ██ ██ █ ███ ██ █ █ █ ██ █ ███████ ██ ███ █ ██ ██ █ ███ █ █ █ █ █ █ ██ ██ █ █ █ ██ ██ ██ ████ ███ ██ █ ██ █ ███ █ █ █ █ █ ██████ ██ █ █ █ ██ ██ █ ██ ██ ███ ██ ██ █ ████ █ █ █ █ █ ██ ████ █ ██ █ ██ ██ █ ███ █ ████ ██ █ █ ████ █ █ █ ██ █ █ █ ████ ███ █ ██ ██ ████ ██ ████ █ █ ██ ███ █ █ ████ █ █ ██ ███ ███ █ █ █████ ████ █ ███████ █ ██ █ ███ █ █ █ ███ █ ██ ██ ████ █ █ ████ ███ ███ ████ █ █ █ ███ ██ █ █ ██ ██ ████ ██ ███ █ █ █ ███████ ███████ ██ ██ █ █ █ ███ ██ █ ██ ██ ██ ███ █ ██ █ █ ████ ████ ████ ██ █ █ ████ █ █ █ ██ ██ ██ █ ████ ██ █ ██ ██ █ ████ ██ ██ ██ █ ██ ██ ██ █ ██ ██ █████ █ ██████ ██ ███████ █ ███ ███ ██ █ ██ █ █ █ ███ ██ ███ ████ █ █████ ██ ██ ███ ██ ██ █ ██ ███ ██ █ █ ██ ███ █ ██ █ ██ ██ ██ ███ █ █ ████ ████ ██ █ ████ █ █ █ █ █ █ ██ ██ ███ ██ ███ ███████ ██ ██████████ █ █ ██ ██ █ █ █ █ █ ██ ████ ████ ████ ██████████████ ███████████ █████████ █ ██ ██ ██ █ █ ██ ███ ██ ███ ███████████ ██████████████████ ████████ █████████ ███████ ██ ██ ███ ██ ██ ██ ████████████ ███████████ █████ █████ █████████ ██████ ██████ ██ ██ ████████ ██████ █████ █████ █████████ ███ ███ ██████ ███ ███ ██████ ████ ███ ███ █████ █████████ ████ ██ ███████ /images/bubbleJump.bin | 128x32 | (64,16) | Saved View:Full | Enc:V | Draw:CLR | «Save» | «Quit» ⟨Clickwheel > ↺ / ↻: move H/V | Click: toggle pixel ⟩ ⟨ Probe Buttons > Connect:set | Remove:clear | Switch > Select:H | Measure:V ⟩ ⟨Terminal > [z]:set [x]:clear [c]:toggle pixel | [m]:Cycle View | [/]: Enc H/V | ctrl+S:Save | ctrl+Q:Quit | [?]:Help ⟩ ``` Half Block view (2:1 vertical compression - each character is 2 pixels tall): ```jython ▄▄▄▄▄▄ ▄▄▄▄▄▄▄ ▄▄▀▀▀▀▀▀█▄ ▄▄▄▄▄▄ ▄▄▄▄▄▄ ▄▄▄▄▀▀▀▀▀█▄ ▄█▀▀▀█▄ ▄█▀▀ ▀█ ▄▀▀ ▀█▄▄▀ █▄ ▄█▀▀▀█ ▄▄▄▄ ▄▀▀█▄ ▄▄▄ ▄█▀▀█▄▄▀▀▀▀ ▀█▄▄▀▀▀▀ ▀▄▀ ▀█▄▀ █▄ ▄▀ ██ █▀ ▄▄ █ ▄█▀ █▀ ▀█▄▀ ██▀ ▀█ █ █ ▀█▀ █ ▄▄▄▄▄ ▀█ ██ █▀ ▄▄██ ▄██▄ █ ████ ▄█ ██ █ ██ █ █ ▄█ █ ▄████ █ ▄▄▄▄▄██ █████ █ ██ █ ▄███▀▀ ▀█ ████▄▄▄██ ▀█████▀▀ ██ █ ██▄ █ ██ █ ██▀▀██ █ ███▀▀ █▄ █ ▀█ █ ██▀ █ ██▄▄▄▄▄ █▄ ▀███ ▀▀▀█ ▀▀█▄ ██ █ ███ █ ▀ █ █ █ █ ██▄▄▄▄ ██ █ █ █ ██ ██ ▀▀ ▀█▀█▄ ▀▀█▄ ▀█▄▄ █▄ ▀█ █ ████ █ █ ▀▄ ▄█ █ ▀▀ ▀█▀█ █▄▄█▀ ▄█▄ █▀ ██ ▄█ ▀▀█▄▄▄ █▄ ▀███▄ ▀█ █▄ ▀█ ███ █ ▄ █ ███▀ █ ▄█ ▄█ ▀▀▀ ▄███ █ █ ▄███▀▀ ▀███ █▄█▀▀████ █ █ █ ███ ██ █ ▄█ ██ ██ ▄██▀▀ ▀█ ██▀ █ █ ▄▄▄█ ████▀▀█▄▄█▀▀▀██▀ █▀ ▀▀ █ ▄█▀▀█▄ █ █▄ ▀ ██ ██▄██ █ ▄▄▄▄▄█▀██ ██▄▄▄▄▄ █ ██▄ ▀█▄█ ██▀▀ ▀█ ▀▀ ██ █ █▀ █ ▀█▀ ██ ███ █▀██ █ ▀██▀▀ ██ ▀▀ ▀██ ██▄ ▀█ █ ▄██▄ ███▄ ▄█▀ █ █▀██ ▄█ █ █ █ █ █ ▀█▄ ▄██▄ ███▄ ▄██▄ ▄▄███▄▄▄▄▄▄███▀███▄▄▄▄▄▄▄█▀ ▀█████████ █▄ ██ ██▄ ▄█▀ █▄ ▄█ ██▄▄███▄▄▄██ ▀██▄▄▄▄▄████▀▀█████ ▀█████▀▀█████████▀ ▀██████▀ ▀▀██████▀ ▀▀▀▀▀▀▀ ██▄ ▄██ ▀██████▀ ▀████▀ ▀███▀ ▀███▀ ▀█████▀▀▀ ▀▀▀ ▀▀▀ ▀▀▀▀▀▀ ▀███████▀ ▀▀▀▀ ▀▀ /images/bubbleJump.bin | 128x32 | (64,16) | Saved View:Half | Enc:V | Draw:CLR | «Save» | «Quit» ⟨Clickwheel > ↺ / ↻: move H/V | Click: toggle pixel ⟩ ⟨ Probe Buttons > Connect:set | Remove:clear | Switch > Select:H | Measure:V ⟩ ⟨Terminal > [z]:set [x]:clear [c]:toggle pixel | [m]:Cycle View | [/]: Enc H/V | ctrl+S:Save | ctrl+Q:Quit | [?]:Help ⟩ ``` Quarter Block view (2×2 compression - each character is 4 pixels): ```jython ▄▄▄ ▄▄▄▖ ▗▞▀▀▜▖ ▄▄▄ ▗▄▄▖▗▄▞▀▀▙ ▟▀▜▖ ▗▛▘ ▝▌▗▀ ▜▄▘ ▙ ▟▀▜▗▄▖ ▞▜▖▗▄ ▗▛▜▄▀▀ ▝▙▞▀▘ ▚▘ ▝▙▘ ▙ ▗▘ ▐▌ ▛ ▄ ▐ ▗▛ ▛ ▜▞ █▘▝▌▐ ▌ ▜▘ ▐ ▗▄▄ ▜ █ ▛ ▄█ ▟▙ ▌ ▐█▌ ▟ ▐▌ ▌ ▐▌ ▐ ▌▟ ▌ ▟█▌ ▐ ▗▄▄█ ██▌ ▐ █ ▌ ▗█▛▘▜ ██▄▟▌ ▝██▛▘ █ ▌ ▐▙ ▐ ▐▌ ▌ █▀█ ▐ █▛▘▐▖ ▌ ▜ ▐ ▐▛ ▌ ▐▙▄▄▐▖ ▝█▌▀▜ ▀▙ ▐▌ ▌ ▐█ ▐ ▘ ▌ ▌ ▐ ▐ █▄▄▐▌ ▐ ▐ ▐ ▐▌ █ ▝▘ ▝▛▙ ▀▙▝▙▖ ▐▖ ▝▌ ▌ ▐█▌ ▐ ▌ ▚ ▟ ▐ ▀ ▝▛▌ ▐▄▛ ▟▖ ▐▘ █ ▗▌▝▜▄▖ ▐▖▝█▙ ▜ ▙ ▜ █▌ ▐ ▖ ▌ ▐█▘ ▐ ▗▌▟ ▝▀ ▗█▌ ▐ ▐ ▟█▀ ▜█ ▙▛▜█▌ ▐ ▐ ▐ █▌ ▐▌ ▌▗▌ █ █ ▗█▀ ▜ █▘▌ ▐ ▄▟ ██▀▙▟▀▜▛ ▛ ▀ ▐ ▟▀▙▐ ▐▖ ▝ ▐▌ █▟▌ ▐ ▄▄▟▜▌ ▐▙▄▄▐ ▐▙ ▝▙▌ ▐▛▘▝▌ ▝▘ ▐▌ ▌ ▛ ▌ ▝▛ ▐▌ █▌ ▐▜▌ ▐ ▜▛▘▐▌ ▀ ▝█ █▖ ▝▌ ▌ ▟▙ ▐█▖ ▟▘ ▌ ▛█ ▗▌▌ ▐ ▌ ▐ ▐ ▝▙ ▗█▖ █▙ ▗█▖ ▗▟█▄▄▄█▛█▙▄▄▄▛▝████▌ ▙ ▐▌▐▙ ▟▘▙ ▟ █▄█▙▄█ ▜▙▄▄██▀██▌▜██▀████▛▝███▘ ▝▜██▛ ▝▀▀▀ ▐▙ ▗█ ▜██▛ ▜█▛ ▜█▘▜█▘ ▝██▛▀ ▝▀ ▀▘ ▝▀▀▘ ▜███▘ ▀▀ ▀ /images/bubbleJump.bin | 128x32 | (64,16) | Saved View:Qtr | Enc:V | Draw:CLR | «Save» | «Quit» ⟨Clickwheel > ↺ / ↻: move H/V | Click: toggle pixel ⟩ ⟨ Probe Buttons > Connect:set | Remove:clear | Switch > Select:H | Measure:V ⟩ ⟨Terminal > [z]:set [x]:clear [c]:toggle pixel | [m]:Cycle View | [/]: Enc H/V | ctrl+S:Save | ctrl+Q:Quit | [?]:Help ⟩ ``` The output of `?` ```jython === Bitmap Editor Help === Navigation: Encoder wheel - Move cursor (H or V mode) Arrow keys / WASD - Move cursor j/k/l (vim) - Move cursor Down at bottom edge - Enter menu bar Editing: Encoder click - Apply current draw mode at cursor Enter / Space - Apply current draw mode at cursor Connect button HOLD - Set pixels while held (draw lines) Remove button HOLD - Clear pixels while held (erase lines) Direct Pixel Actions (keyboard): z - Set pixel at cursor (draw) x - Clear pixel at cursor (erase) c - Toggle pixel at cursor Draw Mode Control: . - Cycle draw modes (Toggle/Set/Clear) Hardware Controls: Probe switch SELECT - Encoder horizontal movement Probe switch MEASURE- Encoder vertical movement Display: m - Cycle view mode (Full/Half/Quarter) / - Toggle encoder H/V movement Menu Bar (Down at bottom edge): Left/Right arrows - Navigate menu items Enter / Space - Activate menu item (cycle/Save/Quit) Up / Escape - Exit menu bar Menu Bar Items: View - Cycle display mode (Full/Half/Quarter) Enc - Toggle encoder direction (H/V) Draw - Cycle draw mode (Toggle/Set/Clear) «Save» - [Button] Save file and exit menu «Quit» - [Button] Quit editor (prompts if modified) File: Ctrl+S - Save file Ctrl+Q / ESC - Quit (prompts if modified) h / ? - Show this help Cursor Colors: Green background - Pixel is OFF Red background - Pixel is ON ``` ## Bitmap File Format The editor works with `.bin` files in two formats: **With Header (Recommended):** - 4 bytes: Width (16-bit little-endian) - 2 bytes: Height (16-bit little-endian) - Remaining: Bitmap data (MSB-first, row-major) - Example: 128×32 = 4 header + 512 data = 516 bytes total **Raw Format:** - Just bitmap data, dimensions inferred from file size - 512 bytes → 128×32, 1024 bytes → 128×64, etc. The editor automatically adds headers when saving, making files ready to use as startup images. ## Converting External Images Want to use your own images? The JumperlOS repository includes Python scripts to convert PNG/JPG images to OLED bitmaps: **Location:** `JumperlOS/scripts/image_to_oled_bitmap.py` **Usage:** ```bash python image_to_oled_bitmap.py input.png output.bin --width 128 --height 32 ``` The script will: 1. Resize your image to fit the OLED dimensions 2. Convert to 1-bit (black/white) format 3. Save with proper header format 4. Output is ready to use as a startup image or edit in the bitmap editor Then you can mount your Jumperless's filesystem as a mass storage device with `U` and drop it into the `/images/` folder. ### Check out the [`/scripts` folder](https://github.com/Architeuthis-Flux/JumperlOS/tree/main/scripts) in the [JumperlOS repo](https://github.com/Architeuthis-Flux/JumperlOS/tree/main), there are a few other scripts related to dealing with bitmaps. --- ## Using the OLED from MicroPython The OLED display has a comprehensive MicroPython API for programmatic control. You can display text with multiple fonts and sizes, show bitmaps, manipulate pixels directly, and even redirect Python's `print()` output to the OLED. ### Quick Start ```jython import jumperless as j import time # Basic text display j.oled_connect() j.oled_print("Hello!", 2) time.sleep(2) j.oled_clear() ``` ### Text Sizes and Scrolling The OLED supports three text size modes: - **Size 0**: Small scrolling text - perfect for terminal-like output with multiple lines - **Size 1**: Normal centered text - **Size 2**: Large centered text (default) ```jython import jumperless as j import time # Set default text size j.oled_set_text_size(0) # Small scrolling text # Display multiple lines for i in range(10): j.oled_print(f"Line {i+1}") time.sleep(0.3) # Switch to large text j.oled_set_text_size(2) j.oled_print("BIG TEXT") ``` ### Print Redirection for Debugging One of the most useful features is print redirection - all `print()` statements can automatically appear on both the serial console **and** the OLED: ```jython import jumperless as j # Enable print copying j.oled_copy_print(True) # These appear on both serial AND OLED print("Starting test...") voltage = j.adc_get(0) print(f"Voltage: {voltage:.2f}V") print("Test complete!") # Disable when done j.oled_copy_print(False) ``` This is perfect for debugging projects where you don't have easy access to the serial console. ### Multiple Fonts Choose from 11 different font families: ```jython import jumperless as j # List all available fonts fonts = j.oled_get_fonts() print(fonts) # Set a fun font j.oled_set_font("Jokerman") j.oled_print("Fun!", 2) # Switch to monospace for code j.oled_set_font("Courier New") j.oled_print("Monospace", 2) ``` ### Display Bitmaps Show bitmap images stored on the filesystem: ```jython import jumperless as j # One-liner to display a bitmap j.oled_show_bitmap_file("/images/jogo32h.bin", 0, 0) # Or load and display separately j.oled_load_bitmap("/images/logo.bin") j.oled_display_bitmap(0, 0, 0, 0) ``` ### Graphics and Pixel Control For custom graphics, you can manipulate individual pixels: ```jython import jumperless as j # Draw a box j.oled_clear() for x in range(20, 108): j.oled_set_pixel(x, 10, 1) # Top j.oled_set_pixel(x, 22, 1) # Bottom for y in range(10, 23): j.oled_set_pixel(20, y, 1) # Left j.oled_set_pixel(107, y, 1) # Right j.oled_show() ``` ### Advanced: Direct Framebuffer Access For maximum control, you can read and write the entire framebuffer: ```jython import jumperless as j # Get display dimensions width, height, size = j.oled_get_framebuffer_size() print(f"Display: {width}x{height}, {size} bytes") # Capture the screen fb = j.oled_get_framebuffer() # Save to file with open("/screen_capture.bin", "wb") as f: f.write(fb) # Restore later with open("/screen_capture.bin", "rb") as f: fb_data = f.read() j.oled_set_framebuffer(fb_data) ``` ### Complete API Reference For the full API documentation with all functions, parameters, and examples, see: **[MicroPython API Reference - OLED Display Section](09.5-micropythonAPIreference.md#oled-display)** The API includes: - Text size control (`oled_set_text_size`, `oled_get_text_size`) - Print redirection (`oled_copy_print`) - Font system (`oled_get_fonts`, `oled_set_font`, `oled_get_current_font`) - Bitmap functions (`oled_load_bitmap`, `oled_display_bitmap`, `oled_show_bitmap_file`) - Framebuffer access (`oled_get_framebuffer`, `oled_set_framebuffer`, `oled_get_framebuffer_size`) - Pixel manipulation (`oled_set_pixel`, `oled_get_pixel`) ### Example Projects **Animated Sine Wave:** ```jython import jumperless as j import math import time width, height, _ = j.oled_get_framebuffer_size() for offset in range(100): j.oled_clear(False) # Don't show() after clear to avoid flashing for x in range(width): y = int(height//2 + 10 * math.sin((x + offset) / 10)) if 0 <= y < height: j.oled_set_pixel(x, y, 1) j.oled_show() time.sleep(0.05) ``` **Sensor Monitor:** ```jython import jumperless as j import time # Monitor voltage with print redirection j.oled_copy_print(True) j.oled_clear() while True: voltage = j.adc_get(0) current = j.ina_get_current(0) print(f"V: {voltage:.2f}V") print(f"I: {current*1000:.1f}mA") time.sleep(1) ``` --- # Arduino Stuff ## UART Passthrough With an Arduino Nano in the header and the UART lines connected, anything on those lines should be passed through to the second serial port that shows up when you plug in your Jumperless. (You can also set the config option `[serial_1] print_passthrough = true;` and have it print on both. Don't worry about the baud rate, the Jumperless senses what the host computer is set to and changes the speed accordingly. ## Quick Connection Shortcuts The shortcuts to connect `D0` and `D1` to the Jumperless's UART `Tx` and `Rx` is `A` to connect, and `a` to disconnect. ## Automatic Flashing It will even sense when Arduino IDE is trying to upload code and twiddle the reset lines to allow you to flash code with just a single USB cable going to your Jumperless. **Tip:** You can also use [Wokwi](https://wokwi.com) with the Jumperless Bridge app for flashing - no need to even have the Arduino IDE open --- ## Commands from Routable UART You can send commands to the Jumperless from your Arduino (or anything connected to the routable UART) by wrapping them in XML-style tags. The tags are stripped out and the command is executed - the Arduino never sees them come back. ### Two Types of Tags There are two flavors of command tags, depending on what you want to do: #### `` Tags - Raw Commands These run exactly like you typed them in the main Jumperless menu. Use these for things like making connections with `f`, loading files, or any single-character menu command. #### `` Tags - Python Commands These run MicroPython commands directly. Perfect for `connect()`, `disconnect()`, `adc_get()`, `dac_set()`, and all the other Python hardware functions. The `` tag automatically prepends the `>` that normally tells the Jumperless "this is a Python command." ### Supported Tag Names Any of these work (use matching opening and closing tags): | Tag | Example | |-----|---------| | `` | `f 1-30` | | `` | `x` | | `` | `n` | | `` | `adc_get(0)` | --- ## Python Commands with `` Tags The `` tag is the most powerful way to control your Jumperless from Arduino code. It gives you direct access to all the MicroPython hardware functions. ### Basic Example ```cpp #define OPENJCOMMAND Serial.print(""); #define CLOSEJCOMMAND Serial.println(""); void setup() { Serial.begin(115200); delay(1500); // Give Jumperless time to boot } void loop() { // Read voltage on ADC channel 0 OPENJCOMMAND Serial.print("adc_get(0)"); CLOSEJCOMMAND delay(100); // Read the response while(Serial.available() > 0) { char c = Serial.read(); // Process the voltage reading... } } ``` ### Full Example - ADC Scanning Your browser does not support the video tag. This sketch connects ADC0 to different breadboard rows and reads the voltage at each one: ```cpp #define OPENJCOMMAND Serial.print(""); #define CLOSEJCOMMAND Serial.println(""); void setup() { pinMode(LED_BUILTIN, OUTPUT); Serial.begin(115200); delay(1500); } int lastNode = 8; int node = 8; unsigned long delayTime = 60; void loop() { digitalWrite(LED_BUILTIN, HIGH); delay(delayTime); node++; if (node > 60) { node = 1; } // Disconnect from previous node OPENJCOMMAND Serial.print("disconnect( ADC0," + String(lastNode) + ")"); CLOSEJCOMMAND delay(delayTime); // Connect to new node OPENJCOMMAND Serial.print("connect(ADC0 ," + String(node) + ")"); CLOSEJCOMMAND delay(delayTime); // Read the voltage OPENJCOMMAND Serial.print("adc_get(0)"); CLOSEJCOMMAND delay(delayTime); // Read response from Jumperless char response[30] = {0}; int idx = 0; while(Serial.available() > 0 && idx ` tags: ```cpp // Connections "connect(1, 30)" // Connect breadboard rows "connect(D13, TOP_RAIL)" // Connect Arduino pin to power "disconnect(ADC0, 15)" // Remove a connection "nodes_clear()" // Clear ALL connections // Analog I/O "adc_get(0)" // Read voltage (channels 0-4) "dac_set(0, 3.3)" // Set DAC output voltage "dac_set(TOP_RAIL, 5.0)" // Set rail voltage // Digital I/O "gpio_set(1, True)" // Set GPIO high "gpio_set(1, False)" // Set GPIO low "gpio_get(2)" // Read GPIO state // Current sensing "ina_get_current(0)" // Read current in amps "ina_get_voltage(0)" // Read shunt voltage ``` See the [MicroPython API Reference](09.5-micropythonAPIreference.md) for the complete list. --- ## Raw Commands with `` Tags Use `` tags when you want to send menu commands - the same ones you'd type in the serial terminal. ### Example - Making Connections ```cpp #define OPENJCOMMAND Serial.print(""); #define CLOSEJCOMMAND Serial.println(""); void setup() { pinMode(LED_BUILTIN, OUTPUT); Serial.begin(115200); delay(1500); } int node1 = 1; int node2 = 8; unsigned long delayTime = 60; void loop() { digitalWrite(LED_BUILTIN, HIGH); delay(delayTime); node1++; node2++; if (node1 > 60) node1 = 1; if (node2 > 60) node2 = 1; // Use the 'f' command to make a connection // Format: f - OPENJCOMMAND Serial.print("f " + String(node1) + "-" + String(node2) + "\n"); CLOSEJCOMMAND delay(delayTime); // Read any response char response[30] = {0}; int idx = 0; while(Serial.available() > 0 && idx `** for anything that's a Python function: `connect()`, `adc_get()`, `dac_set()`, etc. - **Use ``** for menu commands: `f`, `x`, `n`, `s`, etc. --- ## Wokwi Integration If you're using the [Jumperless Wokwi Bridge](https://github.com/Architeuthis-Flux/Jumperless-Wokwi-Bridge), you can flash your Arduino directly from Wokwi simulations - the bridge handles all the communication for you. --- # Config File To change any persistent settings that apply to the Jumperless as a whole, there's a `config` file. You can read it with `~` and edit settings by copying any of those lines, pasting it back, and changing the value to whatever you want it to be. ## Viewing Config.txt You can enter `~` to print the config. ```jython ~ copy / edit / paste any of these lines into the main menu to change a setting Jumperless Config: `[config] firmware_version = 5.6.5.15; `[hardware] generation = 5; `[hardware] revision = 7; `[hardware] probe_revision = 5; `[hardware] psram_installed = 0; `[dacs] set_dacs_on_boot = false; `[dacs] set_rails_on_boot = true; `[dacs] probe_power_dac = 0; `[dacs] auto_connect_probe = 1; `[dacs] limit_max = 8.00; `[dacs] limit_min = -8.00; `[debug] file_parsing = false; `[debug] net_manager = false; `[debug] nets_to_chips = false; `[debug] nets_to_chips_alt = false; `[debug] leds = false; `[debug] probing = false; `[debug] oled = false; `[debug] logo_pads = false; `[debug] logic_analyzer = true; `[debug] arduino = 0; `[debug] usb_mass_storage = false; `[routing] stack_paths = 2; `[routing] stack_rails = 3; `[routing] stack_dacs = 0; `[routing] rail_priority = 1; `[calibration] top_rail_zero = 1655; `[calibration] top_rail_spread = 18.80; `[calibration] bottom_rail_zero = 1655; `[calibration] bottom_rail_spread = 19.27; `[calibration] dac_0_zero = 1655; `[calibration] dac_0_spread = 19.53; `[calibration] dac_1_zero = 1650; `[calibration] dac_1_spread = 19.44; `[calibration] adc_0_zero = 8.95; `[calibration] adc_0_spread = 17.89; `[calibration] adc_1_zero = 8.91; `[calibration] adc_1_spread = 17.81; `[calibration] adc_2_zero = 8.97; `[calibration] adc_2_spread = 17.90; `[calibration] adc_3_zero = 8.89; `[calibration] adc_3_spread = 17.79; `[calibration] adc_4_zero = 0.00; `[calibration] adc_4_spread = 4.86; `[calibration] adc_7_zero = 9.24; `[calibration] adc_7_spread = 18.34; `[calibration] probe_max = 4055; `[calibration] probe_min = 25; `[calibration] probe_switch_threshold_high = 1.30; `[calibration] probe_switch_threshold_low = 1.14; `[calibration] probe_switch_threshold = 0.40; `[calibration] measure_mode_output_voltage = 3.27; `[calibration] probe_current_zero = 2.41; `[calibration] minimum_probe_reading = 85; `[logo_pads] top_guy = uart_tx; `[logo_pads] bottom_guy = uart_rx; `[logo_pads] building_pad_top = isense_pos; `[logo_pads] building_pad_bottom = isense-; `[logo_pads] repeat_ms = 100; `[display] lines_wires = wires; `[display] menu_brightness = -10; `[display] led_brightness = 10; `[display] rail_brightness = 55; `[display] special_net_brightness = 20; `[display] net_color_mode = rainbow; `[display] dump_leds = ; `[display] dump_format = image; `[display] terminal_line_buffering = 0; `[serial_1] function = passthrough; `[serial_1] baud_rate = 115200; `[serial_1] print_passthrough = false; `[serial_1] connect_on_boot = false; `[serial_1] lock_connection = false; `[serial_1] autoconnect_flashing = true; `[serial_1] async_passthrough = true; `[serial_1] tag_parsing = enabled; `[serial_1] flash_reset_type = avr; `[serial_2] function = micropython; `[serial_2] baud_rate = 115200; `[serial_2] print_passthrough = false; `[serial_2] connect_on_boot = false; `[serial_2] lock_connection = false; `[serial_2] autoconnect_flashing = false; `[top_oled] enabled = true; `[top_oled] i2c_address = 0x3C; `[top_oled] width = 128; `[top_oled] height = 32; `[top_oled] connection_type = i2c0; `[top_oled] sda_pin = 4; `[top_oled] scl_pin = 5; `[top_oled] gpio_sda = GP_4; `[top_oled] gpio_scl = GP_5; `[top_oled] sda_row = -1; `[top_oled] scl_row = -1; `[top_oled] connect_on_boot = true; `[top_oled] lock_connection = false; `[top_oled] show_in_terminal = false; `[top_oled] font = BerkMono; `[top_oled] startup_message = images/bubbleJumpThin.bin `[usb_cdc] ignore_dtr = false; ``` This is just a file on your filesystem called `config.txt` and just editing that file directly works too. ## Config Help There's also a `help` you can get to by entering `~?` ```c++ ~? Help for command: ~ Read config ~ = show current config ~names = show names for settings ~numbers = show numbers for settings ~[section] = show specific section (e.g. ~[routing]) Write config `[section] setting = value; = enter config settings (pro tip: copy/paste setting from ~ output and just change the value) Reset config `reset = reset to defaults (keeps calibration and hardware version) `reset_hardware = reset hardware settings (keeps calibration) `reset_calibration = reset calibration settings (keeps hardware version) `reset_all = reset to defaults and clear all settings `force_first_start = clears everything to factory settings and runs first startup calibration Help ~? = show this help ``` ## State File ## States vs Config States are now saved as YAML and we did away with the old text file format. `globalState` holds all connections, paths, and other circuit configuration in a single object that most of the code uses now. **State vs Config - What's the Difference?** - **State** stores things relevant to the currently loaded slot - connections, wire colors, rail voltages, GPIO settings. These change when you switch slots. - **Config** (config.txt) contains hardware-wide settings that apply to the entire Jumperless regardless of which slot is active. Rail voltages, GPIO settings, and other circuit-specific parameters now go with the state (in the YAML file) rather than config, so each slot can have its own power supply and GPIO configuration. ## State File Structure For things specific to the current `state` of the Jumperless, there's a YAML file that contains all the connections, colors (optional), `rail` / `DAC` voltages, `GPIO` directions and pulls, stuff like that. The idea is this defines a complete setup of a particular circuit that can be switched between in different `slots`. The Jumperless always boots at `Slot 0`, and you can switch to other `slots` with ` `Load` > `0-7` (it will show a preview of each one.) To save a copy of the currently `active slot`; `Slots` > `Save` > `0-7` will save a copy of the `active slot` to another `slot` and also make that target slot the `active`. ```jython ╭────────────────────────────────────╮ │ Current YAML State (RAM) │ ╰────────────────────────────────────╯ Active Slot: 0 Dirty Flag: NO (saved) ─── YAML Output ─── version: 2 sourceOfTruth: bridges bridges: - {n1: 38, n2: 44, dup: 2} - {n1: 21, n2: 28, dup: 2} - {n1: 48, n2: 55, dup: 2} - {n1: 4, n2: 2, dup: 2} - {n1: BUFFER_IN, n2: DAC0, dup: 1} nets: - {num: 4, nodes: [DAC_0, BUF_IN], name: "DAC 0", anim: true} - {num: 6, nodes: [38, 44], color: pink} - {num: 7, nodes: [21, 28], color: blue} - {num: 8, nodes: [48, 55], color: green} - {num: 9, nodes: [4, 2], color: amber} power: topRail: 3.30 bottomRail: 2.50 dac0: 3.30 dac1: 0.00 config: routing: {stackPaths: 2, stackRails: 3, stackDacs: 0, railPriority: 1} gpio: direction: [1,1,1,1,1,1,1,1,1,1] pulls: [0,0,0,0,0,0,0,0,0,0] pwmFrequency: [1.00,1.00,1.00,1.00,1.00,1.00,1.00,1.00,1.00,1.00] pwmDutyCycle: [0.50,0.50,0.50,0.50,0.50,0.50,0.50,0.50,0.50,0.50] pwmEnabled: [0,0,0,0,0,0,0,0,0,0] uart: {txFunction: 0, rxFunction: 1} oled: {connected: false, lockConnection: false} ─── Memory Usage ─── Connections: 5 State RAM: ~58048 bytes ``` ## Source of Truth Because the information in here is *sort of* redundant (the connections could be computed from just the `bridges` or the `nets` section on their own), there's a field called `sourceOfTruth` which is the section that actually gets parsed and then the other section is written with the computed values. (I haven't done much testing on changing this to `nets` so I'd probably just leave it on `bridges` for now.) There's some weirdness with how colors are applied, since the `source of truth` is the bridges, and the things that actually get colored are the `nets`, it'll take the colors from the `bridges` (if specified) and try to apply them to the `nets`. But since nets always have a single color (to show that they're connected), if you have `bridges` with different colors in the same `net`, it'll just pick one (don't ask me exactly how the logic chooses, idk.) If you specify a color to a `net` even with `sourceOfTruth: bridges` it should respect the `net` assignment over the `bridge` assignment. ## Switching Between Saved Circuits (Slots) The Jumperless has **8 slots** (0-7) where you can save different circuit configurations. Think of them like presets or save files. **Quick slot cycling:** - Type `<` in the terminal to cycle to the next slot **Other slot commands:** - `l 5` - Load slot 5 specifically - `Q` - Query which slot is currently active - `s` - Show a list of all saved slots When you make connections with the probe, they're automatically saved to whichever slot is currently active. See the [Glossary](99-glossary.md) for more details about slots. ## Live Editing State Files You can edit the YAML slot files and they will live update to the board! Whether you're editing them in the onboard `eKilo` editor or as a mounted USB MSC device on your computer, changes will be reflected immediately on your Jumperless. --- # Debug Views Look *Inside* your Jumperless ## Crossbar Array There's a new way to see what the 12 analog crossbar switches are up to, just enter `C` in the menu [Image: Crossbar View] Lowercase `c` will show a compact version [Image: Crossbar Compact] You can also set it to live updating mode with `c!` Your browser does not support the video tag. --- ## Bridge Array Enter `b` in the menu. This is generally the most helpful one for *me* to troubleshoot what's going on if your issue has anything to do with routing or connections. It probably looks like nonsense to you but I've been in it so long it makes perfect sense to me. [Image: Screenshot 2025-05-30 at 7 04 54 AM] --- ## Net List Enter `n` in the menu to show this one. If you have anything that's doing any measurement (`gpio` input or `ADC`s), it'll stay up and live update if any of them change. (And just like basically any menu not asking for input, entering anything will bring you back to the main menu.) [Image: Screenshot 2025-05-30 at 7 10 04 AM] --- # File Manager The Jumperless has a built in File Manager which you can access in the menu with `/`, or enter `U` in the menu and Jumperless will mount as a USB Mass Storage drive called `JUMPERLESS` where you can edit files on the filesystem. ## File System Structure ``` ├── config.txt │ ├── slots/ │ ├── slot0.yaml │ ├── slot1.yaml │ ├── slot2.yaml │ └── ... (up to slot7.yaml) │ └── python_scripts/ ├── history.txt ├── cool_micropython_script.py ├── ... (your python scripts go here) │ └── examples/ ├── adc_basics.py ├── dac_basics.py ├── gpio_basics.py ├── led_brightness_control.py ├── node_connections.py ├── stylophone.py ├── uart_basics.py ├── uart_loopback.py └── voltage_monitor.py ``` Each slot's configuration is stored as a YAML file in the `/slots/` directory, and the global hardware configuration is in `/config.txt`. --- ## Navigation ### Basic Movement | Control | Action | |---------|--------| | **↑/↓ Arrow Keys** or **Rotary Encoder** | Move selection up/down | | **Enter** or **Click Encoder** | Open directory or edit file | | **/** | Go to root directory | | **.** | Go up one directory| | **CTRL + q** | Quit File Manager or Text Editor --- ### File Manager Commands | Key | Action | Description | |-----|--------|-------------| | [enter] | Open | Open file or enter directory | | **h** | Help | Show help | | **v** | Quick view | View file contents | | **.** | Up dir | Go up one directory | | **n** | New file | Create new file (prompts for filename) | | **d** | New directory | Create new directory | | **x** | Delete | Delete file or directory (confirm with `y`/`N`) | --- ### File Type Icons and Colors | Icon | File Type | Extensions | Color | |------|-----------|------------|-------| | **⌘** | Directories | - | Blue | | **𓆚** | Python files | .py, .pyw, .pyi | Green | | **⍺** | Text files | .txt, .md | White | | **⚙** | Config files | .cfg, .conf, config.txt | Yellow | | **⟐** | JSON/YAML files | .json, .yaml | Cyan | | **☊** | Slot files | /slots/slot*.yaml | Magenta | | **⎃** | Legacy slot files | nodeFileSlot*.txt | Orange | --- ## Jumperless eKilo Text Editor The File Manager also has text editor based off [**eKilo**](https://github.com/antonio-foti/ekilo) ### Editor Controls - **Ctrl+S**: Save file - **Ctrl+Q**: Quit editor - **Ctrl+P**: Save and launch MicroPython REPL - **Arrow keys**: Navigate cursor - **Rotary encoder**: Move cursor horizontally - **Click encoder**: Enter character selection mode (you can scroll through the letters on the OLED and click again to insert it) ### Character Selection With the Click Wheel and OLED When using the rotary encoder in the editor: - **Click encoder**: Enter character selection mode - **Rotate encoder**: Cycle through available characters - **Click encoder**: Confirm character selection - **Wait 3 seconds**: Exit character selection mode Yes, you could write code with just the click wheel and the OLED if you really wanted to. [Image: 1760676653009] --- ## OLED Display Support If you have an OLED connected, the File Manager shows: - **Current path** and **selected file** - **File navigation** with scrolling support - **Real-time updates** as you navigate --- ### MicroPython Examples The File Manager automatically creates example Python scripts in `/python_scripts/examples/`: #### Basic Hardware Examples - [**adc_basics.py**](https://github.com/Architeuthis-Flux/JumperlOS/blob/main/scripts/ex/adc_basics.py): Basic ADC (Analog-to-Digital Converter) operations. - This example shows how to read analog voltages from all ADC channels (0-3). Connect voltage sources to ADC inputs and monitor readings in real-time. - [**dac_basics.py**](https://github.com/Architeuthis-Flux/JumperlOS/blob/main/scripts/ex/dac_basics.py): Basic DAC (Digital-to-Analog Converter) operations. - Shows how to set DAC voltages on all channels (DAC_A, DAC_B, TOP_RAIL, BOTTOM_RAIL). - Hardware setup: Connect voltmeter or LED to DAC output pins. - [**gpio_basics.py**](https://github.com/Architeuthis-Flux/JumperlOS/blob/main/scripts/ex/gpio_basics.py): Basic GPIO (General Purpose Input/Output) operations. - This example demonstrates digital I/O, direction control, and pull resistors. - Tests input mode with pull-up, pull-down, and floating configurations. - [**node_connections.py**](https://github.com/Architeuthis-Flux/JumperlOS/blob/main/scripts/ex/node_connections.py): Node connection and routing operations. - This example shows how to connect/disconnect nodes, check connections, and clear all connections. - Demonstrates working with breadboard nodes, DAC outputs, and GPIO pins. - - [**uart_loopback.py**](https://github.com/Architeuthis-Flux/JumperlOS/blob/main/scripts/ex/uart_loopback.py): UART Loopback Demo. - Demonstrates UART communication by looping back data from UART_TX to UART_RX. - Open a serial monitor on the Jumperless's second port at 115200 baud to see the looped messages. #### Interactive Examples - [**interaction_demo.py**](https://github.com/Architeuthis-Flux/JumperlOS/blob/main/scripts/ex/interaction_demo.py): Interactive Demo - Control connections with probe, encoder, and buttons. - This example shows how to use all the interactive controls together. - No special hardware needed - use the probe to tap nodes, the encoder to adjust bridge spread, and buttons to change colors. - [**led_brightness_control.py**](https://github.com/Architeuthis-Flux/JumperlOS/blob/main/scripts/ex/led_brightness_control.py): LED Brightness Control Demo. - Tap breadboard pads 1-60 to control the voltage on an LED and display the current draw. - Hardware setup: Connect LED anode to breadboard row 15, connect LED cathode to GND. - Displays voltage and current on OLED. - [**stylophone.py**](https://github.com/Architeuthis-Flux/JumperlOS/blob/main/scripts/ex/stylophone.py): Jumperless Stylophone. - Musical instrument using probe and GPIO to generate audio tones. - Hardware setup: Connect speaker between rows 25 (positive) and 55 (negative). - Touch breadboard pads to play different frequencies, use probe buttons to adjust sustain. You can trigger them to regenerate if you messed them up by deleting it with `x` (when you're in the File Manager), and then entering `m` to create new copies of any examples it doesn't see. ## Editing Slot Files Slot files (located in `/slots/`) use **YAML format** and can be edited directly! They're human-readable files containing: - **bridges** - Your circuit connections - **power** - Rail and DAC voltages - **colors** - Wire colors from Wokwi or custom colors - **config** - Routing preferences and GPIO settings **Example slot file:** ```yaml version: 2 sourceOfTruth: bridges bridges: - {n1: 1, n2: 10, dup: 2, color: red} - {n1: NANO_D5, n2: GPIO_1, dup: 2} - {n1: TOP_RAIL, n2: 5, dup: 2} power: topRail: 3.30 bottomRail: 2.50 dac0: 3.33 dac1: 0.00 ``` **Named nodes** you can use: `NANO_D0-D13`, `NANO_A0-A7`, `GPIO_1-8`, `TOP_RAIL`, `BOTTOM_RAIL`, `GND`, `DAC0_5V`, `DAC1_5V`, and more (see [glossary](99-glossary.md)) When you edit and save a slot file, the Jumperless will automatically reload it if it's the active slot. This works whether you're using the onboard eKilo editor or have the Jumperless mounted as a USB Mass Storage drive and are editing the files on your computer in you favorite editor. --- ## USB Mass Storage Enter `U` in the menu and Jumperless will mount as a USB Mass Storage drive called `JUMPERLESS` where you can edit files on the filesystem. Keep in mind that file operations are pretty slow, so make sure to give it time to fully save files when you drop them onto the filesystem. When you're finished `u` (or just eject the drive) will unmount the Mass Storage device. You can also enter `Z` for a little debug menu --- ## Navigation Reference | Key | Action | |-----|--------| | ↑/↓ | Move selection | | Enter | Open/Edit | | / | Go to root | | . | Go up directory | | h | Show help | --- ### File Operations | Key | Action | |-----|--------| | v | View file | | e | Edit file | | i | File info | | n | New file | | d | New directory | | x | Delete | | r | Refresh | --- ### System | Key | Action | |-----|--------| | u | Memory status | | m | Initialize examples | | Ctrl+Q | Quit | --- # MicroPython This guide covers how to write, load, and run Python scripts that control Jumperless hardware using the embedded MicroPython interpreter. If you just want an overview of all the available calls, check out the [**MicroPython API Reference**](09.5-micropythonAPIreference.md) For stuff that's not Jumperless-specific, check out the [MicroPython Docs](https://docs.micropython.org/en/latest/index.html) ## Now you can live code with [JumperIDE](https://ide.jumperless.org/)! Holy shit I should have done this years ago Seriously, this is *such* a better experience than using the onboard text editor and REPL, you should play with it right now Go to [https://ide.jumperless.org/](https://ide.jumperless.org/) and press the connect button. Choose the 3rd Jumperless port in that list (Windows may not put them in order, so if nothing happens, try the other ones) and click Connect Then open some examples (this update should overwrite the examples with the new ones) and hit the Run / Stop button Press it again to Stop. If you make changes, hit the green Save button next to it (it takes a second and the script should be stopped.) ### If you write something cool, send it to me and I'll add it to the default examples (I'll put a page on this site soon where you can share them.) This is using [MicroPython's built-in Raw REPL](https://docs.micropython.org/en/latest/reference/repl.html#raw-mode-and-raw-paste-mode), so anything that can interact with that will work here. I've only tested with Viper IDE but I'm pretty sure just about anything else would work. There's also `jumperless.py` and `jumperless.pyi` module with stubs for all the built-in functions so syntax highlighting and autocomplete will work in your favorite code editor (sorry, autocomplete for jumperless functions doesn't work in ViperIDE.) You can grab them here: ### [jumperless.py](https://github.com/Architeuthis-Flux/JumperlOS/blob/main/scripts/jumperless.py) ### [jumperless.pyi](https://github.com/Architeuthis-Flux/JumperlOS/blob/main/scripts/jumperless.pyi) --- ## Quick Start (to do it from the built-in REPL) From the main Jumperless menu, press `p` to enter the MicroPython REPL: [Image: Screenshot 2025-07-04 at 7 03 24 PM] ## REPL Navigation Up / Down arrow keys on a blank prompt will scroll through history, any other key will break out of history mode and enter multiline editing. So you can use arrow keys to navigate and edit the script. In history mode, the `>>>` prompts will be pink, when you're editing, they'll be blue. ## Hardware Control Functions All Jumperless hardware functions are automatically imported into the global namespace - no prefix is actually necessary, but it's probably good to use `import jumperless as j` when using Viper IDE or something so it doesn't complain about not undefined names. --- ## Basic Script Structure ```jython """ My Jumperless Script Description of what this script does """ print("Starting my script...") # Connect some nodes connect(1, 5) connect(2, 6) # Set up GPIO gpio_set_dir(1, True) # Output gpio_set_dir(2, False) # Input # Main loop for i in range(10): gpio_set(1, True) time.sleep(0.5) gpio_set(1, False) time.sleep(0.5) # Read input (gpio_get returns truthy for HIGH, falsy for LOW) if gpio_get(2): print("Button pressed!") # Cleanup nodes_clear() print("Script complete!") ``` ## Loading and Running Scripts ### Method 1 (Recommended): [Viper IDE](https://viper-ide.blackhart.dev/) See [above](#now-you-can-live-code-with-viper-ide) for instructions. It's at the top of the page for a reason, it's awesome. ### Method 2: File Manager From the REPL (enter `p` in the main menu), then type `files` to open the file manager: ```jython >>> files ``` Navigate to your script and press Enter to load it for editing, then press `Ctrl+P` to load it into the REPL for execution. **Note:** The standard Python `exec(open(...).read())` method is not supported in the Jumperless MicroPython environment. Always use the file manager and `Ctrl+P` to run scripts. ### Method 3: REPL Commands From the MicroPython REPL, you can use the following commands to manage scripts: ```jython # Load script into editor for modification load my_script.py # Save current session as script save my_new_script.py ``` ### Method 4: Direct Execution From the main Jumperless menu, you can execute single commands: ```jython > gpio_set(1, True) > adc_get(0) > connect(1, 5) ``` ## REPL (Interactive Mode) ### Starting REPL From main menu: Press `p` ### REPL Commands ```jython CTRL + q - Exit REPL history - Show command history and saved scripts save [name] - Save last executed script load - Load script by name or number files - Open file manager new - Create new script with eKilo editor helpl - Show REPL help help() - Show hardware commands ``` ### Navigation ``` ↑/↓ arrows - Browse command history ←/→ arrows - Move cursor, edit text TAB - Add 4-space indentation Enter - Execute (empty line in multiline to finish) Ctrl+Q - Force quit REPL or interrupt running script ``` ### Multiline Auto-Indent Mode The REPL automatically detects when you need multiple lines after a `:` ```jython >>> def blink_led(): ... for i in range(5): ... gpio_set(1, True) ... time.sleep(0.5) ... gpio_set(1, False) ... time.sleep(0.5) ... >>> blink_led() ``` If you want to use *real* multiline mode, use the Kilo file editor. ### Command History - Use ↑/↓ arrows to browse previous commands - Commands are automatically saved - Type `history` to see all saved scripts ## Connection Context Switching The MicroPython REPL now supports **connection contexts** that determine how connections persist: - **`global` context**: Changes persist to global state - connections remain after exiting Python - **`python` context**: Connections are restored to how they were when exiting REPL (saved to `slots/slotPython.yaml`) **To toggle contexts:** Type `context` in the REPL **How it works:** - In `global` mode: Any connections you make become permanent, just like using the normal command interface - In `python` mode: The connection state when you entered the REPL is saved, and restored when you exit - The current context is displayed in the REPL prompt ## Built-in Examples The system includes several example scripts. To run an example: 1. Type `files` in the REPL. 2. Navigate to the `examples/` directory. 3. Select the desired script and press Enter to edit/view it. 4. Press `Ctrl+P` to load it into the REPL for execution. Example scripts include: - `dac_basics.py` - `adc_basics.py` - `gpio_basics.py` - `node_connections.py` - `led_brightness_control.py` - `stylophone.py` - `uart_basics.py` - `uart_loopback.py` - `interaction_demo.py` - `test_neopixel.py` - `fake_gpio.py` **REPL not responding:** - Press Ctrl+Q to force quit - Unplug / replug your Jumperless (don't worry, almost everything is persistent) ## Formatted Output and Custom Types The Jumperless module returns custom types that print nicely but also work in conditionals: ```jython # GPIO functions return custom types that print as readable strings state = gpio_get(1) # Prints "HIGH", "LOW", or "FLOATING" direction = gpio_get_dir(1) # Prints "INPUT" or "OUTPUT" pull = gpio_get_pull(1) # Prints "PULLUP", "PULLDOWN", or "NONE" # These types are also truthy/falsy for use in conditionals: if gpio_get(1): # True if HIGH, False if LOW or FLOATING print("Pin is HIGH") if gpio_get_dir(1): # True if OUTPUT, False if INPUT print("Pin is output") # Connection status works the same way connected = is_connected(1, 5) # Prints "CONNECTED" or "DISCONNECTED" if connected: # True if connected, False if not print("Nodes are connected") # Voltage and current readings are floats voltage = adc_get(0) # Returns float (e.g., 3.300) current = ina_get_current(0) # Returns float in A (e.g., 0.0123) power = ina_get_power(0) # Returns float in W (e.g., 0.4567) # All functions work with both numbers and string aliases gpio_set_dir("GPIO_1", True) # Same as gpio_set_dir(1, True) connect("TOP_RAIL", "GPIO_1") # Same as connect(101, 131) ``` --- # MicroPython API Reference [Node Connections](#node-connections) (Connecting stuff with the Jumperless) - *[Examples](#node-connections)*: - `connect(node1, node2, [duplicates=-1])` - Connect two nodes (duplicates: -1=add, N=force N) - `disconnect(node1, node2)` - Disconnect nodes (`node2` = -1 clear everything connected to `node1`) - `fast_connect(node1, node2, [duplicates=-1])` - Connect, skip LED update - `fast_disconnect(node1, node2)` - Disconnect, skip LED update - `is_connected(node1, node2)` - Check if nodes are connected - `nodes_clear()` - Clear all connections [DAC](#dac-digital-to-analog-converter) (Digital-to-Analog Converter) - *[Examples](#dac-digital-to-analog-converter)*: - `dac_set(channel, voltage)` - Set DAC output voltage - `dac_get(channel)` - Get DAC output voltage [ADC](#adc-analog-to-digital-converter) (Analog-to-Digital Converter) - *[Examples](#adc-analog-to-digital-converter)*: - `adc_get(channel)` - Read ADC input voltage [GPIO](#gpio-general-purpose-inputoutput) (General Purpose Input / Output from the RP2350B) - *[Examples](#gpio-general-purpose-inputoutput)*: - `gpio_set(pin, value)` - Set GPIO pin state - `gpio_get(pin)` - Read GPIO pin state - `gpio_set_dir(pin, direction)` - Set GPIO pin direction - `gpio_get_dir(pin)` - Get GPIO pin direction - `gpio_set_pull(pin, pull)` - Set GPIO pull-up/down - `gpio_get_pull(pin)` - Get GPIO pull-up/down [INA](#ina-currentpower-monitor) (Current/Power Monitor) - *[Examples](#ina-currentpower-monitor)*: - `ina_get_current(sensor)` - Read current in amps - `ina_get_voltage(sensor)` - Read shunt voltage - `ina_get_bus_voltage(sensor)` - Read bus voltage - `ina_get_power(sensor)` - Read power in watts [PWM](#pwm-pulse-width-modulation) (Pulse Width Modulation) - *[Examples](#pwm-pulse-width-modulation)*: - `pwm(pin, [frequency], [duty])` - Setup PWM on GPIO pin - `pwm_set_duty_cycle(pin, duty)` - Set PWM duty cycle - `pwm_set_frequency(pin, freq)` - Set PWM frequency - `pwm_stop(pin)` - Stop PWM on pin [OLED Display](#oled-display) (Organic Light Emitting Diode 128 x 32 pixel display) - *[Examples](#oled-display)*: - `oled_print("text")` - Display text - `oled_clear()` - Clear display - `oled_connect()` - Connect OLED - `oled_disconnect()` - Disconnect OLED [Graphic Overlays](#graphic-overlays) (Directly controlling the breadboard LEDs) - *[Examples](#graphic-overlays)*: - `overlay_set(name, row, col, w, h, colors)` - Create/update overlay - `overlay_clear(name)` - Remove overlay - `overlay_clear_all()` - Remove all overlays - `overlay_shift(name, dRow, dCol)` - Move overlay relative - `overlay_place(name, row, col)` - Move overlay absolute - `overlay_set_pixel(row, col, color)` - Set single pixel - `overlay_count()` - Get active overlay count - `overlay_serialize()` - Get overlays as YAML [Probe Functions](#probe) (Probe status / actions)=L=e==ft - *[Examples](#probe)*: - `probe_read_blocking()` - Wait for probe touch (don't return until a pad is touched) - `probe_read_nonblocking()` - Check probe immediately (return -1 if no pad is touched) - `get_button([blocking=True])` - Get button state (default: blocking) - `probe_button([blocking=True])` - Get button state (default: blocking) - `probe_button_blocking()` - Wait for button press - `probe_button_nonblocking()` - Check buttons immediately - `check_button()` - Check for probe button presses - `get_switch_position()` - Get probe switch position (0=measure, 1=select, -1=unknown) - `set_switch_position(position)` - Set switch position manually - `check_switch_position()` - Check switch via current sensing - `probe_autoconnect([enable])` - Get/set probe auto-connect (True/False, temporary until reboot) [Clickwheel](#clickwheel) (Rotary Encoder / button on the top left) - *[Examples](#clickwheel)*: - `clickwheel_get_position()` - Get raw position counter - `clickwheel_reset_position()` - Reset position to 0 - `clickwheel_get_direction([consume])` - Get direction event (NONE/UP/DOWN) - `clickwheel_get_button()` - Get button state [Fake GPIO](#fake-gpio-virtual-gpio-pins) (Multiplex an ADC to read up to 32 inputs) - *[Examples](#fake-gpio-virtual-gpio-pins)*: - `FakeGpioPin(node, [mode], [v_high], [v_low])` - Create virtual GPIO - `pin.value([val])` - Control pin [Net Information](#net-information-api) (Ask the Jumperless about `Nets`) - *[Examples](#net-information-api)*: - `get_net_name(netNum)` - Get the name of a net - `set_net_name(netNum, name)` - Set a custom net name - `get_net_color(netNum)` - Get net color as 0xRRGGBB - `get_net_color_name(netNum)` - Get net color as name string - `set_net_color(netNum, color)` - Set net color by name or RGB - `set_net_color_hsv(netNum, h, [s], [v])` - Set net color by HSV (auto-detects 0-1 or 0-255) - `get_num_nets()` - Get number of active nets - `get_num_bridges()` - Get number of bridges - `get_net_nodes(netNum)` - Get comma-separated node list - `get_bridge(bridgeIdx)` - Get bridge info tuple - `get_net_info(netNum)` - Get full net info as dict [Path Query API](#path-query-api) (Info about the internal routing) - *[Examples](#path-query-api)*: - `get_num_paths([include_duplicates])` - Get number of routing paths - `get_path_info(path_idx)` - Get detailed path info as dict - `get_all_paths()` - Get all paths as list of dicts - `get_path_between(node1, node2)` - Query specific path between nodes [JFS](#jfs-jumperless-filesystem) (Jumperless File System) - *[Examples](#jfs-jumperless-filesystem)*: - `jfs.open(path, mode)` - Opens file, returns file handle - `jfs.read(file, size=1024)` - Read from file - `jfs.write(file, data)` - Write to file - `jfs.close(file)` - Close file - `jfs.seek(file, position, whence=0)` - Seek in file - `jfs.tell(file)` - Get current position - `jfs.size(file)` - Get file size - `jfs.available(file)` - Get bytes available - `jfs.exists(path)` - Check if path exists (returns True/False) - `jfs.listdir(path)` - List directory contents (returns list) - `jfs.mkdir(path)` - Create directory - `jfs.rmdir(path)` - Remove directory - `jfs.remove(path)` - Remove file - `jfs.rename(from, to)` - Rename/move file - `jfs.stat(path)` - Get file/directory status info - `jfs.info()` - Returns (total, used, free) tuple - - `file.print(data)` - Print to the file (like `write` but auto-flushes) - - `file.flush()` - Flush buffered data to file - - `file.position()` - Alias for `f.tell()` - - `file.name()` - Get file name [Status](#status-functions) (Print status stuff to main Serial) - *[Examples](#status-functions)*: - `print_bridges()` - Print all bridges - `print_paths()` - Print path between nodes - `print_crossbars()` - Print crossbar array - `print_nets()` - Print nets - `print_chip_status()` - Print chip status [Misc / System Functions](#system-functions) (Things that don't have their own category) - *[Examples](#system-functions)*: - `arduino_reset()` - Reset Arduino - `run_app('appName')` - Run app - `pause_core2(pause)` - Pause/resume core2 processing - `send_raw(chip, x, y, setOrClear)` - Send raw data to core2 - `change_terminal_color(color, [flush])` - Set terminal color (0-255) - `cycle_term_color([reset], [step], [flush])` - Cycle through colors - `force_service(name)` - Force run a service by name - `force_service_by_index(idx)` - Force run service by index (faster) - `get_service_index(name)` - Get service index for caching - `context_toggle()` - Toggle connection context (global/python) - `context_get()` - Get current context name - `nodes_save([slot])` - Save connections to slot - `nodes_discard()` - Discard unsaved changes - `nodes_has_changes()` - Check for unsaved changes - `switch_slot(slot)` - Switch to a different slot - `get_state()` - Get complete board state as JSON string - `set_state(json, [clear_first=True], [from_wokwi=False])` - Apply complete state from JSON string; set `from_wokwi` to True to parse Wokwi diagram [Help](#help-functions) - *[Examples](#the-entire-output-of-help)*: - `help()` - Display help - `nodes_help()` - Show available nodes and how to address them --- ## A Note on Usage All functions and constants from the `jumperless` module are automatically imported into the global namespace. This means you can call them directly (e.g., `connect(1, 5)`) without needing the `jumperless.` prefix. There are three primary ways to specify nodes in functions: 1. **By Number**: Use the integer corresponding to the breadboard row (1-60). 2. **By String Name**: Use a case-insensitive string for any named node (e.g., `"d13"`, `"TOP_RAIL"`). 3. **By Constant**: Use the predefined, case-sensitive constant for a node (e.g., `D13`, `TOP_RAIL`). --- ## Node Connections These functions manage the connections between nodes on the breadboard and special function pins. ### `connect(node1, node2, [duplicates=-1])` Creates a bridge between two nodes. * `node1`, `node2`: The nodes to connect. Can be integers, strings, or constants. * `duplicates` (optional): Controls duplicate connection behavior: * `-1` (default): Just add the connection without managing duplicates (standard behavior) * `0`: Force exactly 0 duplicates (removes any existing duplicate paths) * `1+`: Force exactly N duplicates (adds or removes connections to reach that count) **Example:** ```jython # Connect breadboard row 1 to row 30 connect(1, 30) # Connect Arduino D13 to the top power rail connect(D13, TOP_RAIL) # Connect GPIO 1 to ADC 0 using strings connect("GPIO_1", "ADC0") # Duplicate management examples connect(1, 5, duplicates=0) # Ensure no duplicate paths connect(1, 5, duplicates=2) # Force exactly 2 parallel paths connect(1, 5) # Standard: just add the connection ``` ### `disconnect(node1, node2)` Removes a specific bridge between two nodes. * `node1`, `node2`: The two nodes to disconnect. * To remove all connections from a single node, set `node2` to `-1`. **Example:** ```jython # Remove the bridge between rows 1 and 30 disconnect(1, 30) # Remove all connections from GPIO_1 disconnect(GPIO_1, -1) ``` ### `is_connected(node1, node2)` Checks if a direct or indirect connection exists between two nodes. * Returns a custom `ConnectionState` object which evaluates to `True` if connected (`CONNECTED`) and `False` if not (`DISCONNECTED`). **Example:** ```jython if is_connected(D13, TOP_RAIL): print("D13 is connected to the top rail.") state = is_connected(1, 2) print(state) # Prints "CONNECTED" or "DISCONNECTED" ``` ### `nodes_clear()` Removes all connections from the board. **Example:** ```jython nodes_clear() print("All connections cleared.") ``` ### `node(name_or_id)` Creates a node object from a string name or integer ID. This is useful for storing a node reference in a variable. **Node Type:** `Node` objects support arithmetic and comparison operations with integers: * **Comparisons**: `==`, `!=`, ``, `>=` * **Arithmetic**: `+`, `-`, `*`, `//`, `%` * **Conversions**: `int(node)` converts to integer value **Example:** ```jython my_pin = node("D7") led_pin = node(15) connect(my_pin, led_pin) oled_print(my_pin) # Displays 'D7' on the OLED # Arithmetic and comparison work! if my_pin 10 MΩ sensors) and may cause a small leakage current or change the measured state. If your external circuit is super high‑impedance, either disable floating-read for that pin or add a defined pull resistor. ### `gpio_get_read_floating(pin)` Returns whether floating-read detection is enabled for a GPIO pin. * `pin`: The GPIO pin number (1-10). * Returns: `True` if floating-read is enabled, otherwise `False`. * **Aliases**: `get_gpio_read_floating()` **Example:** ```jython # Enable floating detection on GPIO 3 gpio_set_read_floating(3, True) # Confirm it is enabled print(gpio_get_read_floating(3)) # True or False # If a connected high‑impedance sensor behaves oddly, disable floating-read gpio_set_read_floating(3, False) ``` **Pinout:** * `1-8`: Routable GPIO pins `GPIO_1` to `GPIO_8`. * `9`: `UART_TX`. * `10`: `UART_RX`. **Example:** ```jython # Set GPIO 1 as an output and turn it on gpio_set_dir(1, True) gpio_set(1, True) # Set GPIO 2 as an input with a pull-up gpio_set_dir(2, False) gpio_set_pull(2, 1) # Read the state of GPIO 2 state = gpio_get(2) if state: # GPIOState is truthy when HIGH, falsy when LOW or FLOATING print("GPIO 2 is HIGH") # You can also print it directly - shows "HIGH", "LOW", or "FLOATING" print("State: " + str(state)) ``` --- ## PWM (Pulse-Width Modulation) Functions for generating PWM signals on GPIO pins. ### `pwm(pin, [frequency], [duty_cycle])` Sets up and starts a PWM signal on a GPIO pin. * `pin`: The GPIO pin to use (1-8). * `frequency` (optional): The PWM frequency in Hz (0.001 to 62500000). Defaults to 1000. * `duty_cycle` (optional): The duty cycle from 0.0 to 1.0. Defaults to 0.5. * **Aliases**: `set_pwm()` **Frequency Ranges:** * **Hardware PWM**: 10Hz to 62.5MHz (high precision, hardware peripheral) * **Slow PWM**: 0.001Hz to 10Hz (hardware timer based, precise timing) * **Automatic Selection**: System automatically chooses the appropriate mode based on frequency ### `pwm_set_duty_cycle(pin, duty_cycle)` Changes the duty cycle of an existing PWM signal. * `pin`: The GPIO pin number (1-8). * `duty_cycle`: The new duty cycle (0.0 to 1.0). * **Aliases**: `set_pwm_duty_cycle()` ### `pwm_set_frequency(pin, frequency)` Changes the frequency of an existing PWM signal. * `pin`: The GPIO pin number (1-8). * `frequency`: The new frequency in Hz (0.001 to 62500000). * **Aliases**: `set_pwm_frequency()` ### `pwm_stop(pin)` Stops the PWM signal on a GPIO pin. * `pin`: The GPIO pin number (1-8). * **Aliases**: `stop_pwm()` **Example:** ```jython # Hardware PWM: 1kHz, 25% duty cycle on GPIO_1 pwm(GPIO_1, 1000, 0.25) # Slow PWM: 0.1Hz (10 second period), 50% duty cycle on GPIO_2 pwm(GPIO_2, 0.1, 0.5) # Ultra-slow PWM: 0.001Hz (1000 second period), 25% duty cycle on GPIO_3 pwm(GPIO_3, 0.001, 0.25) # Change the duty cycle to 75% pwm_set_duty_cycle(GPIO_1, 0.75) # Change frequency (will automatically switch between hardware/slow PWM) pwm_set_frequency(GPIO_1, 500) # 500Hz (hardware PWM) pwm_set_frequency(GPIO_1, 5) # 5Hz (slow PWM) # Stop the PWM signal pwm_stop(GPIO_1) ``` --- ## Graphic Overlays Functions for creating and manipulating 2D color overlays on the breadboard LEDs. The breadboard is addressed as a **10-row × 30-column grid**: * **Row 1-5**: Top half (A-E) * **Row 6-10**: Bottom half (F-J) * **Column 1-30**: Breadboard columns 1-30 ### `overlay_set(name, x, y, height, width, colors)` Creates or updates a graphic overlay. * `name`: Unique string identifier for the overlay. * `x`: Starting column (**1-30**). Matches breadboard column labels. * `y`: Starting row (**1-10**). 1-5 = Top half (A-E), 6-10 = Bottom half (F-J). * `height`: Height in rows (y-dimension). * `width`: Width in columns (x-dimension). * `colors`: List of 32-bit integer colors (0xRRGGBB). Can be a flat list or 2D list (rows). * Returns the overlay index. **Note:** If you pass a 2D array (list of lists) for `colors`, the outer list represents rows (height) and the inner lists represent columns (width). Make sure your `width` and `height` arguments match the data shape. **Example:** ```jython # Create a 5-wide, 2-high overlay at x=2, y=5 # x=2 (Column 2), y=5 (Row 5/E) colors_2d = [ [0x550000, 0x550000, 0x550000, 0x550000, 0x550000], # Row 0 [0x550000, 0x550000, 0x550000, 0x550000, 0x550000] # Row 1 ] overlay_set("box_2d", 12, 5, 2, 5, colors_2d) ``` ### `overlay_clear(name)` Removes a specific overlay. * `name`: The identifier of the overlay to remove. * Returns `1` if found/removed, `0` otherwise. ### `overlay_clear_all()` Removes ALL active overlays. ### `overlay_shift(name, dx, dy)` Moves an overlay by a relative offset. Wraps around edges. * `name`: Overlay identifier. * `dx`: Column delta (e.g., 1 for right, -1 for left). * `dy`: Row delta (e.g., 1 for down, -1 for up). * Returns `1` if found, `0` otherwise. ### `overlay_place(name, x, y)` Moves an overlay to a specific absolute position. Wraps around edges. * `name`: Overlay identifier. * `x`: New column (**1-30**). * `y`: New row (**1-10**). * Returns `1` if found, `0` otherwise. **Example:** ```jython # Use the probe to move the box overlay import jumperless as j import time colors_2d = [ [0x550000, 0x104000, 0x005500, 0x001040, 0x000055], # Row 0 [0x550000, 0x104000, 0x005500, 0x001040, 0x000055], # Row 1 ] j.overlay_set("box_2d", 12, 5, 2, 5, colors_2d) while True: node = j.probe_read_blocking() j.overlay_place("box_2d",node, 5) time.sleep(0.1) ``` ### `overlay_set_pixel(x, y, color)` Sets a single pixel directly (convenience wrapper). * `x`: Column (1-30). * `y`: Row (1-10). * `color`: 0xRRGGBB color. ### `overlay_serialize()` Returns the current state of all overlays as a JSON string. --- ## INA (Current/Power Monitor) Functions for reading data from the INA219 current sensors. ### `ina_get_current(sensor)` Reads the current in Amps. * `sensor`: The sensor to read (0 or 1). * **Aliases**: `get_current()` ### `ina_get_voltage(sensor)` Reads the shunt voltage in Volts. * `sensor`: The sensor to read (0 or 1). * **Aliases**: `get_voltage()` ### `ina_get_bus_voltage(sensor)` Reads the bus voltage in Volts. * `sensor`: The sensor to read (0 or 1). * **Aliases**: `get_bus_voltage()` ### `ina_get_power(sensor)` Reads the power in Watts. * `sensor`: The sensor to read (0 or 1). * **Aliases**: `get_power()` **Example:** ```jython current_mA = ina_get_current(0) * 1000 print("Current: " + str(current_mA) + " mA") ``` --- ## OLED Display Functions for controlling the onboard OLED display with advanced text, graphics, and bitmap capabilities. ### Basic Display Functions #### `oled_print(text, [size=-1])` Displays text on the OLED screen. It can print strings, numbers, and custom Jumperless types. * `text`: The content to display. * `size` (optional): Text size (0=small scrolling, 1=normal, 2=large). If -1 or omitted, uses the default size set by `oled_set_text_size()`. Defaults to 2. **Size Modes:** - **Size 0**: Small scrolling text (perfect for terminal-like output with multiple lines) - **Size 1**: Normal centered text - **Size 2**: Large centered text (default) **Example:** ```jython oled_print("Hello!") # Uses default size oled_print("Big Text", 2) # Large text oled_print("Scrolling", 0) # Small scrolling text ``` #### `oled_clear([show=True])` Clears the OLED display. * `show` (optional): If `True` (default), automatically calls `oled_show()` after clearing. Set to `False` for animations to avoid flashing between frames. **Example:** ```jython # Normal use - clears and shows oled_clear() # Animation loop - clear without showing to prevent flashing for frame in range(100): oled_clear(False) # Clear without show # Draw your frame... oled_show() # Show once after drawing ``` #### `oled_show()` Refreshes the OLED display to show the latest changes. **Note:** Usually not needed as `oled_print()` handles display updates automatically. Required after `oled_set_pixel()` calls. #### `oled_connect()` Connects the I2C lines to the OLED display. #### `oled_disconnect()` Disconnects the I2C lines from the OLED display. ### Text Size Control #### `oled_set_text_size(size)` Set the default text size for all subsequent `oled_print()` calls. * `size`: Text size (0=small scrolling, 1=normal, 2=large) * Returns: `True` if successful, `False` if invalid size **Example:** ```jython import jumperless as j # Use small scrolling text for terminal output j.oled_set_text_size(0) for i in range(10): j.oled_print(f"Line {i+1}") # Each creates a new scrolling line # Switch to large text j.oled_set_text_size(2) j.oled_print("BIG TEXT") ``` #### `oled_get_text_size()` Get the current default text size. * Returns: Current text size (0, 1, or 2) **Example:** ```jython current_size = oled_get_text_size() print(f"Current OLED text size: {current_size}") ``` ### Print Redirection #### `oled_copy_print(enable)` Enable or disable copying Python `print()` output to the OLED display in real-time. When enabled, all `print()` statements will appear on both the serial console and the OLED in small scrolling text mode. This is perfect for debugging without a serial connection. * `enable`: `True` to enable, `False` to disable **Example:** ```jython import jumperless as j # Enable print copying j.oled_copy_print(True) # These appear on both serial AND OLED print("Starting test...") voltage = j.adc_get(0) print(f"Voltage: {voltage:.2f}V") print("Test complete!") # Disable print copying j.oled_copy_print(False) print("This only goes to serial") ``` ### Font System #### `oled_get_fonts()` Get a list of all available font families. * Returns: List of font family names (strings) **Available Fonts:** 1. Eurostyle - sans serif, futuristic, like Xenon pinball or Star Trek 2. Jokerman - decorative font with lots of extra artistic dots and bars 3. Comic Sans - sans serif, comic book style lettering 4. Courier New - slab serif, typewriter style 5. New Science - sans serif, humanist/geometric, like Helvetica 6. New Science Ext - wider version of New Science 7. Iosevka Regular - fixed spacing sans serif, like Lucida, slashed zero 8. Berkeley Mono - fixed spacing, sans serif, large X height 9. Pragmatism - sans serif, vaguely romantic 10. Andale Mono - fixed spacing sans serif, used for small text (Descriptions by MadBodger) **Example:** ```jython import jumperless as j import time fonts = j.oled_get_fonts() print(f"Available fonts: {fonts}") # Display each font for font in fonts: j.oled_set_font(font) j.oled_print(font, 2) time.sleep(1.5) ``` #### `oled_set_font(name)` Set the current font family by name. The font will remain active until changed. * `name`: Font family name (case-insensitive) * Returns: `True` if successful, `False` if font not found **Example:** ```jython import jumperless as j # Set to Jokerman if j.oled_set_font("Jokerman"): j.oled_print("Fun Font!", 2) else: print("Font not found") # Set to Courier for code-like display j.oled_set_font("Courier New") j.oled_print("Code Style", 2) ``` #### `oled_get_current_font()` Get the name of the currently active font family. * Returns: Current font family name (string) **Example:** ```jython import jumperless as j current = j.oled_get_current_font() print(f"Current font: {current}") # Save and restore font saved_font = j.oled_get_current_font() j.oled_set_font("Jokerman") j.oled_print("Temporary", 2) j.oled_set_font(saved_font) # Restore original ``` ### Bitmap Display #### `oled_load_bitmap(filepath)` Load a bitmap file into the internal bitmap buffer. * `filepath`: Path to bitmap file (e.g., "/images/logo.bin") * Returns: `True` if loaded successfully, `False` on error **Bitmap File Format:** - **Raw Format**: Pure bitmap data (guesses dimensions from file size) - 128x32 = 512 bytes - 128x64 = 1024 bytes - 64x32 = 256 bytes - **Custom Format**: 4-byte header + bitmap data - Bytes 0-1: Width (16-bit little-endian) - Bytes 2-3: Height (16-bit little-endian) - Remaining: Bitmap data (1 bit per pixel, packed) **Example:** ```jython if oled_load_bitmap("/images/jogo32h.bin"): print("Logo loaded!") else: print("Failed to load logo") ``` #### `oled_display_bitmap(x, y, width, height, [data=None])` Display a bitmap on the OLED. **Two modes:** 1. **Use loaded bitmap**: If `data` is `None`, displays the bitmap from `oled_load_bitmap()` 2. **Direct data**: If `data` is provided, displays that bitmap immediately * `x`: X position on display (0-127) * `y`: Y position on display (0-31) * `width`: Bitmap width in pixels (ignored if using loaded bitmap) * `height`: Bitmap height in pixels (ignored if using loaded bitmap) * `data` (optional): Bitmap data to display directly * Returns: `True` if successful, `False` on error **Example:** ```jython import jumperless as j # Method 1: Load then display j.oled_load_bitmap("/images/logo.bin") j.oled_display_bitmap(0, 0, 0, 0) # width/height ignored for loaded bitmap # Method 2: Display direct data bitmap_data = bytes([0xFF, 0x00, 0xFF, 0x00] * 128) # Striped pattern j.oled_display_bitmap(0, 0, 128, 32, bitmap_data) ``` #### `oled_show_bitmap_file(filepath, x, y)` Convenience function that loads and displays a bitmap in one call. * `filepath`: Path to bitmap file * `x`: X position on display * `y`: Y position on display * Returns: `True` if successful, `False` on error **Example:** ```jython import jumperless as j import time # One-liner to show a logo j.oled_show_bitmap_file("/images/jogo32h.bin", 0, 0) time.sleep(2) # Show another image j.oled_show_bitmap_file("/images/badge.bin", 32, 8) ``` ### Framebuffer and Pixel Manipulation The framebuffer functions provide low-level access to the display memory, enabling advanced graphics, animations, and screen capture. **Framebuffer Format:** - 1 bit per pixel (0=black/off, 1=white/on) - Organized in vertical bytes (8 pixels per byte) - Size: 512 bytes (128x32) or 1024 bytes (128x64) - Compatible with Adafruit SSD1306 format #### `oled_get_framebuffer()` Get a copy of the current OLED framebuffer as a bytes object. * Returns: Framebuffer data as `bytes` (512 or 1024 bytes depending on display size) #### `oled_set_framebuffer(data)` Set the entire OLED framebuffer from bytes or bytearray. * `data`: Framebuffer data (must be correct size for display) * Returns: `True` if successful, `False` if wrong size **Example:** ```jython import jumperless as j import time # Capture current display fb = j.oled_get_framebuffer() print(f"Framebuffer size: {len(fb)} bytes") # Save to file for later with open("/screen_capture.bin", "wb") as f: f.write(fb) j.oled_clear() time.sleep(2) # Load and display saved screen with open("/screen_capture.bin", "rb") as f: fb_data = f.read() if j.oled_set_framebuffer(fb_data): print("Screen restored!") else: print("Wrong framebuffer size") ``` #### `oled_get_framebuffer_size()` Get the dimensions and size of the framebuffer. * Returns: Tuple of `(width, height, buffer_size_in_bytes)` **Example:** ```jython width, height, size = oled_get_framebuffer_size() print(f"Display: {width}x{height}, {size} bytes") # Output: Display: 128x32, 512 bytes # Calculate pixels total_pixels = width * height print(f"Total pixels: {total_pixels}") ``` #### `oled_set_pixel(x, y, color)` Set a single pixel on the OLED. **Note:** Call `oled_show()` after setting pixels to make changes visible. * `x`: X coordinate (0 to width-1) * `y`: Y coordinate (0 to height-1) * `color`: Pixel color (0=black/off, 1=white/on) * Returns: `True` if successful, `False` if OLED not connected **Example:** ```jython import jumperless as j # Draw a diagonal line j.oled_clear() for i in range(32): j.oled_set_pixel(i, i, 1) j.oled_show() # Draw a box for x in range(20, 108): j.oled_set_pixel(x, 10, 1) # Top edge j.oled_set_pixel(x, 22, 1) # Bottom edge for y in range(10, 23): j.oled_set_pixel(20, y, 1) # Left edge j.oled_set_pixel(107, y, 1) # Right edge j.oled_show() ``` #### `oled_get_pixel(x, y)` Get the color value of a single pixel. * `x`: X coordinate (0 to width-1) * `y`: Y coordinate (0 to height-1) * Returns: Pixel color (0=black/off, 1=white/on, -1=error) **Example:** ```jython # Check if a pixel is set pixel = oled_get_pixel(64, 16) if pixel == 1: print("Pixel is white/on") elif pixel == 0: print("Pixel is black/off") else: print("Error reading pixel") ``` ### Advanced OLED Examples #### Debug Output to OLED ```jython import jumperless as j import time # Enable print copying for debugging j.oled_copy_print(True) # Your code with debug output for i in range(10): voltage = j.adc_get(0) print(f"V{i}: {voltage:.2f}V") time.sleep(0.5) j.oled_copy_print(False) ``` #### Simple Oscilloscope ```jython import jumperless as j import math import time # Clear display j.oled_clear() width, height, _ = j.oled_get_framebuffer_size() j.connect(j.ADC0, 11) # Draw sine wave animation for offset in range(100): j.oled_clear(False) # Don't show() after clear to avoid flashing # Draw axes for x in range(width): if (x % 4 == 0): j.oled_set_pixel(x, height//2, 1) # Dotted center line # Draw sine wave for x in range(width): # y = int(height//2 + 10 * math.sin((x + offset) / 10)) # Dummy sine wave y = int(j.get_adc(0)) + 16 if 0 `, `>=` * **Arithmetic**: `+`, `-`, `*`, `//`, `%` * **Conversions**: `int(pad)` converts to integer value **Example:** ```jython pad = probe_read(False) # Non-blocking if pad != NO_PAD: print(pad) if pad 60: # Works! node_2 -= 60 connect(node_1, node_2) ``` ### `probe_button([blocking=True], [consume=False])` Reads the state of the buttons on the probe. * `blocking` (optional): If `True` (default), waits for a button press. If `False`, returns the current state immediately. * `consume` (optional): If `True`, clears the button press after reading (one-shot detection). If `False` (default), the button state persists while held, allowing repeated reads in a loop. * Returns a `ProbeButton` object (`CONNECT_BUTTON`, `REMOVE_BUTTON`, or `BUTTON_NONE`). * **Aliases**: `get_button()`, `button_read()`, `read_button()`, `probe_button_blocking()`, `probe_button_nonblocking()`, `check_button()`, `button_check()` **Consume Behavior:** - `consume=False` (default): Holding the button returns the same state repeatedly - **ideal for continuous control** (e.g., incrementing values while held) - `consume=True`: Each button press is detected only once - ideal for menu navigation or one-shot actions **Example:** ```jython # Continuous control - hold button to keep changing hue while True: button = check_button() # consume=False by default if button == BUTTON_CONNECT: hue += 1 # Keeps incrementing while button is held set_net_color_hsv(0, hue) elif button == BUTTON_REMOVE: hue -= 1 # Keeps decrementing while button is held set_net_color_hsv(0, hue) time.sleep(0.05) # One-shot detection - each press counted once presses = 0 while presses high threshold - Switches to MEASURE when current {menu_items[selected]}") elif direction == j.CLICKWHEEL_DOWN: selected = (selected - 1) % len(menu_items) print(f"> {menu_items[selected]}") # Check button button = j.clickwheel_get_button() if button == j.CLICKWHEEL_PRESSED: print(f"Selected: {menu_items[selected]}") elif button == j.CLICKWHEEL_DOUBLECLICKED: print("Exit menu") break # Note: Direction persists, so even with slow polling (50ms), # you won't miss turn events! time.sleep(0.05) ``` **Example - Value Adjustment with Persistence:** ```jython import jumperless as j import time value = 50 while True: # Check direction without consuming direction = j.clickwheel_get_direction(consume=False) if direction == j.CLICKWHEEL_UP: value = min(100, value + 1) j.oled_print(f"Value: {value}", 2) time.sleep(0.1) # Delay for visual feedback # Consume after displaying j.clickwheel_get_direction(consume=True) elif direction == j.CLICKWHEEL_DOWN: value = max(0, value - 1) j.oled_print(f"Value: {value}", 2) time.sleep(0.1) j.clickwheel_get_direction(consume=True) # Exit on button press if j.clickwheel_get_button() == j.CLICKWHEEL_PRESSED: break time.sleep(0.01) ``` --- ## WaveGen (Waveform Generator) Functions for generating analog waveforms on DAC outputs. **Setters:** - `wavegen_set_output(output)` - Select output: `DAC0`, `DAC1`, `TOP_RAIL`, `BOTTOM_RAIL` (default `DAC1`) - `wavegen_set_freq(hz)` - Set frequency: 0.0001–10000.0 Hz (default 100 Hz) - `wavegen_set_wave(shape)` - Set waveform shape (see constants below) - `wavegen_set_sweep(start_hz, end_hz, seconds)` - Configure a linear sweep - `wavegen_set_amplitude(vpp)` - 0.0–16.0 Vpp (default 3.3 Vpp) - `wavegen_set_offset(v)` - -8.0–+8.0 V (default 1.65 V for 0–3.3 V centered) - `wavegen_start([run=True])` - Start/stop output; calling with False stops - `wavegen_stop()` - Stop output immediately **Getters:** - `wavegen_get_output()` - Get current output channel - `wavegen_get_freq()` - Get current frequency - `wavegen_get_wave()` - Get current waveform - `wavegen_get_amplitude()` - Get current amplitude (Vpp) - `wavegen_get_offset()` - Get current offset - `wavegen_is_running()` - Check if wavegen is active **Aliases:** All setters/getters have `set_wavegen_*` and `get_wavegen_*` aliases. **Waveform Constants:** - `SINE` (0) - Sine wave - `TRIANGLE` (1) - Triangle wave - `SAWTOOTH` (2) - Sawtooth/ramp wave - `RAMP` (2) - Alias for SAWTOOTH - `SQUARE` (3) - Square wave - `ARBITRARY` (4) - Arbitrary waveform (not yet implemented) **Example:** ```jython # Generate a 100Hz sine wave on DAC1 wavegen_set_output(DAC1) wavegen_set_wave(SINE) wavegen_set_freq(100) wavegen_set_amplitude(3.3) # 3.3V peak-to-peak wavegen_set_offset(1.65) # Center at 1.65V (0-3.3V range) wavegen_start() # Check if running if wavegen_is_running(): print("Wavegen active at " + str(wavegen_get_freq()) + "Hz") # Change waveform while running wavegen_set_wave(TRIANGLE) # Stop wavegen_stop() ``` **Notes:** - Wavegen runs on core 2 and is fully blocking while active; LEDs and routing updates pause until `wavegen_stop()`. - Frequency, waveform, amplitude, and offset can be changed live while running. --- ## Net Information API Functions for querying and modifying net metadata. Nets are groups of connected nodes. ### `get_net_name(netNum)` Gets the name of a specific net. * `netNum`: The net number (0 to number of nets - 1). * Returns the net name string, or `None` if the net doesn't exist. **Example:** ```jython name = get_net_name(0) print("Net 0 is called: " + str(name)) ``` ### `set_net_name(netNum, name)` Sets a custom name for a net. * `netNum`: The net number. * `name`: The new name string. Pass empty string or `None` to reset to default. **Example:** ```jython set_net_name(0, "VCC") set_net_name(1, "Signal_A") ``` ### `get_net_color(netNum)` Gets the color of a net as a 32-bit RGB value. * `netNum`: The net number. * Returns the color as `0xRRGGBB`. **Example:** ```jython color = get_net_color(0) print("Net color: " + hex(color)) # e.g., "0xff0000" for red ``` ### `get_net_color_name(netNum)` Gets the color name of a net as a human-readable string. * `netNum`: The net number. * Returns a color name like "red", "blue", "green", etc. **Example:** ```jython color_name = get_net_color_name(0) print("Net 0 is " + color_name) # e.g., "Net 0 is red" ``` ### `set_net_color(netNum, color, [r], [g], [b])` Sets the color of a net by name, hex string, or RGB values. * `netNum`: The net number. * `color`: Color as a name ("red", "blue", "pink") or hex string ("#FF0000", "0xFF0000"). * `r`, `g`, `b` (optional): If providing RGB values directly, pass them as separate arguments. * Returns `1` on success, `0` on failure (invalid color). **Available color names:** red, orange, amber, yellow, chartreuse, green, seafoam, cyan, blue, royal blue, indigo, violet, purple, pink, magenta, brown, white, black, grey **Example:** ```jython # Set by color name set_net_color(0, "red") set_net_color(1, "cyan") # Set by hex string set_net_color(2, "#FF00FF") # Magenta set_net_color(3, "0x00FF00") # Green # Set by RGB values set_net_color(4, 255, 128, 0) # Orange ``` ### `set_net_color_hsv(netNum, h, [s], [v])` Sets the color of a net using HSV (Hue, Saturation, Value) color space. Automatically detects whether you're using normalized (0.0-1.0) or full-range (0-255) values based on the hue parameter. * `netNum`: The net number. * `h`: Hue value. If `h` is between 0.0-1.0, all HSV values are treated as normalized (0.0-1.0). Otherwise, values are treated as 0-255 range. * `s` (optional): Saturation value. Defaults to maximum saturation (255) if not provided or negative. * `v` (optional): Value/brightness. Defaults to 32 (reasonable LED brightness) if not provided or negative. * Returns `1` on success, `0` on failure. **Range auto-detection:** - If `h` ≤ 1.0: Normalized mode (0.0-1.0 for all values) - If `h` > 1.0: Full-range mode (0-255 for all values) **Default brightness (32):** This provides good visibility without being overly bright. For maximum brightness, explicitly set `v` to 1.0 (normalized) or 255 (full-range). **Example:** ```jython # Normalized mode (0.0-1.0) - detected because h 1.0 set_net_color_hsv(6, 0) # Pure red, max saturation, default brightness (32) set_net_color_hsv(7, 85) # Green (85 ≈ 255/3), default brightness (32) set_net_color_hsv(8, 170) # Blue (170 ≈ 2*255/3), default brightness (32) set_net_color_hsv(9, 128, 200) # Cyan-ish with 200/255 saturation, default brightness set_net_color_hsv(10, 128, 255, 128)# Cyan with max saturation, 50% brightness set_net_color_hsv(11, 128, 255, 255)# Cyan at MAXIMUM brightness (255) # Using all defaults (h only) set_net_color_hsv(12, 0.25) # Yellow with full saturation, brightness 32 set_net_color_hsv(13, 64) # Yellow in 0-255 mode, brightness 32 ``` **Why use HSV?** HSV is often more intuitive for color selection than RGB: - **Hue** represents the actual color (red → yellow → green → cyan → blue → magenta → red) - **Saturation** controls color intensity (0 = grayscale, max = vivid color) - **Value** controls brightness (0 = black, max = full brightness) ### `get_num_nets()` Gets the number of currently active nets. * Returns an integer. **Example:** ```jython num = get_num_nets() print("There are " + str(num) + " nets") ``` ### `get_num_bridges()` Gets the total number of bridges (connections). * Returns an integer. **Example:** ```jython num = get_num_bridges() print("There are " + str(num) + " bridges") ``` ### `get_net_nodes(netNum)` Gets all nodes in a net as a comma-separated string. * `netNum`: The net number. * Returns a string like "D13,TOP_RAIL,GPIO_1". **Example:** ```jython nodes = get_net_nodes(0) print("Net 0 contains: " + nodes) ``` ### `get_bridge(bridgeIdx)` Gets information about a specific bridge. * `bridgeIdx`: The bridge index (0 to number of bridges - 1). * Returns a tuple `(node1, node2, duplicates)`. **Example:** ```jython bridge = get_bridge(0) print("Bridge 0: " + str(bridge[0]) + " to " + str(bridge[1])) ``` ### `get_net_info(netNum)` Gets comprehensive information about a net as a dictionary. * `netNum`: The net number. * Returns a dict with keys: `name`, `number`, `color`, `color_name`, `nodes`. **Example:** ```jython info = get_net_info(0) print("Net name: " + info['name']) print("Net color: " + info['color_name']) print("Net nodes: " + info['nodes']) ``` --- ## Path Query API Functions for inspecting the internal routing paths between nodes. Paths represent the actual physical routes through the crossbar chips that connect nodes together. ### Understanding Paths vs Bridges * **Bridges**: Direct connections you create (e.g., `connect(1, 5)`) * **Paths**: The actual routing through crossbar chips to implement those bridges A single bridge may require multiple paths through different chips. The path query API lets you inspect these internal routing details. ### `get_num_paths([include_duplicates=True])` Gets the number of routing paths currently in use. * `include_duplicates` (optional): If `True` (default), count all paths including duplicates. If `False`, count only primary (non-duplicate) paths. * Returns an integer. **Example:** ```jython total = get_num_paths() # All paths including duplicates primary = get_num_paths(False) # Only primary paths print(f"Total paths: {total}, Primary: {primary}") ``` ### `get_path_info(path_idx)` Gets detailed information about a specific routing path. * `path_idx`: The path index (0 to `get_num_paths()-1`) * Returns a dict with keys: `node1`, `node2`, `net`, `chips`, `x`, `y`, `duplicate` * Returns `None` if index is invalid **Example:** ```jython path = get_path_info(0) if path: print(f"Path from {path['node1']} to {path['node2']}") print(f"Uses chips: {path['chips']}") print(f"Is duplicate: {path['duplicate']}") ``` ### `get_all_paths()` Gets all routing paths as a list of dictionaries. * Returns a list of path dicts (same format as `get_path_info()`) **Example:** ```jython paths = get_all_paths() for i, path in enumerate(paths): print(f"Path {i}: {path['node1']} -> {path['node2']} via net {path['net']}") ``` ### `get_path_between(node1, node2)` Queries the routing path between two specific nodes. * `node1`, `node2`: The nodes to query * Returns a path dict if found, `None` otherwise **Example:** ```jython path = get_path_between(1, 5) if path: print(f"Route uses chips: {path['chips']}") print(f"Crossbar coordinates: x={path['x']}, y={path['y']}") else: print("No path found between nodes 1 and 5") ``` --- ## Fake GPIO (Virtual GPIO Pins) Fake GPIO uses the Jumperless' internal crossbar switches to create virtual GPIO pins on any routable node. Unlike the RP2350's hardware GPIO (limited to 10 routable pins at 0-3.3V), Fake GPIO lets you use any node with custom voltage levels from -8V to +8V, or as inputs, you can have as many as you like and the ADC will switch to that node and take a reading on demand. *This is still pretty new and will probably change in the future* *Note: Fake GPIO Outputs are currently disabled until I fix the code, they were causing dead shorts between supplies* ### Why Use Fake GPIO? The RP2350's hardware GPIO is limited to 10 routable pins (GPIO_1 through GPIO_8, plus UART TX/RX) with fixed 3.3V logic levels. Fake GPIO extends this by using the crossbar switching matrix to create virtual GPIO pins on any routable node with configurable voltage levels. ### Configuring INPUT Pins ```python FakeGpioPin(node, j.INPUT, threshold_high, threshold_low) ``` Creates a digital input that reads voltage via the ADC. **Parameters:** - `node` (int, required): Any routable node to read from - `mode` (constant, required): `j.INPUT` - `threshold_high` (float, optional): Input HIGH threshold in volts (default: 2.0) - `threshold_low` (float, optional): Input LOW threshold in volts (default: 0.8) **Returns:** FakeGpioPin object **Example:** ```jython import jumperless as j # Read a digital signal button = j.FakeGpioPin(20, j.INPUT, 0.8, 2.0) state = button.value() # Returns 0 or 1 # Custom thresholds for 5V logic signal_5v = j.FakeGpioPin(25, j.INPUT, 3.5, 1.5) if signal_5v.value(): print("5V logic HIGH detected") ``` ### FakeGpioPin Methods #### `pin.value([val])` For INPUT: Reads the current pin state (0 or 1). ```jython # Basic usage pin = j.FakeGpioPin(20, j.INPUT, 0.8, 2.0) # Using value() state = pin.value() # Read current state ``` --- ## System Functions ### `arduino_reset()` Resets the connected Arduino Nano. ### `run_app(appName)` Launches a built-in Jumperless application. * `appName`: The name of the app to run (e.g., "File Manager", "I2C Scan"). ### `pause_core2(pause)` Pauses or resumes core2 processing. * `pause`: `True` to pause core2, `False` to resume. *For doing time sensitive things where you don't want core 2 to cause timing glitches while it periodically updates the LEDs.* Core 2 will resume when you exit the REPL no matter what. ### `send_raw(chip, x, y, setOrClear)` Sends raw data to core2 for direct chip control. * `chip`: Chip identifier (string, e.g., "A", "B", "C"). * `x`, `y`: Coordinates for the operation. * `setOrClear`: `1` to set, `0` to clear. You should probably be looking at the schematic if you use this. By bypassing all the routing logic, you can make or break connections in ~1 µs. Pretty handy if want to do high speed switching between known states. ### `change_terminal_color(color, [flush=True])` Sets the terminal text color using 256-color ANSI codes. * `color`: Color index (0-255), or -1 to reset to default * `flush` (optional): Flush output immediately (default: `True`) This is the 6x6x6 color cube that terminals use: [Image: TermColors] **Example:** ```jython change_terminal_color(196) # Bright red print("Error message") change_terminal_color(-1) # Reset to default change_terminal_color(46) # Cyan print("Info message") ``` ### `cycle_term_color([reset=False], [step=1.0], [flush=True])` Cycles through the terminal color palette. * `reset` (optional): If `True`, reset to start of color sequence * `step` (optional): Color increment step (defaults to the last one you set) * `flush` (optional): Flush output immediately (default: `True`) Useful for creating rainbow effects or visually distinguishing output sections. This is really just a helper function for me, it only uses a subset of the colors (I think ~44 of them?) above that actually look good. It takes a float so you can use fractional values if you have more than 44 things and you want a smooth spectrum. *This doesn't currently work in Viper IDE because it strips the escape sequences* **Example:** ```jython cycle_term_color(True, 1.5) # Start fresh for i in range(10): cycle_term_color() # Next color print(f"Line {i} in different color") # Visual separator in help output cycle_term_color(True, step=5.0) ``` [Image: CycleTerm] ### `force_service(name)` Forces immediate execution of a specific system service by name. * `name`: Service name as a string (e.g., `"ProbeButton"`, `"Peripherals"`). * Returns: `True` if service was found and executed, `False` otherwise. Useful for manually triggering specific services during tight loops where automatic service scheduling might not run frequently enough. **Example:** ```jython while True: # Fast loop that might miss automatic service updates connect(1, 2) force_service("ProbeButton") # Ensure button state updates button = check_button() time.sleep(0.001) ``` ### `force_service_by_index(index)` Forces immediate execution of a specific system service by index (faster than name lookup). * `index`: Service index (integer, obtained via `get_service_index()`). * Returns: `True` if index valid and service executed, `False` otherwise. **Example:** ```jython # Cache the index once for maximum speed btn_idx = get_service_index("ProbeButton") while True: connect(1, 2) force_service_by_index(btn_idx) # Fastest way to force service button = check_button() time.sleep(0.001) ``` ### `get_service_index(name)` Gets the index of a service by name for use with `force_service_by_index()`. * `name`: Service name as a string. * Returns: Service index (integer, 0 or higher), or `-1` if not found. Cache the returned index for repeated fast calls to `force_service_by_index()`. **Example:** ```jython # Look up once, use many times probe_idx = get_service_index("ProbeButton") if probe_idx >= 0: # Use the cached index in your loop force_service_by_index(probe_idx) ``` ### `switch_slot(slot)` Switches to a different connection slot. * `slot`: The slot number to switch to (0-7). * Returns the previous slot number. **Example:** ```jython old_slot = switch_slot(2) # Switch to slot 2 print("Was in slot: " + str(old_slot)) ``` ### `context_toggle()` Toggles the connection context between `global` and `python` modes. * In **global** mode: Connection changes persist after exiting Python. * In **python** mode: Connection state is restored when you exit the REPL. **Example:** ```jython context_toggle() # Switch from global to python (or vice versa) print("Now in " + context_get() + " mode") ``` ### `context_get()` Gets the current connection context name. * Returns `"global"` or `"python"`. **Example:** ```jython if context_get() == "global": print("Changes will persist after exit") else: print("Changes will be discarded on exit") ``` ### `get_state()` Returns the entire board state as a formatted JSON string. This includes nets, power settings, and GPIO configuration. **Example:** ```jython state_json = get_state() print(state_json) ``` ### `set_state(json, [clear_first=True], [from_wokwi=False])` Applies a board state from a JSON string or, if ``from_wokwi`` is True, from a Wokwi diagram (provided as raw JSON or filepath on the board). * `json`: A JSON string representing the state (same format as returned by `get_state()`), or a filename/Wokwi JSON when ``from_wokwi`` is used. * `clear_first` (optional): If `True` (default), clears all existing connections and FakeGPIO state before applying the new state. Set to `False` to merge the new state with the existing one. * `from_wokwi` (optional): If `True`, interpret `json` as a Wokwi diagram.json and convert it to a Jumperless state before applying. **Examples:** ```jython # Apply a simple state from a string set_state('{"nets": [{"index": 1, "name": "GND", "nodes": ["GND"]}], "power": {"top_rail": 5.0}}') # Apply directly from a Wokwi diagram file saved on the board set_state('/python_scripts/diagram1.json', from_wokwi=True) # or supply raw Wokwi JSON text set_state(wokwi_json_string, from_wokwi=True) ``` --- ## Status Functions These functions print detailed status information to the serial console. * `print_bridges()`: Prints all active bridges. * `print_paths()`: Prints all resolved paths between nodes. * `print_crossbars()`: Prints the raw state of the crossbar matrix. * `print_nets()`: Prints the current net list. * `print_chip_status()`: Prints the status of the CH446Q chips. --- ## JFS (Jumperless FileSystem) The `jfs` module is basically like MicroPython's `vfs` and parts of `os`, but kinda written in a style that's probably more familiar to Arduino-style C++ people (me). It uses the *almost* standardized API shared by [`FatFS` (the one Jumperless actually uses), `LittleFS`, and `SDFS`](https://arduino-pico.readthedocs.io/en/latest/fs.html#file-system-object-littlefs-sd-sdfs-fatfs), but still has been *Pythonified* to use types that are easier to work with in MicroPython. ### Usage ```jython import jfs #you don't actually need this, jfs is imported globally by default # List files in the root directory files = jfs.listdir('/') # returns a python list print(files) ``` Output: ```jython ['config.txt', 'nodeFileSlot0.txt', 'python_scripts/', 'nodeFileSlot1.txt', 'nodeFileSlot2.txt', 'nodeFileSlot3.txt', 'nodeFileSlot4.txt', 'nodeFileSlot5.txt', 'nodeFileSlot6.txt', 'nodeFileSlot7.txt', 'net_colors/'] ``` If you want to make this print with subdirectories ```jython files = jfs.listdir('/') for file in range(len(files)): print(files[file]) if files[file].endswith("/"): subdir = jfs.listdir(files[file]) for i in range(len(subdir)): print(" - " + subdir[i]) ``` Output: ```config.txt nodeFileSlot0.txt python_scripts/ - history.txt - examples/ - fake_gpio.py - _temp_repl_edit.py - Hey.txt - script_1.py - script_2.py - script_3.py - log.txt - lib/ - script_4.py - pathtest.py nodeFileSlot1.txt nodeFileSlot2.txt nodeFileSlot3.txt nodeFileSlot4.txt nodeFileSlot5.txt nodeFileSlot6.txt nodeFileSlot7.txt net_colors/ - netColorsSlot0.txt ``` --- ### File API File objects returned by `jfs.open()` support method calls directly on the object: ```jython # Object-oriented file operations # Write-only mode f = jfs.open('hello.txt', 'w') f.write('Hello, Jumperless!') f.close() # Read from the file (need to reopen or use w+/r+ mode) f = jfs.open('hello.txt', 'r') content = f.read() # Read from file object size = f.size() # Get file size f.close() # Read-write mode (truncates file) f = jfs.open('hello.txt', 'w+') f.write('Hello, Jumperless!') f.seek(0) # Seek to beginning to read what we wrote content = f.read() # Now this works! f.close() # Context manager support (automatically closes file) with jfs.open('data.txt', 'w+') as f: f.write('This file will be automatically closed') f.seek(0) # Reset to beginning content = f.read() # Read back what we wrote pos = f.tell() # Get current position name = f.name() # Get file name ``` #### Using `f.print()` for logging The `f.print()` method works like Python's `print()` but writes to the file. It automatically converts arguments to strings and flushes after each call - perfect for logging: ```jython # Great for logging - auto-converts types and flushes immediately with jfs.open('log.txt', 'w+') as f: f.print("Starting test...") voltage = adc_get(0) f.print("Voltage:", voltage, "V") # Multiple args work f.print("Test complete!") # Unlike f.write(), f.print() handles non-strings automatically f = jfs.open('data.txt', 'w') f.print(123) # OK - converts int to string f.print(3.14) # OK - converts float to string f.print("mixed", 42) # OK - multiple args joined with spaces f.close() ``` **Note:** `f.print()` is a file object method only. There is no `jfs.print()` module-level function. Other ways to do the same thing #### 2. Module-Level Functions You can also use module-level functions with file handles: ```jython f = jfs.open('hello.txt', 'w') jfs.write(f, 'Hello, Jumperless!') # Module-level function jfs.seek(f, 0) # Module-level function content = jfs.read(f) # Module-level function jfs.close(f) # Module-level function ``` #### 3. Direct String Operations (For simple cases) ```jython # Write/read entire files at once (no file handles needed) jfs.write('config.txt', 'key=value\nother=setting') content = jfs.read('config.txt') ``` ### File Modes When using `jfs.open(path, mode)`, the following modes are supported: | Mode | Description | Read | Write | Create | |------|-------------|------|-------|---------| | `'r'` | Read only | ✅ | ❌ | ❌ | | `'w'` | Write only | ❌ | ✅ | ✅ | | `'a'` | Append only | ❌ | ✅ | ✅ | | `'r+'` | Read + Write | ✅ | ✅ | ❌ | | `'w+'` | Read + Write | ✅ | ✅ | ✅ | | `'a+'` | Read + Append | ✅ | ✅ | ✅ | **Important:** You cannot read from a file opened in write-only mode (`'w'` or `'a'`). Use `'w+'`, `'r+'`, or `'a+'` if you need both read and write access. ### Open a file for writing ```jython f = jfs.open('hello.txt', 'w') f.write('Hello, Jumperless!') # Now works with object-oriented API! f.close() ``` ### Directory Operations #### `jfs.listdir(path)` Returns a list containing the names of the entries in the directory given by `path`. * `path` (str): The path to the directory. **Example:** ```jython # List contents of the root directory print(jfs.listdir('/')) # List contents of a subdirectory jfs.mkdir('/my_dir') print(jfs.listdir('/my_dir')) ``` #### `jfs.mkdir(path)` Create a new directory. * `path` (str): The path of the new directory. #### `jfs.rmdir(path)` Remove an empty directory. * `path` (str): The path of the directory to remove. #### `jfs.remove(path)` Remove a file. * `path` (str): The path of the file to remove. #### `jfs.rename(old_path, new_path)` Rename a file or directory. * `old_path` (str): The current path. * `new_path` (str): The new path. #### `jfs.exists(path)` Check if a file or directory exists. * `path` (str): The path to check. * Returns `True` if it exists, `False` otherwise. #### `jfs.stat(path)` Get status of a file or directory. * `path` (str): The path of the file or directory. * Returns a tuple with file information (mode, size, etc.), similar to `os.stat()`. ### Filesystem Information #### `jfs.info()` Get information about the filesystem. * Returns a tuple `(total_bytes, used_bytes, free_bytes)`. **Example:** ```jython total, used, free = jfs.info() print("Filesystem Size: " + str(total / 1024) + " KB") print("Used: " + str(used / 1024) + " KB") print("Free: " + str(free / 1024) + " KB") ``` ### File I/O The `jfs` module supports standard file opening and handling using `jfs.open()` and file objects, including support for the `with` statement for automatic resource management. #### `jfs.open(path, mode='r')` Open a file and return a corresponding file object. * `path` (str): The path to the file. * `mode` (str, optional): The mode in which the file is opened. Defaults to `'r'`. * `'r'`: Read (default). * `'w'`: Write (creates a new file or truncates an existing one). * `'a'`: Append. * `'r+'`: Read and write. * `'w+'`: Write and read (creates/truncates). * `'a+'`: Append and read. **Example:** ```jython # Open a file for reading f = jfs.open('config.txt', 'r') content = f.read() f.close() # Use 'with' for automatic closing with jfs.open('data.log', 'a') as log_file: log_file.write('New log entry.\\n') ``` ### File Object Methods The file object returned by `jfs.open()` has the following methods: #### `file.read([size])` Read `size` bytes from the file. If `size` is omitted or negative, the entire file is read. #### `file.write(data)` Write the given string or bytes `data` to the file. Returns the number of bytes written. #### `file.close()` Close the file. A closed file cannot be read or written to. #### `file.seek(offset, [whence])` Change the stream position. * `offset`: The byte offset. * `whence` (optional): * `0`: Seek from the start of the stream (default). Use `jfs.SEEK_SET`. * `1`: Seek from the current position. Use `jfs.SEEK_CUR`. * `2`: Seek from the end of the stream. Use `jfs.SEEK_END`. #### `file.tell()` Return the current stream position. * **Aliases**: `file.position()` #### `file.size()` Return the total size of the file in bytes. #### `file.available()` Return the number of bytes available to be read from the current position to the end of the file. #### `file.name` Returns the name of the file. --- ### Module-Level File Operations For convenience, the `jfs` module also provides functions that operate directly on file handles returned by `jfs.open()`. This can be useful in some scripting scenarios but using file object methods is generally preferred for clarity. * `jfs.read(file_handle, [size])` * `jfs.write(file_handle, data)` * `jfs.close(file_handle)` * `jfs.seek(file_handle, offset, [whence])` * `jfs.tell(file_handle)` * `jfs.size(file_handle)` * `jfs.available(file_handle)` **Example:** ```jython file_handle = jfs.open('temp.txt', 'w') jfs.write(file_handle, 'some data') jfs.close(file_handle) ``` --- ## Help Functions ### `help()` Displays a comprehensive list of all available functions and constants in the `jumperless` module. You can also pass it sections, so `help("GPIO")` will just print that section. ### `nodes_help()` Displays a detailed reference for all available node names and their aliases. ## Node Names and Constants The Jumperless module provides extensive node name support with multiple aliases for each node: ```jython # Power rails (multiple aliases supported) TOP_RAIL = 101 # Also: TOPRAIL, T_R, TOP_R BOTTOM_RAIL = 102 # Also: BOT_RAIL, BOTTOMRAIL, BOTRAIL, B_R, BOT_R SUPPLY_3V3 = 103 # Also: 3V3, 3.3V SUPPLY_5V = 105 # Also: 5V, +5V SUPPLY_8V_P = 120 # Also: 8V_P, 8V_POS SUPPLY_8V_N = 121 # Also: 8V_N, 8V_NEG # Ground connections GND = 100 # Also: GROUND TOP_RAIL_GND = 104 # Also: TOP_GND (not actually routable but included for PADs) BOTTOM_RAIL_GND = 126 # Also: BOT_GND, BOTTOM_GND (not actually routable but included for PADs) # DAC outputs DAC0 = 106 # Also: DAC_0, DAC0_5V DAC1 = 107 # Also: DAC_1, DAC1_8V # ADC inputs ADC0 = 110 # Also: ADC_0, ADC0_8V ADC1 = 111 # Also: ADC_1, ADC1_8V ADC2 = 112 # Also: ADC_2, ADC2_8V ADC3 = 113 # Also: ADC_3, ADC3_8V ADC4 = 114 # Also: ADC_4, ADC4_5V ADC7 = 115 # Also: ADC_7, ADC7_PROBE, PROBE # Current sensing ISENSE_PLUS = 108 # Also: ISENSE_POS, ISENSE_P, INA_P, I_P, CURRENT_SENSE_PLUS, ISENSE_POSITIVE, I_POS ISENSE_MINUS = 109 # Also: ISENSE_NEG, ISENSE_N, INA_N, I_N, CURRENT_SENSE_MINUS, ISENSE_NEGATIVE, I_NEG # GPIO pins (multiple naming conventions) GPIO_1 = 131 # Also: RP_GPIO_1, GPIO1, GP_1, GP1 GPIO_2 = 132 # Also: RP_GPIO_2, GPIO2, GP_2, GP2 GPIO_3 = 133 # Also: RP_GPIO_3, GPIO3, GP_3, GP3 GPIO_4 = 134 # Also: RP_GPIO_4, GPIO4, GP_4, GP4 GPIO_5 = 135 # Also: RP_GPIO_5, GPIO5, GP_5, GP5 GPIO_6 = 136 # Also: RP_GPIO_6, GPIO6, GP_6, GP6 GPIO_7 = 137 # Also: RP_GPIO_7, GPIO7, GP_7, GP7 GPIO_8 = 138 # Also: RP_GPIO_8, GPIO8, GP_8, GP8 # UART pins UART_TX = 116 # Also: RP_UART_TX, TX, RP_GPIO_16 UART_RX = 117 # Also: RP_UART_RX, RX, RP_GPIO_17 # Additional RP GPIOs RP_GPIO_18 = 118 # Also: GP_18 RP_GPIO_19 = 119 # Also: GP_19 # Buffer connections BUFFER_IN = 139 # Also: ROUTABLE_BUFFER_IN, BUF_IN, BUFF_IN, BUFFIN BUFFER_OUT = 140 # Also: ROUTABLE_BUFFER_OUT, BUF_OUT, BUFF_OUT, BUFFOUT # Arduino Nano pins (extensive support) D13 = 83 # Also: NANO_D13 D12 = 82 # Also: NANO_D12 D11 = 81 # Also: NANO_D11 D10 = 80 # Also: NANO_D10 D9 = 79 # Also: NANO_D9 D8 = 78 # Also: NANO_D8 D7 = 77 # Also: NANO_D7 D6 = 76 # Also: NANO_D6 D5 = 75 # Also: NANO_D5 D4 = 74 # Also: NANO_D4 D3 = 73 # Also: NANO_D3 D2 = 72 # Also: NANO_D2 D1 = 71 # Also: NANO_D1 D0 = 70 # Also: NANO_D0 # Arduino Nano analog pins A0 = 86 # Also: NANO_A0 A1 = 87 # Also: NANO_A1 A2 = 88 # Also: NANO_A2 A3 = 89 # Also: NANO_A3 A4 = 90 # Also: NANO_A4 A5 = 91 # Also: NANO_A5 A6 = 92 # Also: NANO_A6 A7 = 93 # Also: NANO_A7 # Arduino Nano non-routable hardwired connections VIN = 69 # Unconnected to anything RST0 = 94 # Hardwired to GPIO 18 on the RP2350 RST1 = 95 # Hardwired to GPIO 19 on the RP2350 N_GND0 = 97 # GND N_GND1 = 96 # GND NANO_5V = 99 # Hardwired to USB 5V bus (can also be used to power the Jumperless) NANO_3V3 = 98 # Unconnected (without bridging the solder jumper on the back) # Probe switch position constants SWITCH_MEASURE = 0 # Probe in measure mode SWITCH_SELECT = 1 # Probe in select mode SWITCH_UNKNOWN = -1 # Position unknown # Clickwheel direction constants CLICKWHEEL_NONE = 0 # No movement CLICKWHEEL_UP = 1 # Turned clockwise CLICKWHEEL_DOWN = 2 # Turned counter-clockwise # Clickwheel button state constants CLICKWHEEL_IDLE = 0 # Not pressed CLICKWHEEL_PRESSED = 1 # Just pressed CLICKWHEEL_HELD = 2 # Being held down CLICKWHEEL_RELEASED = 3 # Just released CLICKWHEEL_DOUBLECLICKED = 4 # Double-clicked ``` ## The entire output of help() ```jython >>> help() Jumperless Native MicroPython Module Available help sections: help() or help("all") - Show all functions help("DAC") - DAC functions help("ADC") - ADC functions help("GPIO") - GPIO functions help("PWM") - PWM functions help("WAVEGEN") - Waveform generator help("INA") - INA current/power monitor help("NODES") - Node connections help("NETS") - Net info (names, colors) help("SLOTS") - Slot management help("OLED") - OLED display help("PROBE") - Probe and button functions help("CLICKWHEEL") - Clickwheel (rotary encoder) functions help("STATUS") - Status and debug functions help("FILESYSTEM") - Filesystem functions help("MISC") - Miscellaneous functions help("EXAMPLES") - Usage examples DAC (Digital-to-Analog Converter): dac_set(channel, voltage) - Set DAC output voltage dac_get(channel) - Get DAC output voltage set_dac(channel, voltage) - Alias for dac_set get_dac(channel) - Alias for dac_get channel: 0-3, DAC0, DAC1, TOP_RAIL, BOTTOM_RAIL channel 0/DAC0: DAC 0 channel 1/DAC1: DAC 1 channel 2/TOP_RAIL: top rail channel 3/BOTTOM_RAIL: bottom rail voltage: -8.0 to 8.0V ADC (Analog-to-Digital Converter): adc_get(channel) - Read ADC input voltage get_adc(channel) - Alias for adc_get channel: 0-4 GPIO: gpio_set(pin, value) - Set GPIO pin state gpio_get(pin) - Read GPIO pin state gpio_set_dir(pin, direction) - Set GPIO pin direction gpio_get_dir(pin) - Get GPIO pin direction gpio_set_pull(pin, pull) - Set GPIO pull-up/down gpio_get_pull(pin) - Get GPIO pull-up/down Aliases: set_gpio, get_gpio, set_gpio_dir, get_gpio_dir, etc. pin 1-8: GPIO 1-8 pin 9: UART Tx pin 10: UART Rx value: True/False for HIGH/LOW direction: True/False for OUTPUT/INPUT pull: -1/0/1/2 for PULLDOWN/NO_PULL/PULLUP/BUS_KEEPER PWM (Pulse Width Modulation): pwm(pin, [frequency], [duty]) - Setup PWM on GPIO pin pwm_set_duty_cycle(pin, duty) - Set PWM duty cycle pwm_set_frequency(pin, freq) - Set PWM frequency pwm_stop(pin) - Stop PWM on pin Aliases: set_pwm, set_pwm_duty_cycle, set_pwm_frequency, stop_pwm pin: 1-8 GPIO pins only frequency: 0.001Hz-62.5MHz default 1000Hz duty_cycle: 0.0-1.0 default 0.5 (50%) WaveGen (Waveform Generator): wavegen_set_output(channel) - Set output: DAC0, DAC1, TOP_RAIL, BOTTOM_RAIL wavegen_set_freq(hz) - Set frequency (0.0001-10000 Hz) wavegen_set_wave(shape) - Set waveform shape wavegen_set_amplitude(vpp) - Set amplitude (0-16 Vpp) wavegen_set_offset(v) - Set DC offset (-8 to +8 V) wavegen_start() - Start waveform generation wavegen_stop() - Stop waveform generation Getters: wavegen_get_output(), wavegen_get_freq(), wavegen_get_wave(), wavegen_get_amplitude(), wavegen_get_offset(), wavegen_is_running() Waveform constants: SINE, TRIANGLE, SAWTOOTH (RAMP), SQUARE INA (Current/Power Monitor): ina_get_current(sensor) - Read current in amps ina_get_voltage(sensor) - Read shunt voltage ina_get_bus_voltage(sensor) - Read bus voltage ina_get_power(sensor) - Read power in watts Aliases: get_current, get_voltage, get_bus_voltage, get_power sensor: 0 or 1 Node Connections: connect(node1, node2) - Connect two nodes disconnect(node1, node2) - Disconnect nodes is_connected(node1, node2) - Check if nodes are connected nodes_clear() - Clear all connections set node2 to -1 to disconnect everything connected to node1 Net Information: get_net_name(netNum) - Get net name set_net_name(netNum, name) - Set custom net name get_net_color(netNum) - Get net color as 0xRRGGBB get_net_color_name(netNum) - Get net color name set_net_color(netNum, color) - Set net color by name or hex set_net_color_hsv(netNum, h, [s], [v]) - Set by HSV (auto-detects range) get_num_nets() - Get number of active nets get_num_bridges() - Get number of bridges get_net_nodes(netNum) - Get comma-separated node list get_bridge(bridgeIdx) - Get bridge info tuple get_net_info(netNum) - Get full net info as dict get_num_paths(include_duplicates=True) - Get number of paths (optionally exclude duplicates) Colors: red, orange, yellow, green, cyan, blue, purple, pink, etc. HSV: h=0.0-1.0 or 0-255 (auto), s=0-1/0-255 (default max), v=0-1/0-255 (default 32) Slot Management: nodes_save([slot]) - Save connections to slot nodes_discard() - Discard unsaved changes nodes_has_changes() - Check for unsaved changes switch_slot(slot) - Switch to different slot (0-7) CURRENT_SLOT - Get current slot number Context (controls persistence): context_toggle() - Toggle global/python mode context_get() - Get current mode name OLED Display: oled_print("text") - Display text oled_clear() - Clear display oled_connect() - Connect OLED oled_disconnect() - Disconnect OLED Probe Functions: probe_read([blocking=True]) - Read probe (default: blocking) read_probe([blocking=True]) - Read probe (default: blocking) probe_read_blocking() - Wait for probe touch (explicit) probe_read_nonblocking() - Check probe immediately (explicit) get_button([blocking], [consume]) - Get button (blocking=True, consume=False) probe_button([blocking], [consume]) - Get button (blocking=True, consume=False) check_button([consume]) - Check button non-blocking (consume=False) probe_button_blocking([consume]) - Wait for button (consume=False) probe_button_nonblocking([consume]) - Check button immediate (consume=False) consume=False (default): Holding button returns same state (continuous control) consume=True: Each press detected once (one-shot detection) Probe Switch Functions: get_switch_position() - Get current switch position set_switch_position(pos) - Set switch position manually check_switch_position() - Check switch via current sensing Touch returns: ProbePad object (1-60, D13_PAD, TOP_RAIL_PAD, LOGO_PAD_TOP, etc.) Button returns: CONNECT, REMOVE, or NONE (front=connect, rear=remove) Switch returns: SWITCH_MEASURE (0), SWITCH_SELECT (1), SWITCH_UNKNOWN (-1) Clickwheel (Rotary Encoder): clickwheel_get_position() - Get raw position counter clickwheel_reset_position() - Reset position to 0 clickwheel_get_direction([consume=True]) - Get direction event clickwheel_get_button() - Get button state clickwheel_is_initialized() - Check if clickwheel is ready consume=True (default): Direction cleared after reading (one-shot detection) consume=False: Direction persists until consumed (can read multiple times) Direction returns: CLICKWHEEL_NONE (0), CLICKWHEEL_UP (1), CLICKWHEEL_DOWN (2) Button returns: CLICKWHEEL_IDLE (0), CLICKWHEEL_PRESSED (1), CLICKWHEEL_HELD (2), CLICKWHEEL_RELEASED (3), CLICKWHEEL_DOUBLECLICKED (4) Status: print_bridges() - Print all bridges print_paths() - Print path between nodes print_crossbars() - Print crossbar array print_nets() - Print nets print_chip_status() - Print chip status Filesystem: jfs.open(path, mode) - Open file jfs.read(file, size) - Read from file jfs.write(file, data) - Write to file jfs.close(file) - Close file jfs.exists(path) - Check if file exists jfs.listdir(path) - List directory jfs.mkdir(path) - Create directory jfs.remove(path) - Remove file jfs.rename(from, to) - Rename file jfs.info() - Get filesystem info Misc: arduino_reset() - Reset Arduino run_app(appName) - Run built-in app pause_core2(pause) - Pause/unpause Core2 (True/False) send_raw(chip, x, y, set) - Send raw data to crossbar chip force_service(name) - Force run a specific service (e.g., "ProbeButton") force_service_by_index(idx) - Force run service by index (faster) get_service_index(name) - Get service index by name (cache for fast calls) Examples (all functions available globally): dac_set(DAC0, 5.0) # Set DAC0 using node constant voltage = get_adc(1) # Read ADC1 using alias connect(TOP_RAIL, D13) # Connect using constants connect(4, 20) # Connect using numbers top_rail = node("TOP_RAIL") # Create node object oled_print("Hello!") # Display text on OLED current = get_current(0) # Read current using alias set_gpio(1, True) # Set GPIO pin high pwm(1, 1000, 0.5) # 1kHz PWM, 50% duty wavegen_set_wave(SINE); wavegen_start() # Start sine wave set_net_color(0, "red") # Color net 0 red set_net_color_hsv(1, 0.5) # Cyan net 1 (HSV hue) nodes_save() # Save current connections pad = probe_read() # Wait for probe touch button = get_button() # Wait for button press ``` ## The entire output of nodes_help() ```jython >>> nodes_help() Jumperless Node Reference ======================== NODE TYPES: Numbered: 1-60 (breadboard) Arduino: D0-D13, A0-A7 (nano header) GPIO: GPIO_1-GPIO_8 (routable GPIO) Power: TOP_RAIL, BOTTOM_RAIL, GND DAC: DAC0, DAC1 (analog outputs) ADC: ADC0-ADC4, PROBE (analog inputs) Current: ISENSE_PLUS, ISENSE_MINUS UART: UART_TX, UART_RX Buffer: BUFFER_IN, BUFFER_OUT THREE WAYS TO USE NODES: 1. NUMBERS (direct breadboard holes): connect(1, 30) # Connect holes 1 and 30 connect(15, 42) # Any number 1-60 2. STRINGS (case-insensitive names): connect("D13", "TOP_RAIL") # Arduino pin to power rail connect("gpio_1", "adc0") # GPIO to ADC (case-insensitive) connect("15", "dac1") # Mix numbers and names 3. CONSTANTS (pre-defined objects): connect(TOP_RAIL, D13) # Using imported constants connect(GPIO_1, A0) # No quotes needed connect(DAC0, 25) # Mix constants and numbers MIXED USAGE: my_pin = "D13" # Create node object from string connect(my_pin, TOP_RAIL) # Use node object with constant oled_print(my_pin) # Display shows 'D13' COMMON ALIASES (many names work for same node): "TOP_RAIL" = "T_R" "GPIO_1" = "GPIO1" = "GP1" "DAC0" = "DAC_0" "UART_TX" = "TX" NOTES: - String names are case-insensitive: "d13" = "D13" = "nAnO_d13" - Constants are case-sensitive: use D13, not d13 - All three methods work in any function ``` --- # Odds and Ends ## What's New in JumperlOS JumperlOS is now a proper operating system with a priority-based task scheduler! This is a huge refactor from the original firmware: - **Viper IDE and Micropython raw REPL** - Live code on the Jumperless' file system in your browser - **Priority-based task scheduler** - Each component has a `service()` routine that checks whether it should do anything, replacing the old busy-wait loop - **Live updating** - Edits to the YAML state files will live update with new connections (whether you're editing them in the onboard editor or as a mounted USB MSC device on your computer) - **Better fonts** - New fonts available: `Berkeley`, `Iosevka`, `Pragmat[ism]` - **YAML connection files** - More permissive of malformed syntax - **Unified syntax highlighting** - Works consistently in `eKilo`, `python`, and normal input after `>` - **Encoder-based connections** - Use the clickwheel to scroll through and select nodes without touching the probe - **Current sensing marching ants** - Animated visual feedback showing current flow direction between I+ and I- connections - **Python context switching** - Toggle between `global` and `python` connection contexts in the MicroPython REPL The JumperlOS firmware repo is at [https://github.com/Architeuthis-Flux/JumperlOS](https://github.com/Architeuthis-Flux/JumperlOS) --- ## Safety Info Here's an image of the little card that should have been inside your box [Image: Safety info Never put voltages above +9V or below -9V anywhere on this board. Don't use unpowered, the crossbars need power to block voltage too. Don't power externally, use the internal power supplies (rails / DACs). It can be powered from the 5V and GND pins on the Nano header or the FPC adapter instead of USB. External signals are okay, as long as the board remains powered.This board gets fairly warm in normal operation from the LEDs, if it ever gets hot, unplug it immediately and let me know. When the switch on the probe is set to Select Mode, it should only be used on the gold probe sense pads.The probe tip in Select Mode is always at 3.3V. Don't stab yourself or others with the probe, unless it's in self-defense. Do not eat your Jumperless V5. When in doubt, don't hesitate to ask!] - Never put voltages above +9V or below -9V anywhere on this board. - Don't use unpowered, the crossbars need power to block voltage too. - Don't power externally, use the internal power supplies (rails / DACs). - It can be powered from the 5V and GND pins on the Nano header or the FPC adapter instead of USB. - External signals are okay, as long as the board remains powered. - This board gets fairly warm in normal operation from the LEDs, if it ever gets hot, unplug it immediately and let me know. - When the switch on the probe is set to Select Mode, it should only be used on the gold probe sense pads. - The probe tip in Select Mode is always at 3.3V. - Don't stab yourself or others with the probe, unless it's in self-defense. - Do not eat your Jumperless V5. - When in doubt, don't hesitate to [ask!](https://discord.gg/bvacV7r3FP) There are a lot of exceptions to these if you know what you're doing. It's pretty hard to permanently damage this board. Some things (usually external power with the Jumperless off) can cause lockup on the [analog CMOS switches](https://tinyurl.com/24xrspea), but the current limiting resistors on their power supply pins generally keep them from drawing so much current that they permanently break. In situations where one chip is getting crazy hot, the first thing to try is to unplug the Jumperless, let it cool down, and try it again (obviously, change whatever you think was causing it). Most of the time they go back to normal after some rest. Don't let any of this scare you, I'd rather you just pretend it's indestructible and use it with reckless abandon. So if you manage to break anything, just let me know and I'll send out a fresh one and a return label, no questions asked*. *Actually, a ton of questions asked, so we can figure out how it happened and maybe prevent it from happening to someone else. But the point is I don't care if it's clearly your fault and not some manufacturing defect, I will make sure you have a working Jumperless. It's even printed on the box --- ## Joom You can get the .uf2 file here: [https://github.com/Architeuthis-Flux/joom/releases/download/0.0.1/joom_full.uf2](https://github.com/Architeuthis-Flux/joom/releases/download/0.0.1/joom_full.uf2) You'll need to put the Jumperless in bootloader mode (unplug it, press the button on the back side of the USB port, plug it back in, then drag this UF2 file onto the drive called `RP2350` that pops up) Connect a speaker between the lower `RST` pin (the one closer to the breadboard) on the Nano header and `GND` for sound. When you're done playing Doom in a blindness simulator, just reload the regular firmware the same way as above. [https://github.com/Architeuthis-Flux/JumperlessV5/releases/latest](https://github.com/Architeuthis-Flux/JumperlessV5/releases/latest) --- ## Bandwidth Michael has done some [awesome work characterizing the bandwidth of the Jumperless](https://codeberg.org/multiplex/jumperless-wigglyvolts). The TL;DR is just the physical breadboard puts the 3dB roll-off at ~13MHz, and a signal passing through the crossbar matrix brings it down to around ~8MHz. It makes sense these are pretty high, these CH446Qs were originally made for switching video signals so bandwidth was pretty important when they were designing them. Keep in mind this isn't a hard limit, it's just where the signal gets attenuated by the (arbitrarilyish) defined 3dB, so your signal's amplitude is reduced by √2. --- [Image: HeroNew] ## Animations The Jumperless uses LED animations to show the state of different components on the breadboard. ### Rail Animations If it's a rail, those are animated and should be a continuous slow pulsing toward the top or bottom depending on the rail. ### ADC Animations `ADCs` are green at 0V, and go through the spectrum to red at +5V, and get whiter hot pink toward +8V. Negative voltages are kinda blue/icy and do that same thing with the "cold" colors towards -8V. ### GPIO Animations ### Input Mode `GPIO` as inputs are animated with a white pulsing (this might be broken in that FW release, I'm fixing that right now actually, and will just be purple/white) when floating, red for high, green for low ### Output Mode `GPIO` outputs will be either green or red depending on their state --- ## What's that `BUFFER_IN - DAC_0` bridge that's always there? That gets added to power the `probe LEDs`, it's kinda weird, but to multiplex 3.3V, GND, LED data, 2 buttons, and a +-9V tolerant analog line over the 4 wires on a TRRS cable, the line powering those LEDs is shared. The `connect`/`measure` switch is a Dual Pole Dual Throw (DPDT) switch. The probe tip needs to be at a steady 3.3V to be read by the `probe sense pads` which is a big resistive divider sensed by a single `ADC`. When you have it in `select` mode, the probe tip is getting 3.3V from a `GPIO` on the RP2350B driven `high`, and the LEDs get their power from the analog line, which is `ROUTABLE_BUFFER_IN` connected to `DAC 0` set to 3.3V. When you switch to `measure` mode, those roles get swapped, the LEDs are powered by that `GPIO`, and the probe tip is now `ROUTABLE_BUFFER_IN`. In the current firmware, that just stays at 3.3V so you can *kinda* sense pads in either mode (you may notice the sensing is a lot wonkier, that's because the `DAC` isn't perfectly calibrated to output *exactly* 3.3V.) But in the future, there will be some other stuff you can do in that mode treating it as an analog line (and of course, I'll forget to update this, if it's after like June 2025, double check this is still true.) ##### A side effect of needing a crossbar connection to light the probe is that the LEDs in `Select` Mode act as a test of whether the Jumperless is properly making connections. ### Why am I using one of the precious two DACs and not another GPIO? The answer is switch position sensing. You may notice there's no obvious way for the Jumperless to know where the switch is set, so I had to get creative on this one. `DAC 0`'s output is hardwired to go through a `current sense` shunt resistor, so when `DAC 0` is powering the `probe LEDs`, they'll be drawing some current I can measure with one of the `INA219`s, and therefore I can be reasonably confident that the switch is in the `select` position. If you need both `DAC`s, you can just get rid of this connection and the `probe LEDs` won't light up, but other than aesthetics, it really has no effect on functionality. Or you connect `ROUTABLE_BUFFER_IN` to a `GPIO` and set it `high` and just lose the ability to sense where the switch is. --- ## AI Generated Wiki If you want to read a wiki generated by AI and ask it questions about how this thing works and how to use it, [**DeepWiki**](https://deepwiki.com/Architeuthis-Flux/JumperlessV5/1-overview) was surprisingly accurate (enough.) The docs on this site are more about how to *use* your Jumperless, this is more geared toward helping understand the circuitry and code. --- ## Onboard Help Use `help` or `[command]?` for onboard documentation [Image: Screenshot 2025-07-04 at 5 52 32 PM] --- ## [GitHub Releases](https://github.com/Architeuthis-Flux/JumperlessV5/releases) If you want more info about each feature when I was particularly excited about it, I usually write about the new features in the [Release notes on Github](https://github.com/Architeuthis-Flux/JumperlessV5/releases). --- ## Schematic Here's the schematic that's printed on the inner flap of the box If you want look at the schematic and PCB together and don't feel like downloading the whole thing and opening it in KiCad, [you can open it in the browser with KiCanvas here](https://kicanvas.org/?github=https://github.com/Architeuthis-Flux/JumperlessV5/blob/main/Jumperless23V50/MainBoard/JumperlessV5r6/JumperlessV5r6.kicad_pro) ## Writing Native apps Writing Apps ### Here's a the example app that should show the calls for most of the things you might want to do You can do literally anything the Jumperless can in an app, so if there's a specific thing, lmk and I'll write an example. Until I make this into a proper operating system, what you're doing when you write an App is just writing a function in the main firmware. There's really no guard rails, and the API is just any function in the firmware. ## First get it PlatformIO set up to flash code So fork the firmware here: https://github.com/Architeuthis-Flux/JumperlOS I'm using PlatformIO in VSCode. And it *should* just work to open the RP23V50firmware folder in that (you'll probably need to comment out `upload_port = /dev/cu.usbmodemJLV5port1` in `Platformio.ini` so it'll just automatically find it) You should probably try to just load the firmware just to make sure everything works. ## To write an App Before you go writing your app, follow these steps to make it so it's listed in the App library and you can run it from the menus. - Go to [`menuTree.h`](https://github.com/Architeuthis-Flux/JumperlessV5/blob/main/RP23V50firmware/src/menuTree.h) and add the name of your app under `Apps\n\` (shown as `-Custom App\n\` here, it needs to fit in 7x2 chars to show on the breadboard) - Go to `Apps.h` and declare your function where you'll write your app - Go to `Apps.cpp` and add a struct in the `struct app apps[30]` for your app `{"Name", index, ??idk, name of the function (unused)}` - Go to `Apps.cpp > runApp()` and add a `case` for your app's index (this is so you can also find it by index rather than exact matching the name `"Custom App"` - Make a function that's the entirety of your app, I just pushed a demo function called `customApp(void)` with some (non exhaustive) examples of things you can do from an app. - Run your app with the clickwheel, `Apps > Custom App`. The quick way run `"Custom App"` is to just enter `2` in the main menu, or just use the clickwheel and go Apps > Custom App. If you want to add your own shortcut, find an unused menu character and add a ``` case'3': { runApp(3); //the app index you set above break; } ``` in the big main menu `switch` statement in `main.cpp`. ## To actually write the app [The code](https://github.com/Architeuthis-Flux/JumperlessV5/blob/6fd4fcba572c4b524435ec36c8901adcedbf52c6/RP23V50firmware/src/Apps.cpp#L141) for `Custom App` is an example of the calls available with comments telling you what's going on. There are tons more, but what's shown there are the higher-level helper functions that should roughly do what they say they're doing. # 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](09.5-micropythonAPIreference.md)) --- ## 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.* ```python # 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.* ```cpp Serial.print("connect(1, 5)"); // Python command Serial.print("n"); // 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 ` 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 ```jython 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, from_wokwi=False) # Apply state from JSON string # set from_wokwi=True to convert Wokwi diagram.json ``` ### Voltage Control ```jython 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 ```jython 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 ```jython 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) ```jython # 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 ```jython 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). ```jython # 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 ```jython # Standard Python I/O is supported! with open('/config.txt', 'r') as f: print(f.read()) # List files import os os.listdir('/') ``` ```jython 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 ```json { "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 | --- 0.001: # > 1mA flows resistance = v_drop / current if resistance 0.1: # Conducting vf = v - measured if vf 0.1: return {"type": "wire", "confidence": 0.9} # Conducts both ways # 4. Capacitance Test (RC time constant) # Use internal resistance + measure charge time # ... (implementation depends on available timing) return {"type": "unknown", "confidence": 0.0} ``` ### Test Sequence for Unknown Board When a user says "I have some stuff on the board but I'm not sure what": ```jython def scan_all_rows(): """Scan all 60 rows for connections and components.""" connections = [] for row_a in range(1, 60): for row_b in range(row_a + 1, 61): result = detect_component(row_a, row_b) if result["type"] != "unknown": connections.append({ "rows": [row_a, row_b], **result }) oled_print(f"{row_a}-{row_b}: {result['type']}") return connections ``` --> ### 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 ```jython # 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. ```jython 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** ```json { "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. ```python # 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: ```python # 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: ```python # 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: ```jython # 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**: ```jython # 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**: ```jython # After connecting power, verify it worked: connect(5, TOP_RAIL) connect(ADC0, 5) v = adc_get(0) if abs(v - 5.0) 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. ```jython # 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. ```jython # 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) | --- # 3D Printable Stand [[Image: 3DStandCollage]](https://www.printables.com/model/1249365-jumperless-stand) [Here are the 3D models](https://www.printables.com/model/1249365-jumperless-stand) for you to print your own stand for your Jumperless. It's extremely handy to have it propped up like this to read text on the breadboard. ## Printing Tips Yes, the model is at a weird angle, just drop it down in the slicer, if you want it to hold at a shallower angle, just drop the model through the bed a bit when you slice. ## Rubber Feet These [stick-on rubber feet](https://www.amazon.com/AmazonBasics-300-Piece-Adhesive-Rubber-Bumpers/dp/B087MG3G76) also make it a lot more solid on your desk (and having the different sizes lets you shim the angle by putting different ones on the front and back.) --- # Glossary of Terms ## Basic Concepts `net` = a group of all the `node`s that are connected together (enter `n` to see the list) `node` = anything the crossbar array can connect to, which includes everything on the breadboard and Nano header, as well as the internal `special function` `node`s like `routable GPIO`, `ADC`s, `DAC`s `row` = *kinda* the same thing as `node` but I generally use it to mean stuff on the breadboard (so special function things like `routable GPIO`, `ADC`s, `DAC`s that don't have a set location are excluded) `rail` = I use this to refer to the 4 horizontal power rails on the top and bottom (`top_rail`, `bottom_rail`, `gnd`), I will never call a vertical `row` a `rail`. (I know they're columns but it's easier to say a lot) `bridge` = a pair of exactly two `node`s (this is what you're making when you connect stuff with the probe, enter `b` to see the bridge array) `path` = the set of crossbar connections needed to make a single `bridge`, so it can have multiple `hop`s if it doesn't have a direct connection and needs to make a `bounce` through an intermediate `chip` (enter `c` to see the crossbar array) ## Hardware `chip` = shorthand for the CH446Qs specifically, lettered A-L. The first 8 (A-H) are considered "breadboard `chips`", and the last 4 (I-L) are considered "special function" chips (enter `c` to see their connections) `menu` = I generally mean the onboard clickwheel `menu` when I say this (`click` the wheel to enter those and `scroll` around.) Sometimes I mean the `main menu` which is the list of single character command that gets presented over serial. ## Slots and Files `slot` = one of **10** saved circuit configurations (slots 0-9) that you can switch between. Use `<` to cycle forward through slots, or use the menus to jump to a specific slot. The **active slot** is the one currently loaded and affecting the hardware. `slot file` = a YAML file on the filesystem that stores a complete circuit configuration including bridges, power settings, and colors. Located at `/slots/slotN.yaml` where N is 0-9. These files are human-readable and can be edited directly! `active slot` = the currently loaded slot. Only the active slot affects the hardware. Use `Q` command to query which slot is active. When you make connections with the probe, they're saved to the active slot automatically. ## Slot Management Commands - `<` = cycle to next slot (0→1→2...→7→0) - `Q` = query which slot is currently active (returns `ACTIVE_SLOT:X`) - `Y` = print YAML ## YAML Format Slot files use YAML format with named nodes for readability: ```yaml bridges: - {n1: 1, n2: 10, dup: 2, color: red} - {n1: NANO_D5, n2: GPIO_1, dup: 2} power: topRail: 3.30 bottomRail: 2.50 ``` **Named nodes:** `NANO_D0-D13`, `NANO_A0-A7`, `GPIO_1-8`, `TOP_RAIL`, `BOTTOM_RAIL`, `GND`, `DAC0_5V`, `DAC1_5V`, etc. You can view and edit these files in the [File Manager](08-file-manager.md) or via USB Mass Storage mode (`U` command). ## Wokwi Import `W` = Import circuit from [Wokwi](https://wokwi.com) simulator 1. Design circuit on wokwi.com 2. Copy `diagram.json` content 3. Type `W` in Jumperless 4. Paste JSON content 5. Circuit is converted and saved to active slot The parser automatically maps Wokwi breadboard pins, Arduino Nano pins, and logic analyzer channels to Jumperless nodes, and preserves your wire colors from Wokwi! ---