Skip to main content

Device access control

An AT1000 drives real hardware - relays, power rails, GPIO. If two test PCs pushed conflicting states at the same time, the result would be undefined. AT1000 solves this with a simple open-first access model: exactly one controller holds the device at a time.

Why a session is needed​

Every controller must open() the device before driving hardware. The server mints an opaque session token and records it as the active token - exactly one is active at any moment. The SDK attaches that token to every request; state-changing calls from any other controller are rejected.

Discovery, most monitoring reads, and event subscriptions need no token. Consuming knob reads and prompt GETs are exceptions: they require a controlling session.

Last-open-wins takeover​

Remote-to-remote takeover follows last-open-wins: a later open() replaces the active token. The previous holder discovers revocation on its next hardware call.

Client A: open() → token a1 (A controls the device)
Client B: open() → token b2 (B controls the device; a1 revoked)
Client A: relay.close() → 409 ACCESS_REVOKED (taken over by B)

Tokens never expire by time. A token dies only by replacement (a later open()), a front-panel reclaim (see below), a project container exiting, or a server restart. A crashed or finished controller leaves only an inert token behind, which the next open() silently replaces - there is nothing to release.

Read-only monitoring​

To watch a device without taking control, open it read-only. Most monitoring APIs remain available, as do event subscriptions. Consuming knob reads and prompt GETs require the controlling session:

const monitor = await AT1000.open(host, { readonly: true });
const state = await monitor.power.dut(0).read();

Handling revocation​

When your controller has been taken over, the next hardware call raises a structured error. In JavaScript this is an ApiError whose code is either ACCESS_REVOKED (a token the server minted and later replaced) or DEVICE_NOT_OPEN (no token, or an unknown one). In Python these surface as the dedicated AccessRevokedError and DeviceNotOpenError exceptions. In Rust they are the At1000Error::Revoked and At1000Error::NotOpen variants, each carrying the ApiError:

import { AT1000, ApiError } from '@ikalogic/at1000';

try {
await tester.relays.relay(0).close();
} catch (err) {
if (err instanceof ApiError && err.code === 'ACCESS_REVOKED') {
console.log('Device was taken over by another controller.');
// Re-open to take the device back (itself a takeover):
tester = await AT1000.open(host);
} else if (err instanceof ApiError && err.code === 'DEVICE_NOT_OPEN') {
console.log('No session - call AT1000.open() first.');
} else {
throw err;
}
}

Checking who holds the device​

tester.session.state() reports the current holder - never the token. It returns the holder's kind (remote for an SDK/interface controller, project for an in-container test sequence), label, and since timestamp, or just active: false when nobody holds the device:

const state = await tester.session.state();
if (state.active) {
console.log(`Held by ${state.label} (${state.kind})`);
} else {
console.log('Device is free.');
}

Reclaiming from the front panel​

Hold the knob for over five seconds to reclaim the device from any panel screen, including an operator prompt. This also stops a running standalone project.

The project has up to three additional seconds for cleanup before forced termination. Access transfers, the active token becomes invalid, and the menu returns only after stopping succeeds. If stopping fails, the current holder keeps access. See standalone project shutdown.