Skip to main content

GPIO API Reference

The GPIO (General Purpose Input/Output) API provides access to 32 programmable I/O pins that can be configured for digital or analog operations. Each pin supports voltages from -25V to +25V for inputs and 0 to 24V for outputs.

Accessing GPIO​

const gpio = tester.gpio;

GPIO Module Methods​

reset()​

Resets all GPIO pins to their default configuration.

await tester.gpio.reset();

Returns: Promise<void> / None / Result<(), At1000Error>


digital(io)​

Access a specific GPIO pin for digital operations.

const pin = tester.gpio.digital(0);

Parameters:

  • io (number / int / usize): GPIO pin number (0-31)

Returns: DigitalIO instance / Result<DigitalIo, At1000Error> in Rust


analog(io)​

Access a specific GPIO pin for analog operations.

const pin = tester.gpio.analog(0);

Parameters:

  • io (number / int / usize): GPIO pin number (0-31)

Returns: AnalogIO instance / Result<AnalogIo, At1000Error> in Rust


hold()​

Holds (freezes) the current state of all GPIO pins. This allows buffering multiple write commands before applying them simultaneously.

await tester.gpio.hold();

Returns: Promise<SyncConfiguration> / SyncConfiguration / Result<SyncConfiguration, At1000Error> - Configuration with action: "hold"

note

Prevents external changes to GPIO states until release() is called. Digital and analog writes issued between hold() and release() are buffered on the device and applied together on release.


release()​

Releases the outputs, applying all buffered writes simultaneously. Releasing also switches the device to sync-slave mode: its outputs then follow the SYNC-IN line, so an external master's hold freezes them (see Output synchronization).

await tester.gpio.release();

Returns: Promise<SyncConfiguration> / SyncConfiguration / Result<SyncConfiguration, At1000Error> - Configuration with action: "release"


read_config(io)​

Reads back the current configuration of a specific I/O pin.

const config = await tester.gpio.read_config(0);

Parameters:

  • io (number / int / usize): GPIO pin number (0-31)

Returns: Promise<GpioConfiguration> / GpioConfiguration / Result<GpioConfiguration, At1000Error> - The pin's current configuration


read_sync()​

Reads the current synchronization hold state. Returns { hold_state: "held" } while outputs are frozen and { hold_state: "released" } otherwise.

const state = await tester.gpio.read_sync();

Returns: Promise<SyncState> / SyncState / Result<SyncState, At1000Error> - The current synchronization hold state

Type: SyncState = { hold_state: "held" | "released" }


sequence()​

Creates a builder for a list of GPIO commands the device runs back to back in one request. See GPIO command sequences for the guide and GpioSequence below for every method.

const sequence = tester.gpio.sequence();

Returns: GpioSequence / GpioSequence / GpioSequence - An empty builder, which talks to the device only on run()

note

The builder is immutable in JavaScript and Python: every call returns a new builder and leaves the receiver alone. In Rust it is consuming: every call takes self and returns Result<Self, At1000Error>, so a whole chain composes with ?.


DigitalIO Class​

Input thresholds may be equal or in either order. If both input-state comparisons match, the low-threshold comparison takes precedence and reads false. A partial REST PATCH retains omitted thresholds. The typed SDK configure_input method requires both vil and vih.

configure_input(config)​

Configures the pin as a digital input with custom thresholds.

await pin.configure_input({
vil: 0.8, // Input low threshold
vih: 2.0 // Input high threshold
});

Parameters:

  • config (PartialDigitalInputConfiguration / &DigitalInputConfig):
    • vil (number / float / f64, required): Voltage input low threshold ([-25, 25] V, inclusive)
    • vih (number / float / f64, required): Voltage input high threshold ([-25, 25] V, inclusive)

Returns: Promise<DigitalInputConfiguration> / DigitalInputConfiguration / Result<GpioConfiguration, At1000Error> - Full configuration including mode and direction

Exceptions:

  • Throws validation error if vil or vih is outside the inclusive range [-25, 25] V

configure_output(config)​

Configures the pin as a digital output with custom voltage levels and thresholds.

await pin.configure_output({
value: true, // Initial state
vol: 0.0, // Output low voltage
voh: 3.3, // Output high voltage
vil: 0.8, // Optional input low threshold
vih: 2.0 // Optional input high threshold
});

