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.