Skip to content

http

tested on simulator

Tier Tier 1
Protocol HTTP/1.1 JSON APIs, HTTP/1.1 plain text APIs, OpenAPI 3 described APIs
Covers smart plugs and relays, networked sensors, lab and bench gadgets with REST APIs, DIY ESP32 and Pico W web servers
Hazard default medium
Conformance control
Tested with httpx.MockTransport with a simulated smart plug

Estimated reach. At least tens of thousands of models. Any device with a documented local HTTP API can be described in a device file without new code. Shelly alone documents a local HTTP API for its whole Gen1 and Gen2 range, Tasmota exposes an HTTP command endpoint on every flashed device, and most microcontroller web server sketches speak JSON over HTTP. The count is limited only by devices that need a protocol other than HTTP or a login flow the driver does not support.

The http driver talks to devices that expose a local HTTP API, and every request the driver sends comes from the device file. Read channels name a request and where the value sits in the response, and write channels and actions name a request and a body or query template. With an OpenAPI 3 document, describe() proposes the channels for you.

What it supports

  • Read channels of type number, bool, string, enum and image, from JSON bodies (with a JSON path) or plain text bodies.
  • Write channels of type number, bool, string and enum, sent as a JSON body, a query string or a path segment, with GET, POST, PUT, PATCH or DELETE.
  • Read-back for write channels through a state request. Without a state request, reading a write channel returns the last value the driver sent, or no value before the first write.
  • Actions whose arguments fill the same templates.
  • A bearer token or API key from an environment variable.
  • describe() from an OpenAPI 3 JSON document.

Setup

uv tool install --compile-bytecode "openhal-hub[http] @ git+https://github.com/sohan-bhat/OpenHAL"

httpx is also part of the hub extra, so a hub install already has it.

Parameters

ParameterDefaultMeaning
base_urlrequiredhttp:// or https:// URL of the device. The shorthand http: "http://192.168.1.50" sets it
timeout5 sHard deadline for each request, enforced by the driver
headersnoneExtra request headers. Names that look like credentials are rejected
token_envnoneName of an environment variable holding a token
auth_headerAuthorizationHeader that carries the token
auth_schemeBearerPrefix before the token. Use "" for a bare API key header
verify_tlstrueCheck HTTPS certificates
health/Path fetched with GET for connect and the heartbeat. Any HTTP response counts as alive
identifynonePath fetched with GET by identify(). Scalar fields of the JSON reply become identity fields
openapinonePath of an OpenAPI 3 JSON document for describe()
max_response_bytes1048576Larger responses are refused

Channel maps

A read channel's map takes these keys.

read:
  power: { unit: W, map: { path: /api/status, json: $.power.w } }
  mode: { type: enum, values: [auto, eco], map: { path: /api/mode } }   # text body
  snapshot: { type: image, map: { path: /snapshot.jpg } }
KeyDefaultMeaning
pathrequiredRequest path, starting with /
methodGETGET or POST
querynoneQuery parameters
bodynoneJSON body, for POST reads such as JSON-RPC
jsonnoneJSON path to the value. A subset of RFC 9535 with keys and list indexes, such as $.relays[0].on
formatjson with a path, else textHow to read the body
on, offcommon wordsWords that mean true and false for bool channels
scale, offset1, 0channel value = device value × scale + offset

A write channel's map takes these keys.

write:
  relay:
    type: bool
    map: { path: /api/relay, body: { on: "{value}" }, state: { path: /api/status, json: relay.on } }
  level:
    unit: percent
    min: 0
    max: 100
    map: { path: /api/dimmer, method: PUT, body: { level: "{value}" }, scale: 0.392157 }
KeyDefaultMeaning
pathrequiredRequest path. {value} in the path is URL-encoded
methodPOSTAny of GET, POST, PUT, PATCH, DELETE
query, bodynoneTemplates. A string that is exactly "{value}" keeps the value's JSON type
on, offthe boolean itselfWhat to send for true and false
scale, offset1, 0Device value = (channel value - offset) / scale. Whole results are sent as integers
statenoneA read map used to read the channel back

Actions use path, method, query and body the same way, with {arg} for each argument, and an optional json path that picks the result out of the reply.

Example

openhal/drivers/http/examples/smart-plug.hal.yaml describes a Shelly Plus Plug S through its Gen2 RPC API. The plug's output relay is switched with GET /rpc/Switch.Set?id=0&on=true, which is how that API works.

Describe from OpenAPI

With openapi: /openapi.json, describe() fetches the document and proposes

  • read channels for the scalar properties of each GET operation's JSON reply, one nesting level deep,
  • write channels for the scalar properties of each PUT, POST or PATCH JSON body, only when numbers carry minimum and maximum,
  • actions for POST operations without a body.

Units come from an x-unit property where the document has one, otherwise dimensionless, and the notes list the channels that need a real unit. Every write channel and action is marked suggested: true, so the file stays read-only until a human confirms the limits. Paths with parameters, remote $ref pointers and YAML documents are skipped.

Errors

  • No response within timeout raises TimeoutError.
  • Connection failures, HTTP status 400 and above, bodies that are not JSON when JSON is expected, missing JSON fields, numbers that are not numbers and enum values outside the list raise DriverError with the request and what to check.

Limitations

  • The driver has no login flows (session cookies, digest auth, OAuth), so devices that need a login flow are not supported yet.
  • The driver has no streaming or server-sent events, so values are read on request or by polling with every.
  • The driver has no discovery, because mDNS discovery belongs to the hub.
  • Each read channel sends its own request, so channels that share an endpoint fetch the endpoint once each.

Hazard default and safe state

The default hazard is medium, because an HTTP API can switch anything. HTTP has no protocol-level emergency behavior, so safe_state_support is file. The hub applies the device file's safe_state through ordinary writes, and that only works while the device responds.

Sources