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.