Skip to content

Aardvark I2C/SPI/GPIO & datasheets

The Total Phase Aardvark adapter tools and the datasheet management workflow.

The Total Phase Aardvark is a USB host adapter that turns your machine into an I2C/SPI master with eight bonus GPIO pins. When one is plugged in, Revka registers it as a device (aardvark0) and exposes six bus-level tools the agent can call: scan the I2C bus, read and write I2C registers, run a full-duplex SPI transfer, drive the adapter’s GPIO header, and look up component datasheets.

Use this page when you want an agent to talk to bench electronics — temperature sensors, EEPROMs, SPI flash, port expanders — over a real I2C or SPI bus. It covers the five Aardvark tools, the datasheet workflow, the aardvark-sys crate and its aardvark.so library, and the REVKA_AARDVARK_LIB override. For tethered-MCU GPIO and the native Raspberry Pi tools see GPIO tools; to connect your first board see the Hardware quickstart.

At daemon start, the hardware boot routine asks aardvark-sys whether any adapters are connected. For each one found it registers a device alias (aardvark0, aardvark1, …, USB VID 0x2B76), attaches an AardvarkTransport, and appends the six Aardvark tools to the agent’s tool registry. If no adapter is present — or the aardvark.so library can’t be loaded — the crate runs in stub mode: discovery returns an empty list, no tools are loaded, and nothing crashes.

The stack is layered. Each tool the agent calls turns into a ZcCommand, the transport translates that into aardvark-sys calls, and aardvark-sys is the only layer that touches the C library:

LayerFileRole
Toolssrc/hardware/aardvark_tools.rsValidate agent JSON, resolve the device, build a ZcCommand, return text.
Transportsrc/hardware/aardvark.rsTranslate ZcCommandaardvark-sys calls; open a handle per command.
FFI bindingscrates/aardvark-sys/src/lib.rsSafe Rust over the Total Phase C library, loaded at runtime.

The three I2C tools share one optional device argument — the Aardvark alias to target. With a single adapter connected it is auto-selected, so you can omit it; with several, pass device="aardvark0". Addresses and bytes are taken as decimal integers and reported back in hex.

Probes every 7-bit address from 0x08 to 0x77 with a one-byte read and returns the addresses that acknowledge. This is the usual first step: identify what’s on the bus, then look up each device’s datasheet.

Agent call: i2c_scan()
Output: I2C scan found 2 device(s): 0x48, 0x68

When the bus is empty the tool reports I2C scan complete — no devices found on the bus.

ParameterTypeRequiredMeaning
devicestringnoAardvark alias; auto-selected when only one adapter is connected.

Reads bytes from an I2C device. When register is supplied, the tool performs the standard register-read pattern (write the register address, then read); without it, it does a plain read from the device address.

Agent call: i2c_read(addr=72, register=0, len=2)
Output: I2C read from addr 0x48: [0xAB, 0xCD]

addr=72 is 0x48; register=0 is the register to read from.

ParameterTypeRequiredDefaultMeaning
devicestringnoautoAardvark alias.
addrintegeryesI2C device address in decimal (e.g. 72 for 0x48).
registerintegernoRegister address to read from; triggers a write-then-read.
lenintegerno1Number of bytes to read.

Writes a sequence of bytes to an I2C device — for example, a configuration-register write.

Agent call: i2c_write(addr=72, bytes=[1, 96])
Output: I2C write to addr 0x48: 2 byte(s) written

This writes [0x01, 0x60] to the device at 0x48.

ParameterTypeRequiredMeaning
devicestringnoAardvark alias.
addrintegeryesI2C device address in decimal.
bytesarray of integersyesBytes to write; each value must be a valid u8 (0–255).

Performs a full-duplex SPI transfer: the bytes you send are clocked out while the same number of bytes are clocked in and returned. A classic use is reading the JEDEC ID from an SPI flash chip.

Agent call: spi_transfer(bytes=[0x9F, 0x00, 0x00])
Output: SPI transfer complete. Received: [0x00, 0xEF, 0x40]
ParameterTypeRequiredMeaning
devicestringnoAardvark alias; auto-selected when only one adapter is connected.
bytesarray of integersyesBytes to send; the received buffer has the same length.

The Aardvark exposes eight GPIO pins on its 10-pin expansion header. Unlike the per-pin gpio_write/gpio_rpi_write tools, gpio_aardvark operates on all eight pins at once using one-byte bitmasks — bit N corresponds to pin N.

Set pin directions and levels with action="set"; read the current pin states with action="get".