Parameters:

  • config (PartialDigitalOutputConfiguration / &DigitalOutputConfig):
    • value (boolean | number / bool | int / bool, required): Initial output state (true/1 = high, false/0 = low)
    • vol (number / float / f64, required): Voltage output low level (0 to 24V)
    • voh (number / float / f64, required): Voltage output high level (0 to 24V)
    • vil (number / float / Option<f64>, optional): Voltage input low threshold ([-25, 25] V, inclusive)
    • vih (number / float / Option<f64>, optional): Voltage input high threshold ([-25, 25] V, inclusive)

Returns: Promise<DigitalOutputConfiguration> / DigitalOutputConfiguration / Result<GpioConfiguration, At1000Error> - Full configuration

Exceptions:

  • Throws validation error if vol or voh is outside the range [0, 24]
  • Throws validation error if vil or vih is outside the inclusive range [-25, 25] V
note

When vil and vih are omitted, they are automatically deduced from the given voh and vol parameters.


configure_open_drain(config)​

Configures the pin as an open-drain output (useful for I2C, 1-Wire, etc.).

await pin.configure_open_drain({
value: true,
vil: 0.8,
vih: 2.0
});

Parameters:

  • config (PartialOpenDrainConfiguration / &OpenDrainConfig):
    • value (boolean | number / bool | int / bool, required): Initial state (true = floating/high-Z, false = low)
    • vil (number / float / f64, required): Voltage input low threshold ([-25, 25] V, inclusive)
    • vih (number / float / f64, required): Voltage input high threshold ([-25, 25] V, inclusive)

Returns: Promise<OpenDrainConfiguration> / OpenDrainConfiguration / Result<GpioConfiguration, At1000Error> - Full configuration

Exceptions:

  • Throws validation error if vil or vih is outside the inclusive range [-25, 25] V

read()​

Reads the current logical state of the pin.

const state = await pin.read();
console.log("Pin is", state ? "HIGH" : "LOW");

Returns: Promise<boolean> / bool / Result<bool, At1000Error> - true for HIGH, false for LOW


write(value)​

Writes a logical value to the pin (must be configured as output).

await pin.write(true); // Set HIGH
await pin.write(false); // Set LOW
await pin.write(1); // Set HIGH (numeric)
await pin.write(0); // Set LOW (numeric)

Parameters:

  • value (boolean | number / bool | int / bool): Value to write (true/1 = HIGH, false/0 = LOW)

Returns: Promise<boolean> / bool / Result<bool, At1000Error> - The written value as boolean


AnalogIO Class​

configure_input()​

Configures the pin as an analog input for voltage measurement.

await pin.configure_input();

Returns: Promise<AnalogInputConfiguration> / AnalogInputConfiguration / Result<GpioConfiguration, At1000Error> - Configuration with mode "analog" and direction "input"


configure_output(value)​

Configures the pin as an analog output and sets the initial voltage.

await pin.configure_output(3.3); // Set to 3.3V

Parameters:

  • value (number / float / f64): Initial output voltage (0 to 24V)

Returns: Promise<AnalogOutputConfiguration> / AnalogOutputConfiguration / Result<GpioConfiguration, At1000Error> - Full configuration

Exceptions:

  • Throws validation error if value is outside the range [0, 24]

read()​

Reads the current analog voltage at the pin.

const voltage = await pin.read();
console.log("Voltage:", voltage, "V");

Returns: Promise<number> / float / Result<f64, At1000Error> - Measured voltage in volts


write(value)​

Sets the analog output voltage (must be configured as analog output).

await pin.write(3.3); // Set to 3.3V
await pin.write(5.0); // Set to 5.0V
await pin.write(12.5); // Set to 12.5V

Parameters:

  • value (number / float / f64): Voltage to output (0 to 24V)

Returns: Promise<number> / float / Result<f64, At1000Error> - The set voltage

Exceptions:

  • Throws validation error if value is outside the range [0, 24]

GpioSequence​

A sequence is a list of GPIO commands sent in one request. The device runs them back to back while holding the I/O subsystem, so no other I/O request interleaves, and answers with a single report. Every pin a sequence touches must already be configured, and every duration is in seconds.

