buspirate

package
v0.690.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 13, 2026 License: AGPL-3.0 Imports: 11 Imported by: 0

Documentation

Overview

Package buspirate is the PromptZero backend for the Bus Pirate 5 universal bus probe (RP2040-based). It exposes a text-mode serial client that drives the Bus Pirate's interactive command interface over USB CDC-ACM and parses the human-readable responses into structured Go types.

Device overview

Bus Pirate 5 is a next-generation bus probe that supersedes the original Bus Pirate hardware. Key capabilities:

  • PIO-driven I2C up to 500 kHz, SPI, hardware UART, 1-Wire, JTAG
  • Built-in voltage measurement (8 × IO pins + Vout rail)
  • I/O voltage selection from 1.2 V to 5 V via on-board power supply
  • USB CDC-ACM at 115200 8N1 (default; CDC-ACM is rate-irrelevant at the physical layer, but 115200 is the firmware's advertised baud)

Firmware source and documentation:

https://github.com/DangerousPrototypes/BusPirate5-firmware
https://hardware.buspirate.com/introduction

Protocol — text mode

Bus Pirate 5 offers two protocol surfaces:

  1. Text ("user terminal") mode — newline-terminated ASCII commands; each response ends with a mode-specific prompt such as "HiZ>" or "I2C>". This is the mode used by this package. It is more transparent for an LLM agent because commands and responses are fully legible.

  2. Binary (BBIO) mode — compact binary framing entered via a NULL-byte sequence. Not used here; described in the firmware README for reference.

Verified against firmware README at commit a7e2f3b (2024-10). The exact prompt strings (`HiZ>`, `I2C>`, `SPI>`, etc.) and command syntax match what the firmware documents; any deviations are noted inline with a "verified against …" comment.

Prompt format

After a command the device replies with zero or more output lines followed by the current-mode prompt on its own line, e.g.:

HiZ>
I2C>
SPI>
UART>
1WIRE>

The prompt always appears at the start of a line and ends the response. This package's parser looks for a line that is solely a prompt string to know when a response is complete.

Mode switching

The `m` command opens the mode menu. The firmware accepts numeric selection and resets to HiZ mode on `m 0` (or just pressing reset). Known mode numbers as documented in the firmware:

m 1  — 1-Wire
m 2  — UART
m 3  — I2C
m 4  — SPI
m 5  — LED (APA102/SK6812)

Note: the spec brief listed I2C as `m 4`; the firmware README documents it as `m 3`. This package follows the firmware documentation (verified against DangerousPrototypes/BusPirate5-firmware README §Modes).

I2C scanner

`(1)` runs the built-in I2C address-space scanner macro. Output lines for each responding device are:

I2C ADDRESS SEARCH
Found address 0x50
Found address 0x68
I2C ADDRESS SEARCH COMPLETE

SPI reads

`r:N` reads N bytes from the SPI bus and prints each byte in hex:

SPI> r:4
0x00 0xFF 0xAB 0x12

`[0xAB r:16]` asserts CS, writes 0xAB, reads 16 bytes, deasserts CS.

Voltage measurement

`v` prints a voltage table. Verified output format (firmware v6.1):

VOUT: 3.30V
VREG: 3.30V
IO0: 3.30V
IO1: 3.30V
...
IO7: 3.29V

IO pins are labelled IO0–IO7 in the firmware output (not pin1–pin8).

Concurrency

Client is safe for concurrent use. A sync.Mutex serialises all port reads and writes so that overlapping tool calls from the agent dispatch do not interleave command/response frames on the wire. Callers should not issue two commands simultaneously via the same Client; the mutex prevents data corruption but a second call will block until the first completes.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func ParseHexBytes

func ParseHexBytes(raw string) []byte

ParseHexBytes parses a space- or newline-separated list of hex byte values as emitted by `r:N` SPI reads and the UART bridge response. Both `0xNN` and bare `NN` formats are accepted.

Example inputs:

"0x00 0xFF 0xAB 0x12"
"0x00\n0xFF\n0xAB"
"00 FF AB 12"

Tokens that cannot be parsed as a hex byte are skipped so partial or annotated output (e.g. the firmware may prefix the line with "READ:") still yields the bytes that are present.

func ParseI2CScan

func ParseI2CScan(raw string) []byte

ParseI2CScan parses the output of the `(1)` I2C scanner macro and returns the list of 7-bit device addresses that responded.

The firmware emits lines like:

I2C ADDRESS SEARCH
Found address 0x50
Found address 0x68
I2C ADDRESS SEARCH COMPLETE

Verified against Bus Pirate 5 firmware output (DangerousPrototypes/ BusPirate5-firmware). Lines that do not match the "Found address 0xNN" pattern are silently ignored so partial output (e.g. scan interrupted by timeout) still yields whatever addresses were discovered before the cut.

func ParseSingleVoltage

func ParseSingleVoltage(raw string) (float64, error)

ParseSingleVoltage extracts a single voltage reading from the output of `a N` (analog pin read). The firmware prints a line like:

IO1 VOLTAGE: 1.65V

or simply:

1.65V

Returns an error when no voltage value can be parsed.

func ParseVoltageTable

func ParseVoltageTable(raw string) map[string]float64

ParseVoltageTable returns a string→float64 map covering all labelled voltage lines in the `v` output, including VOUT, VREG, and all IO pins. Useful for detailed reporting; the buspirate_voltages tool uses ParseVoltages (IO pins only) for its structured result.

func ParseVoltages

func ParseVoltages(raw string) (map[int]float64, error)

ParseVoltages parses the output of the `v` voltage command and returns a map of IO pin index (0–7) to volts.

The firmware emits lines in the format:

VOUT: 3.30V
VREG: 3.30V
IO0: 3.30V
IO1: 3.30V
...
IO7: 3.29V

Verified against Bus Pirate 5 firmware v6.1 output. Only IO0–IO7 lines are mapped to integer keys; VOUT and VREG are ignored by this function (use ParseVoltageTable for the full table including rails).

Returns an error only when no IO pins could be parsed — a non-fatal partial result is still returned alongside the error.

Types

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client is a connected Bus Pirate 5 session.

Create one with Connect (real hardware) or NewWithPort (testing). All exported methods are safe for concurrent use; the internal mutex serialises command/response exchanges on the wire.

func Connect

func Connect(ctx context.Context, portName string, baud int) (*Client, error)

Connect opens the serial port at portName, drains any pending output, and waits for the HiZ> prompt to confirm the device is ready.

func NewWithPort

func NewWithPort(p Port) *Client

NewWithPort wraps a caller-supplied Port in a Client. Production code should call Connect; this constructor exists for tests that need to inject a mock port without opening a real serial device.

func (*Client) Close

func (c *Client) Close() error

Close releases the underlying serial port.

func (*Client) Exec

func (c *Client) Exec(ctx context.Context, cmd string) (string, error)

Exec sends an arbitrary newline-terminated command and returns the raw response text (prompt stripped). Intended for advanced users and future protocol extensions that don't have dedicated Client methods.

The caller is responsible for ensuring the command is appropriate for the current mode. Exec acquires the internal mutex, so it must not be called while another Client method is running.

func (*Client) I2CScan

func (c *Client) I2CScan(ctx context.Context) ([]byte, error)

I2CScan runs the Bus Pirate built-in I2C address scanner macro `(1)` and returns the list of 7-bit addresses that responded.

Requires the client to already be in I2C mode (call Mode("i2c") first).

func (*Client) MeasureVoltages

func (c *Client) MeasureVoltages(ctx context.Context) (map[int]float64, error)

MeasureVoltages runs the `v` command and returns a map of pin index → volts. The Bus Pirate labels IO pins IO0–IO7; this method returns 0–7 as keys. Additional rails (VOUT, VREG) are available via the raw output; this method focuses on the IO pins for the standard tool surface.

func (*Client) Mode

func (c *Client) Mode(ctx context.Context, name string) error

Mode switches the Bus Pirate to the named mode ("hiz", "i2c", "spi", "uart", "1wire"). It sends `m <number>\n` and waits for the new mode prompt.

func (*Client) PinRead

func (c *Client) PinRead(ctx context.Context, pin int) (float64, error)

PinRead reads the voltage on a single IO pin using `a N` (analog read). The firmware prints the pin voltage; this method returns it as a float64.

Verified: Bus Pirate 5 uses `a` for analog pin reads; `A` is the uppercase alias that also prints the percentage of full scale. Pin must be in 0-7 (IO0-IO7 on Bus Pirate 5).

func (*Client) PinSet

func (c *Client) PinSet(ctx context.Context, pin int, vOrLogic any) error

PinSet drives a digital or analog level on an IO pin. vOrLogic may be:

  • int / float64 — written as a voltage e.g. `D 1 3.3` (pin 1 → 3.3 V)
  • bool — written as `D 1 1` (high) or `D 1 0` (low)
  • string "0"/"1"/"high"/"low" — same semantics as bool

Requires the client to already be in a mode that supports pin output. Pin must be in 0-7 (IO0-IO7 on Bus Pirate 5).

func (*Client) SPIDump

func (c *Client) SPIDump(ctx context.Context, n int) ([]byte, error)

SPIDump reads n bytes from the SPI bus using `r:N`. For a command/response exchange (assert CS, write addr, read n bytes, deassert CS) use the raw Exec method with the full Bus Pirate expression syntax.

Requires the client to already be in SPI mode (call Mode("spi") first).

func (*Client) UARTBridge

func (c *Client) UARTBridge(ctx context.Context, send []byte) ([]byte, error)

UARTBridge writes send to the UART bus and reads whatever response arrives before the command timeout. baud is the target UART baud rate (passed to Mode before the exchange if the client is not already in UART mode at that speed).

Requires the client to already be in UART mode (call Mode("uart") first).

type MockPort

type MockPort struct {
	// contains filtered or unexported fields
}

MockPort is an in-memory Port implementation for tests. It approximates the Bus Pirate 5 text-mode protocol: scripted commands produce canned response bodies followed by the current-mode prompt.

Usage:

mp := NewMockPort()
mp.SetMode("hiz")                       // initial prompt mode
mp.Respond("(1)", "Found address 0x50\nI2C ADDRESS SEARCH COMPLETE")
c := NewWithPort(mp)

All methods are goroutine-safe.

func NewMockPort

func NewMockPort() *MockPort

NewMockPort returns an initialised MockPort in HiZ mode with sane defaults.

func (*MockPort) Close

func (mp *MockPort) Close() error

Close implements Port.

func (*MockPort) LinesSeen

func (mp *MockPort) LinesSeen() []string

LinesSeen returns an ordered copy of every command line received.

func (*MockPort) Read

func (mp *MockPort) Read(p []byte) (int, error)

Read implements Port. Blocks (polling at readWait intervals) until data is available or the simulated timeout fires.

func (*MockPort) Respond

func (mp *MockPort) Respond(cmd, body string)

Respond registers a canned response body for cmd. When the MockPort sees cmd written to it, it emits the body followed by the current-mode prompt. A subsequent SetMode changes the prompt for all future responses, including ones already registered. The body should not include a trailing prompt — MockPort appends it automatically.

func (*MockPort) SetMode

func (mp *MockPort) SetMode(name string)

SetMode updates the prompt string that is appended to every response. Use the lowercase mode name ("hiz", "i2c", "spi", "uart", "1wire").

func (*MockPort) SetReadTimeout

func (mp *MockPort) SetReadTimeout(d time.Duration) error

SetReadTimeout implements Port. Updates the simulated read timeout so the mock respects the same contract as a real serial port.

func (*MockPort) Write

func (mp *MockPort) Write(p []byte) (int, error)

Write implements Port. Accumulates bytes until a newline arrives, then dispatches the line to the scripted responder.

type Port

type Port interface {
	io.Reader
	io.Writer
	io.Closer
	SetReadTimeout(time.Duration) error
}

Port is the subset of go.bug.st/serial.Port this package actually uses. Exported so tests can inject a fake backend via NewWithPort without opening a real device.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL