Skip to content

Python API reference

This page is generated from the docstrings of openhal.client, and the methods are grouped as in the Python guide, with the route each method calls.

Connect

connect function

connect(
    url: str | None = None,
    token: str | None = None,
    *,
    home: Path | str | None = None,
    timeout: float = 15.0,
    transport: httpx.BaseTransport | None = None,
    check: bool = True,
) -> Client

Open a blocking client to the hub.

With check, the client calls /v1/whoami first, so a wrong address or token fails here.

aconnect function

async aconnect(
    url: str | None = None,
    token: str | None = None,
    *,
    home: Path | str | None = None,
    timeout: float = 15.0,
    transport: httpx.AsyncBaseTransport | None = None,
    check: bool = True,
) -> AsyncClient

Open an async client to the hub.

With check, the client calls /v1/whoami first, so a wrong address or token fails here.

Client class

Client(
    url: str | None = None,
    token: str | None = None,
    *,
    home: Path | str | None = None,
    timeout: float = 15.0,
    transport: httpx.BaseTransport | None = None,
    use_human_token_file: bool = True,
) -> None

A blocking client of the hub's local API v1.

The client makes no safety decisions. Every write and action is decided by the hub's kernel, and a blocked command comes back as a Decision with status blocked, not an exception.

Connection

close() -> None

Close the connection to the hub.

Hub and devices

status() -> HubStatus

The hub's version, protocol version, conformance level and whether a stop is in force.

Calls GET /v1/status.

whoami() -> Principal

The Principal the hub takes this token for, a human or an agent with its scope.

Calls GET /v1/whoami.

devices() -> list[DeviceSummary]

Every device the hub knows, with its driver, its hazard level and whether the device takes commands.

Calls GET /v1/devices.

device(device_id: str) -> DeviceView

One device with its channels, actions, limits, interlocks and safe state.

Calls GET /v1/devices/{id}.

drivers() -> list[DriverInfo]

The drivers the hub knows, with each driver's protocols and whether the driver is installed.

Calls GET /v1/drivers.

Reading and control

read(device: str, channel: str | None = None) -> Reading

Read one channel. read("printer", "bed_temp") or read("printer.bed_temp").

Calls POST /v1/read.

read_many(items: Iterable[str | tuple[str, str]]) -> list[Reading]

Read several channels in one request, each as "device.channel" or a (device, channel) pair.

Calls POST /v1/read_many.

write(
    device: str,
    channel: str,
    value: Value,
    unit: str | None = None,
) -> Decision

Ask the kernel to set a channel. A number needs its unit, as write("printer", "bed_target", 70, "degC").

Calls POST /v1/write.

action(
    device: str,
    action: str,
    args: Mapping[str, Any] | None = None,
    **kwargs: Any,
) -> Decision

Ask the kernel to run an action, with its arguments in args or as keywords.

action("printer", "pause") takes none. A number argument goes with its unit, as duration={"value": 20, "unit": "s"}.

Calls POST /v1/action.

dry_run(commands: Iterable[Command]) -> list[Decision]

Evaluate commands in order as a dry run. No command reaches a driver.

Calls POST /v1/dry_run.

tighten(
    device: str,
    channel: str,
    *,
    min: Mapping[str, Any] | None = None,
    max: Mapping[str, Any] | None = None,
    rate: Mapping[str, Any] | None = None,
) -> TightenResult

Narrow limits for this token's session. Widening is refused with cannot_loosen.

Calls POST /v1/tighten.

stop(reason: str = stop requested) -> StopReport

Put every device in its safe state and lock writes until a human unlocks.

Calls POST /v1/stop.

unlock(phrase: str) -> UnlockResult

Lift the stop lock. Needs a human token and the phrase unlock.

Calls POST /v1/unlock (human).

ramp_to(
    device: str,
    channel: str,
    target: float | Mapping[str, Any] | str,
    unit: str | None = None,
    *,
    interval_s: float = 1.0,
    margin: float = 0.9,
    sleep: Callable[[float], None] = <built-in function sleep>,
) -> list[Decision]

Move a channel to target in unit in steps that each pass its rate limit.

Waits interval_s before each step, so the first step is safe even right after another write. Stops at the first step the kernel does not accept and returns every decision so far.

Calls several POST /v1/write.

Confirmations

confirmations(
    *,
    include_done: bool = False,
) -> list[ConfirmationRequest]

Requests waiting for a person. include_done adds the ones already decided or expired.

Calls GET /v1/confirmations.

confirmation(request_id: str) -> ConfirmationRequest

One confirmation request with its command, its dry run and, once decided, its result.

Calls GET /v1/confirmations/{id}.

approve(request_id: str) -> Decision

Approve a queued request with a human token. The kernel runs every check again first.

Calls POST /v1/confirmations/{id}/approve (human).

reject(
    request_id: str,
    reason: str =,
) -> ConfirmationRequest