Appending a command sends nothing: only run() talks to the device. repeat, delay, stable_for and expect are carried out on the device, so no network round trip separates two readings. GPIO command sequences walks through a complete example.

read(io, options?)​

Appends a read of one pin, or of several pins in the order given.

const report = await tester.gpio.sequence()
.read(0) // one pin, one reading
.read([0, 1, 2]).as('scan') // three pins, in the order given
.read(0, { repeat: 4, delay: 0.001 }).as('burst') // four readings, 1 ms apart
.read(1, { expect: true }).as('check') // assert every reading
.run();

Parameters:

  • io (number | number[] / int | list[int] / impl Into<SequencePins>): Pin, or distinct pins walked in the order given (0-31). Rust takes a usize or a Vec<usize>
  • options (GpioSequenceReadOptions / keyword arguments / ReadOptions, optional):
    • repeat (number / int / Option<u32>): Total number of readings of the whole pin list, an integer of at least 1, and 1 when omitted
    • delay (number / float / Option<f64>): Seconds between two passes of a repeated read, requires an effective repeat of at least 2
    • expect (boolean | { min, max } / bool | tuple[float, float] | SequenceBounds / Option<SequenceExpect>): Assertion checked on every reading: a level on a digital pin, inclusive voltage bounds on an analog one

Returns: GpioSequence / GpioSequence / Result<GpioSequence, At1000Error> - The sequence with the command appended

Exceptions:

  • Throws validation error if a pin is outside 0-31, the pin list is empty or lists a pin twice, repeat is 0, delay comes with an effective repeat below 2, or an expect bound is outside [-25, 25] V
note

A single pin read once reports its reading in the step's value. A pin list, or a repeat above 1, reports one entry per pin and pass in samples instead.


write(io, value)​

Appends a write of one pin, or of several pins in the order given.

const report = await tester.gpio.sequence()
.write(0, true) // one pin
.write([0, 1, 2], false) // one value on every listed pin
.write([0, 1, 2], [true, false, 2.5]) // one value per pin
.run();

Parameters:

  • io (number | number[] / int | list[int] / impl Into<SequencePins>): Pin, or distinct pins walked in the order given (0-31)
  • value (boolean | number | (boolean | number)[] / bool | float | Sequence[bool | float] / impl Into<SequenceValues>): One value applied to every listed pin, or one value per pin, in the order of the pin list. Rust takes a GpioValue or a Vec<GpioValue>

Returns: GpioSequence / GpioSequence / Result<GpioSequence, At1000Error> - The sequence with the command appended

Exceptions:

  • Throws validation error if a pin is outside 0-31, the pin list is empty or lists a pin twice, a value is not finite, or a list of values is not exactly as long as the pin list
note

Every value is read through its own pin's mode, exactly like a per-pin write, so one command may mix a level and a voltage. On a digital pin a number is a level (0 low, anything else high); on an analog pin it is a voltage in [0, 24] V and a boolean is rejected. A write of one pin reports what it applied in the step's value, a write of several pins one entry per pin in samples.


wait_voltage(io, options)​

Appends a wait for an analog pin to read inside given voltage bounds.

const report = await tester.gpio.sequence()
.wait_voltage(3, { min: 2.0, max: 3.0, timeout: 0.5 }).as('band')
.run();

Parameters:

  • io (number / int / usize): A single analog pin (0-31)
  • options (GpioSequenceWaitVoltageOptions / keyword arguments / WaitVoltageOptions):
    • min (number / float / Option<f64>): Lowest accepted voltage, [-25, 25] V. Alone, the wait completes when the pin reads at or above it
    • max (number / float / Option<f64>): Highest accepted voltage, [-25, 25] V. Alone, the wait completes when the pin reads at or below it
    • timeout (number / float / Option<f64>, optional): Seconds this wait may take, clamped to what is left of the sequence budget
    • stable_for (number / float / Option<f64>, optional): Seconds the condition must hold. Any sample that fails it restarts the window

Returns: GpioSequence / GpioSequence / Result<GpioSequence, At1000Error> - The sequence with the command appended

Exceptions:

  • Throws validation error if neither min nor max is given, min is above max, a bound is outside [-25, 25] V, or timeout or stable_for is not finite and greater than zero