# Set pin 0 as output, drive it HIGH
Agent call: gpio_aardvark(action="set", direction=0b00000001, value=0b00000001)
Output: Aardvark GPIO set — direction: 0b00000001, value: 0b00000001
# Read all eight pins
Agent call: gpio_aardvark(action="get")
Output: Aardvark GPIO pins: 0b00000001 (0x01)
ParameterTypeRequiredMeaning
devicestringnoAardvark alias.
actionstringyesset or get.
directionintegerfor setBitmask of pin directions: 1 = output, 0 = input.
valueintegerfor setBitmask of output levels: 1 = HIGH, 0 = LOW.

Outputs are shown in both binary (0b…) and hex so the bitmask is easy to read back.

The datasheet tool ties the bus tools into a practical loop: scan the bus, identify a device, fetch its datasheet, and use the register map to program it. Datasheets are stored as PDFs under ~/.revka/hardware/datasheets/. It is registered alongside the other Aardvark tools when an adapter is detected.

It has four actions:

ActionRequired fieldsWhat it does
searchdevice_nameReturns a suggested web-search query for the datasheet (does not run the search itself).
downloaddevice_name, urlDownloads the PDF at url and saves it as <device_name>.pdf.
listLists the PDFs cached in the datasheets directory.
readdevice_nameReturns the local path (and extracted text, with rag-pdf) of a cached datasheet.
// 1. Get a search query (the agent then runs it with its web_search tool)
{ "action": "search", "device_name": "LM75" }
// 2. Download once you have the URL
{ "action": "download", "device_name": "LM75", "url": "https://www.ti.com/lit/ds/symlink/lm75.pdf" }
// 3. List what's cached
{ "action": "list" }
// 4. Get the cached file path / text
{ "action": "read", "device_name": "LM75" }
  1. Scan the bus to find connected devices.

    i2c_scan() → I2C scan found 1 device(s): 0x48
  2. Look up the datasheet. The agent calls datasheet(action="search", device_name="LM75"), runs the returned query with web_search, then downloads the PDF with datasheet(action="download", …).

  3. Read a register using the address and register map from the datasheet.

    i2c_read(addr=72, register=0, len=2) → I2C read from addr 0x48: [0x19, 0x00]
  4. Write configuration if needed.

    i2c_write(addr=72, bytes=[1, 96]) → I2C write to addr 0x48: 2 byte(s) written

aardvark-sys (crates/aardvark-sys) provides safe Rust bindings to the Total Phase Aardvark C library. It loads aardvark.so dynamically at runtime via libloading, and is the only place in Revka where unsafe is permitted. It exposes device enumeration (find_devices), an RAII open/close handle, I2C enable/read/write/scan, SPI enable/transfer, and GPIO set/get. You never call this crate directly — it sits under AardvarkTransport — but its behavior explains how the tools degrade and recover.

Key behaviors:

  • Stub mode is graceful. If aardvark.so can’t be found or loaded, every method returns an error and find_devices() returns []. The daemon starts normally with the Aardvark tools simply absent.
  • Free adapters only. find_devices() returns the port numbers of free adapters; ports already in use by another process are filtered out.
  • Per-command handles. A fresh handle is opened for each command and its Drop impl calls aa_close automatically, so connections are never left open.

The Aardvark tools depend on the proprietary aardvark.so shared library from Total Phase. aardvark-sys searches for it at runtime in this order:

  1. The path in the REVKA_AARDVARK_LIB environment variable (a full path to aardvark.so).
  2. <workspace>/crates/aardvark-sys/vendor/aardvark.so (the development default).
  3. aardvark.so next to the running binary (for deployment).
  4. aardvark.so in the current working directory.

The first candidate that loads wins. To point Revka at a library installed elsewhere, set the override before starting the daemon:

Terminal window
export REVKA_AARDVARK_LIB=/opt/totalphase/aardvark.so
revka daemon

If no candidate resolves, aardvark-sys logs that no library was found and falls back to stub mode — the daemon still runs, just without Aardvark tools.

After plugging in an adapter and starting the daemon, confirm the tools loaded:

Terminal window
curl http://127.0.0.1:42617/tools | grep -E "i2c_scan|spi_transfer|gpio_aardvark|datasheet"

If those tool names appear, the adapter was detected and the library loaded. If they’re missing: check that the binary was built with --features hardware, that the adapter was connected before the daemon started, and that aardvark.so is on the search path (set REVKA_AARDVARK_LIB if needed). For a quick end-to-end check, ask the agent to scan the bus:

Terminal window
revka agent -m "Scan the I2C bus with the Aardvark"

See Status, health, config & tools endpoints for the /tools endpoint and revka doctor, status & self-test for diagnostics.