Turn down a queued request, with an optional reason. Needs a human token.

Calls POST /v1/confirmations/{id}/reject (human).

Audit log

audit(
    *,
    device: str | None = None,
    status: str | None = None,
    event: str | None = None,
    since: int = 0,
    limit: int = 200,
) -> list[AuditEntry]

Audit log entries after since, newest first, filtered by device, status or event.

Calls GET /v1/audit.

verify_audit() -> AuditVerify

Check the audit log's hash chain. broken_at names the first entry that does not match.

Calls GET /v1/audit/verify.

Discovery

discover(drivers: Iterable[str] | None = None) -> DiscoveryResult

Look for devices with every driver, or only the named ones. Devices no file matches come back unknown.

Calls POST /v1/discover.

unknown_devices() -> list[Unknown]

Devices discovery found that no device file matches yet.

Calls GET /v1/discover/unknown.

Routines

routines() -> list[RoutineSummary]

Every stored routine with its devices, its triggers and whether the routine is enabled.

Calls GET /v1/routines.

routine(routine_id: str) -> Routine

One routine with its source and who drafted and enabled the routine.

Calls GET /v1/routines/{id}.

create_routine(
    routine_id: str,
    source: str,
    *,
    kind: str = yaml,
) -> Routine

Store a routine. Every new routine starts disabled.

Calls POST /v1/routines, always disabled.

routine_dry_run(routine_id: str) -> RoutineDryRun

Evaluate every step of a routine against the kernel without running the routine.

Calls POST /v1/routines/{id}/dry_run.

enable_routine(routine_id: str) -> Routine

Turn a routine on. Needs a human token, or an agent under ADR 0007's opt in.