note

Both bounds are inclusive, and together they describe a band. The semantics are level-based, not edge-based: a pin already inside the bounds completes the wait at once, unless stable_for first requires them to hold. The device reads the pin from polled ADC samples, so an excursion shorter than the interval between two samples is not observable. A wait that runs out of time fails the whole run with SEQUENCE_TIMED_OUT.


wait_level(io, level, options?)​

Appends a wait for a digital pin to read a level.

const report = await tester.gpio.sequence()
.wait_level(0, 'high', { stable_for: 0.02, timeout: 0.5 }).as('ready')
.run();

Parameters:

  • io (number / int / usize): A single digital pin (0-31)
  • level ('low' | 'high' | boolean / "low" | "high" | bool | SequenceLevel / impl Into<SequenceLevel>): Level to wait for, where true is high. Rust takes a bool or a SequenceLevel
  • options (GpioSequenceWaitOptions / keyword arguments / WaitLevelOptions, optional):
    • timeout (number / float / Option<f64>): Seconds this wait may take, clamped to what is left of the sequence budget
    • stable_for (number / float / Option<f64>): Seconds the level must hold. Any sample that fails it restarts the window

Returns: GpioSequence / GpioSequence / Result<GpioSequence, At1000Error> - The sequence with the command appended

Exceptions:

  • Throws validation error if the pin is outside 0-31, or timeout or stable_for is not finite and greater than zero
note

The level is resolved from the pin's configured vil and vih, from polled ADC samples, so a transition shorter than the sampling interval is not observable. A pin that already reads the level completes the wait at once, unless stable_for first requires it to hold.


delay(duration)​

Appends a pause the device takes between the surrounding commands.

const report = await tester.gpio.sequence()
.write(0, true)
.delay(0.01) // 10 ms, taken on the device
.write(0, false)
.run();

Parameters:

  • duration (number / float / f64): Pause in seconds, finite and greater than zero

Returns: GpioSequence / GpioSequence / Result<GpioSequence, At1000Error> - The sequence with the command appended


hold() / release()​

Appends a hold of the outputs, or a release that applies every write buffered since the hold. Same effect as gpio.hold() and gpio.release() (see Output synchronization), without one request each.

const report = await tester.gpio.sequence()
.hold() // freeze the outputs
.write([0, 1, 2], [true, true, false])
.release() // apply the three writes together
.run();

Parameters: None

Returns: GpioSequence / GpioSequence / Result<GpioSequence, At1000Error> - The sequence with the command appended

caution

A hold whose release never runs, because a later command stopped the run, leaves the outputs held. Call gpio.release() in your error path.


as(name) / as_(name)​

Names the command just appended, so its result can be read back by name. The method is as() in JavaScript and as_() in Python and Rust, and it adds no command of its own.

const report = await tester.gpio.sequence()
.read(0).as('initial')
.delay(0.01).as('pause')
.run();

console.log(report.named.initial.value, report.named.pause.elapsed);

Parameters:

  • name (string / str / impl Into<String>): Exact, case-sensitive name. Names stay in the SDK and never reach the device

Returns: GpioSequence / GpioSequence / Result<GpioSequence, At1000Error> - The same sequence, with the last command named

Exceptions:

  • Throws validation error if the name is empty, if the name is already used in this sequence, if the command just appended already has a name, or if no command has been appended yet

run(options?)​

Sends the appended commands and returns one result per command. The only method that reaches the device.

const report = await tester.gpio.sequence()
.read([0, 1, 2]).as('scan')
.run({ timeout: 10 });

console.log(report.elapsed, report.results.length);

Parameters:

  • options ({ timeout?: number } / timeout keyword argument / Option<f64>, optional):
    • timeout (number / float / Option<f64>): Time budget for the whole run in seconds, 30 when omitted. It bounds every command, including every pass of a repeated read, and a per-wait timeout is clamped to what is left of it

Returns: Promise<GpioSequenceReport> / SequenceReport / Result<SequenceReport, At1000Error> - One result per submitted command, in order

