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:
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.
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 ¶
- func ParseHexBytes(raw string) []byte
- func ParseI2CScan(raw string) []byte
- func ParseSingleVoltage(raw string) (float64, error)
- func ParseVoltageTable(raw string) map[string]float64
- func ParseVoltages(raw string) (map[int]float64, error)
- type Client
- func (c *Client) Close() error
- func (c *Client) Exec(ctx context.Context, cmd string) (string, error)
- func (c *Client) I2CScan(ctx context.Context) ([]byte, error)
- func (c *Client) MeasureVoltages(ctx context.Context) (map[int]float64, error)
- func (c *Client) Mode(ctx context.Context, name string) error
- func (c *Client) PinRead(ctx context.Context, pin int) (float64, error)
- func (c *Client) PinSet(ctx context.Context, pin int, vOrLogic any) error
- func (c *Client) SPIDump(ctx context.Context, n int) ([]byte, error)
- func (c *Client) UARTBridge(ctx context.Context, send []byte) ([]byte, error)
- type MockPort
- func (mp *MockPort) Close() error
- func (mp *MockPort) LinesSeen() []string
- func (mp *MockPort) Read(p []byte) (int, error)
- func (mp *MockPort) Respond(cmd, body string)
- func (mp *MockPort) SetMode(name string)
- func (mp *MockPort) SetReadTimeout(d time.Duration) error
- func (mp *MockPort) Write(p []byte) (int, error)
- type Port
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func ParseHexBytes ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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) Exec ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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) Read ¶
Read implements Port. Blocks (polling at readWait intervals) until data is available or the simulated timeout fires.
func (*MockPort) Respond ¶
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 ¶
SetMode updates the prompt string that is appended to every response. Use the lowercase mode name ("hiz", "i2c", "spi", "uart", "1wire").
func (*MockPort) SetReadTimeout ¶
SetReadTimeout implements Port. Updates the simulated read timeout so the mock respects the same contract as a real serial port.