Calls POST /v1/routines/{id}/enable (human, or an agent under ADR 0007's opt-in).

disable_routine(routine_id: str) -> Routine

Turn a routine off. Every token may turn a routine off except a read scoped agent's token.

Calls POST /v1/routines/{id}/disable.

run_routine(routine_id: str) -> Run

Run a routine once now. Needs a human token.

Calls POST /v1/routines/{id}/run (human).

delete_routine(routine_id: str) -> OkResult

Delete a routine. Needs a human token.

Calls DELETE /v1/routines/{id} (human).

approve_routine(
    routine_id: str,
    device: str,
) -> Approval

Approve a routine's current text to command one device. Needs a human token.

Calls POST /v1/routines/{id}/approve (human).

Driver writer

start_writer(
    *,
    unknown_id: str | None = None,
    driver: str | None = None,
    params: Mapping[str, Any] | None = None,
    sketch_path: str | None = None,
    manual_text: str | None = None,
) -> WriterSession

Start a driver writer session from an unknown device, a driver and its params, a sketch or a manual.

Calls POST /v1/writer/sessions.

writer_session(session_id: str) -> WriterSession

One writer session with its status, draft and the limits no person has confirmed yet.

Calls GET /v1/writer/sessions/{id}.

propose(
    session_id: str,
    draft_yaml: str,
) -> WriterSession

Store a draft device file. The hub marks every limit in the draft as suggested.

Calls POST /v1/writer/sessions/{id}/propose.

interview(session_id: str) -> Interview

The questions a person answers to confirm the draft's limits.

Calls GET /v1/writer/sessions/{id}/interview.

answer(
    session_id: str,
    answers: Mapping[str, Any],
) -> WriterSession

Answer limits interview questions. Needs a human token.

Calls POST /v1/writer/sessions/{id}/answers (human).

finish(
    session_id: str,
    save_as: str | None = None,
) -> FinishResult

Save the confirmed device file, named save_as if given. Needs a human token.

Calls POST /v1/writer/sessions/{id}/finish (human).

test_plan(session_id: str) -> TestPlan

The steps to try the new device file on the real hardware, in order.

Calls GET /v1/writer/sessions/{id}/test_plan.

Tokens and pairing

new_pairing_code() -> PairingCode

A six digit code that pairs a browser for 300 s. Needs a human token.

Calls POST /v1/pair/code (human).

pair(code: str) -> OkResult

Trade a pairing code from openhal pair for a human session on this client.

Calls POST /v1/pair.

tokens() -> list[TokenInfo]

Every token the hub has issued, without the secrets. Needs a human token.

Calls GET /v1/tokens (human).

create_token(
    name: str,
    *,
    kind: str = agent,
    scope: str = read,
) -> NewToken

Issue a token. The answer carries the plain token once. Needs a human token.

Calls POST /v1/tokens (human).

revoke_token(token_id: str) -> OkResult

Revoke a token so the hub refuses the token from then on. Revoking needs a human token.

Calls DELETE /v1/tokens/{id} (human).

Any route

call(call: Call[T]) -> T

Send one call and parse the answer. Raises a subclass of OpenHALError on failure.

AsyncClient class

AsyncClient(
    url: str | None = None,
    token: str | None = None,
    *,
    home: Path | str | None = None,
    timeout: float = 15.0,
    transport: httpx.AsyncBaseTransport | None = None,
    use_human_token_file: bool = True,
) -> None

An asyncio client of the hub's local API v1.

The client makes no safety decisions. Every write and action is decided by the hub's kernel, and a blocked command comes back as a Decision with status blocked, not an exception.

AsyncClient has every method of Client as a coroutine, with the same arguments. The methods below belong to AsyncClient alone.

Connection

async aclose() -> None

Close the connection to the hub.

Reading and control

async ramp_to(
    device: str,
    channel: str,
    target: float | Mapping[str, Any] | str,
    unit: str | None = None,
    *,
    interval_s: float = 1.0,
    margin: float = 0.9,
    sleep: Callable[[float], Awaitable[None]] = sleep,
) -> list[Decision]

Move a channel to target in unit in steps that each pass its rate limit.

Waits interval_s before each step, so the first step is safe even right after another write. Stops at the first step the kernel does not accept and returns every decision so far.

Calls several POST /v1/write.

Live events

events() -> AsyncIterator[dict[str, Any]]

Yield live events (hello, reading, state, audit, watchdog) from /v1/live.

Needs the websockets package, which the hub extra installs.

Commands

Write class

Set one write channel. A number carries its unit, in unit or as a {value, unit} mapping.

Field Type
kind Literal
device str
channel str
value str | bool | dict[str, Any] | int | float
unit str | None

Action class

Run one action with typed arguments.

Field Type
kind Literal
device str
action str
args dict

Results

Decision class

The kernel's answer to one write or action.

Field Type
status str
code str | None
message str
device str
target str
value Any
unit str | None
limit dict[str, Any] | None
request_id str | None

Reading class

Field Type
device str
channel str
ok bool
value Image | bool | int | float | str | dict[str, Any] | None
unit str | None
at float | None
suspect bool | None
bytes int | None
error str | None

Image class

An image channel's value, such as a camera frame. content() gives the image itself.

Field Type
mime_type str
bytes int
width int | None
height int | None
data str | None

HubStatus class

Field Type
product str
version str
spec str
protocol_version str | None
conformance str | None
allow_lan bool | None
stopped bool
stop_reason str | None
devices int
connected int
pending_confirmations int
started_at float | None
load_problems list
you Principal

DeviceSummary class

Field Type
id str
name str
model str | None
manufacturer str | None
driver str
hazard str
writable bool
connected bool
unavailable str | None
last_error str | None
verification str | None
source str | None
conformance str | None
simulated bool

DeviceView class

Field Type
id str
name str
model str | None
manufacturer str | None
driver str
hazard str
writable bool
connected bool
unavailable str | None
last_error str | None
verification str | None
source str | None
conformance str | None
simulated bool
unconfirmed list
warnings list
path str | None
read dict
write dict
actions dict
interlocks list
watchdog Watchdog
safe_state list
notes str | None

StopReport class

Field Type
reason str
by str
cancelled_requests int
devices list

ConfirmationRequest class

Field Type
id str
status str
command dict
requested_by str
created_at float
expires_at float
dry_run Decision
decided_by str | None
result Decision | None

Routines and audit

RoutineSummary class

Field Type
id str
kind str
enabled bool
sha256 str
devices list
max_hazard str | None
triggers list
drafted_by str | None
enabled_by str | None
disabled_reason str | None
last_run Run | None

Routine class

Field Type
id str
kind str
enabled bool
sha256 str
devices list
max_hazard str | None
triggers list
drafted_by str | None
enabled_by str | None
disabled_reason str | None
last_run Run | None
source str
source_raw str | None
history list
last_dry_run RoutineDryRun | None

AuditEntry class

Field Type
seq int
at float
event str
principal str
device str | None
target str | None
status str | None
code str | None
details dict
hash str

WriterSession class

Field Type
id str
status str
detected dict
sources list
draft_yaml str | None
problems list
unconfirmed list

Errors

OpenHALError exception

Subclass of Exception.

Base class for every client error. status_code is the HTTP status when there was one.

HubNotRunning exception

Subclass of OpenHALError.

Nothing answered at the hub address.

Unauthorized exception

Subclass of OpenHALError.

The hub did not accept the token, or no token was found.

NotFound exception

Subclass of OpenHALError.

The device, channel, request, routine or session does not exist.

NotSupported exception

Subclass of NotFound.

The hub is older than this client and does not have the route at all.

Refused exception

Subclass of OpenHALError.

The hub understood the call and refused it, for example an agent asking to approve.

BadRequest exception

Subclass of OpenHALError.

The hub could not read the request. detail holds the validation problems.

HubError exception

Subclass of OpenHALError.

The hub failed while answering. Check the hub log.