Exceptions:

  • Throws validation error if no command has been appended, or if timeout is not finite and greater than zero
  • A run that does not complete returns no report: a wait that ran out of time and an exhausted budget fail with SEQUENCE_TIMED_OUT, a failed read assertion with SEQUENCE_ASSERTION_FAILED, both naming the command that stopped the run (see Sequence errors)
caution

Writes that already ran stay applied when a run fails, and a hold without its release leaves the outputs held. Validation is different: the device checks the whole list before it runs anything, so a rejected sequence changes nothing.


Types​

DigitalInputConfiguration​

{
mode: "digital",
direction: "input",
vil: number, // [-25, 25] V, inclusive
vih: number // [-25, 25] V, inclusive
}

Rust: ikalogic_at1000::DigitalInputConfig

pub struct DigitalInputConfig {
pub vil: f64,
pub vih: f64,
}

DigitalOutputConfiguration​

{
mode: "digital",
direction: "output",
value: boolean,
vol: number, // 0 to 24V
voh: number, // 0 to 24V
vil: number, // [-25, 25] V, inclusive
vih: number // [-25, 25] V, inclusive
}

Rust: ikalogic_at1000::DigitalOutputConfig

pub struct DigitalOutputConfig {
pub value: bool,
pub vol: f64,
pub voh: f64,
pub vil: Option<f64>,
pub vih: Option<f64>,
}

OpenDrainConfiguration​

{
mode: "digital",
direction: "open_drain",
value: boolean,
vil: number, // [-25, 25] V, inclusive
vih: number // [-25, 25] V, inclusive
}

Rust: ikalogic_at1000::OpenDrainConfig

pub struct OpenDrainConfig {
pub value: bool,
pub vil: f64,
pub vih: f64,
}

AnalogInputConfiguration​

{
mode: "analog",
direction: "input"
}

Rust: no analog configuration struct. configure_input() takes no argument and returns GpioConfiguration.

AnalogOutputConfiguration​

{
mode: "analog",
direction: "output",
value?: number // 0 to 24V
}

Rust: no analog configuration struct. configure_output() takes the voltage as an f64 and returns GpioConfiguration.

GpioConfiguration​

What every configure call and read_config() return: in JavaScript and Python, one of the five configurations above.

DigitalInputConfiguration | DigitalOutputConfiguration | OpenDrainConfiguration
| AnalogInputConfiguration | AnalogOutputConfiguration

Rust: ikalogic_at1000::GpioConfiguration. The Rust SDK has no per-mode configuration type: configure_input, configure_output, configure_open_drain and read_config all return the same seven-field struct, whatever the pin's mode.

pub struct GpioConfiguration {
pub mode: GpioMode,
pub direction: GpioDirection,
pub value: GpioValue,
pub vil: f64,
pub vih: f64,
pub vol: f64,
pub voh: f64,
}

pub enum GpioMode { Analog, Digital }

pub enum GpioDirection { Input, Output, OpenDrain }

pub enum GpioValue { Digital(bool), Analog(f64) }

SyncState​

{ hold_state: "held" | "released" }

Rust: ikalogic_at1000::SyncState

pub struct SyncState {
pub hold_state: HoldState,
}

pub enum HoldState { Released, Held }

GpioSequenceStep​

{
op: "read" | "write" | "wait_voltage" | "wait_level" | "delay" | "hold" | "release",
io?: number | number[], // absent on delay, hold and release
value?: boolean | number, // a one-pin read that did not repeat, a one-pin write, or a wait
samples?: GpioSequenceSample[], // a pin list, or a read that repeated
elapsed: number // seconds from the sequence start to the end of this command
}

Python: SequenceStep

Rust: ikalogic_at1000::SequenceStep

pub struct SequenceStep {
pub op: SequenceOp,
pub io: Option<SequencePins>,
pub value: Option<GpioValue>,
pub samples: Option<Vec<SequenceSample>>,
pub elapsed: f64,
}

pub enum SequenceOp { Read, Write, WaitVoltage, WaitLevel, Delay, Hold, Release }

pub enum SequencePins { One(usize), Many(Vec<usize>) }

GpioSequenceSample​

{
io: number,
value: boolean | number,
elapsed: number // seconds from the sequence start to this entry
}

Python: SequenceSample

Rust: ikalogic_at1000::SequenceSample

