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 checked 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 maps this token to, a human or an agent with its scope.

Calls GET /v1/whoami.

devices() -> list[DeviceSummary]

Every device registered with the hub, 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 has, 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

Send a request 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

Send a request 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 blocked with cannot_loosen.

Calls POST /v1/tighten.

risk(device: str) -> DeviceRisk

Every channel's and action's risk level on a device, with the reasons.

Calls GET /v1/devices/{id}/risk.

set_risk(
    device: str,
    name: str,
    level: str,
    *,
    phrase: str | None = None,
) -> RiskResult

Change a level. Needs a human token. Lowering takes the phrase confirm, or waits for a person.

Calls POST /v1/risk (human).

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.

Find, wait, sample and runs

find(
    text: str | None = None,
    *,
    unit: str | None = None,
    kind: str | None = None,
    hazard: str | None = None,
    connected: bool | None = None,
    limit: int = 50,
) -> FindResult

Channels and actions on every device that match, as device.channel targets.

text matches names, descriptions, units and what a unit measures, so find("temperature") finds a degC channel named temp. unit matches every channel a value in that unit converts to. kind is readable, writable or action, and readable includes write channels.

Calls GET /v1/find.

wait_until(
    condition: str,
    *,
    timeout_s: float = 30.0,
    interval_s: float = 1.0,
) -> WaitResult

Read the channels condition names until it holds or timeout_s passes.

The condition is in the routine expression language, with each channel as device.channel.

Calls POST /v1/wait_until.

sample(
    targets: Iterable[str],
    *,
    duration_s: float,
    interval_s: float = 1.0,
) -> SampleResult

Read device.channel targets every interval_s for duration_s, with min, max and mean for numbers.

Calls POST /v1/sample.

run_steps(
    steps: Iterable[calls.Step],
    *,
    repeat: int = 1,
    wait_s: float = 5.0,
) -> StepRun

Check the steps as a plan, then run them in order through the kernel.

A step is a Write, an Action or a dict with kind read, write, action, wait or wait_until. The reply comes when the run ends or after wait_s, with run_id for run() and cancel_run().

Calls POST /v1/runs.

run(
    run_id: str,
    *,
    wait_s: float = 0.0,
    last: int = 50,
) -> StepRun

One run with its last step results, after waiting up to wait_s for the run to end.

Calls GET /v1/runs/{id}.

runs() -> list[StepRun]

The runs this token started, newest first. A human token sees every run.

Calls GET /v1/runs.

cancel_run(run_id: str) -> StepRun

End a run before its next step. Only the token that started the run, or a human token.

Calls POST /v1/runs/{id}/cancel.

run_caps() -> RunCaps

The caps on steps per run, seconds per run, wait or sample, and runs per token.

Calls GET /v1/runs/caps.

set_run_caps(
    *,
    max_steps: int | None = None,
    max_seconds: float | None = None,
    per_agent: int | None = None,
    phrase: str | None = None,
) -> RunCaps

Change the caps. Needs a human token, and raising a cap needs the phrase confirm.

Calls POST /v1/runs/caps (human).

Confirmations

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

Requests held for a person. include_done adds the requests 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

Search 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 test 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 response 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 no longer accepts the token. Revoking needs a human token.

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

Any route

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

Send one call and parse the response. 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 checked 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 result for 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

Find, wait, sample and runs

FindResult class

Field Type
count int
matches list

WaitResult class

How a wait on a condition ended, with the readings of its last check.

Field Type
condition str
status str
held bool
result bool | None
waited_s float
checks int
interval_s float
readings list

SampleResult class

Field Type
status str
interval_s float
duration_s float
started_at float | None
series list

StepRun class

A run of steps. status is running, done, blocked, pending, failed or cancelled.

Field Type
run_id str
owner str
status str
message str
steps int
repeat int
total_steps int
done_steps int
current dict[str, Any] | None
started_at float | None
finished_at float | None
elapsed_s float | None
stop RunStop | None
request_id str | None
plan dict
results list

RunCaps class

Field Type
max_steps int
max_seconds float
per_agent int
max_points int | None
changed dict

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.

No response came from 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 parsed the call and blocked it, for example an agent's request 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 handling the call. Check the hub log.