pub struct SequenceSample {
pub io: usize,
pub value: GpioValue,
pub elapsed: f64,
}

GpioSequenceReport​

{
elapsed: number, // total duration of the run, in seconds
results: GpioSequenceStep[], // one entry per submitted command, in order
named: Record<string, GpioSequenceNamedResult>, // typed per name declared with as()
get(name: string): GpioSequenceNamedResult | undefined
}

Python: SequenceReport, read through report.results and report.named["name"].

Rust: ikalogic_at1000::SequenceReport

pub struct SequenceReport {
pub elapsed: f64,
// the results and the name associations are private
}

In Rust the results and the name associations are private, so a name stays bound to its command: read them through report.results(), which borrows a &[SequenceStep], and report.get(name), which returns an Option<SequenceNamedResult>.

GpioSequenceNamedResult​

// scalar: a one-pin read or write, and every wait
{ kind: "scalar", name: string, index: number, op: string, elapsed: number, io: number | number[], value: boolean | number }

// collection: a read over a pin list or with repeat > 1, and a multi-pin write
{ kind: "collection", name: string, index: number, op: string, elapsed: number, io: number | number[], samples: GpioSequenceSample[] }

// control: hold, release and delay
{ kind: "control", name: string, index: number, op: string, elapsed: number }

Python: SequenceNamedResult, a union of SequenceScalarResult, SequenceCollectionResult and SequenceControlResult discriminated on kind.

Rust: ikalogic_at1000::SequenceNamedResult, an enum whose variants borrow from the report.

pub enum SequenceNamedResult<'a> {
Scalar {
name: &'a str,
index: usize,
op: SequenceOp,
kind: SequenceResultKind,
io: &'a SequencePins,
elapsed: f64,
value: GpioValue,
},
Collection {
name: &'a str,
index: usize,
op: SequenceOp,
kind: SequenceResultKind,
io: &'a SequencePins,
elapsed: f64,
samples: SequenceSamples<'a>,
},
Control {
name: &'a str,
index: usize,
op: SequenceOp,
kind: SequenceResultKind,
elapsed: f64,
},
}

pub enum SequenceResultKind { Scalar, Collection, Control }

Sequence errors​

codeHTTPWhen it fires
SEQUENCE_TIMED_OUT409A wait exceeded its timeout, or what was left of the sequence budget: Command 3 ('wait_voltage'): pin 3 still reads 2.1000 V. The budget ran out before a command started: Command 7 ('read'): the sequence exhausted its 30 s budget
SEQUENCE_ASSERTION_FAILED409A reading did not satisfy a read's expect: Command 4 ('read'): pin 1 read false, which does not satisfy 'expect'
INVALID_VALUE400A field is missing, or the op does not accept it: Command 2 ('delay'): field 'io' is not accepted by 'delay'. Also an empty commands list, a pin listed twice, and a pin list where a wait requires a single pin
OUT_OF_RANGE_VALUE400Sequence of 70000 commands exceeds the maximum of 65536, Command 1 ('read'): field 'io' pin 40 is out of range [0; 31], or Field 'timeout' must be a finite duration greater than zero
INVALID_CONFIGURATION400A pin is not configured for the command: Command 0 ('write'): cannot write to pin 5, configured as input, or Command 1 ('wait_voltage'): pin 3 must be in 'analog' mode
JSON_DESERIALIZATION400Malformed JSON, an unknown field, or a field of the wrong type, rejected before the sequence is validated

The four 400 codes reject the whole sequence before anything runs. The two 409 codes stop a run part-way, and every write that already executed stays applied. A failure surfaces as an ApiError carrying that code in JavaScript and Python, and as At1000Error::Api(err) with err.code in Rust.


Notes​

  • All 32 pins support both digital and analog modes
  • Voltage range: -25 V to +25 V for measured inputs and digital thresholds, 0 to 24 V for outputs. Both ranges are inclusive.
  • Open-drain mode is ideal for protocols requiring external pull-ups (I2C, 1-Wire)
  • When writing digital values, both boolean and number (0/1) are accepted
  • Analog readings provide precise voltage measurements
  • Use hold() and release() for synchronized multi-pin operations
  • Use sequence() to run many reads, writes and waits in one request; see GPIO command sequences