consortium-hmi
Backend-neutral command bridge for Consortium HMI runtimes.
A UI needs to call into the application: arm a motor, read a sensor, change a setpoint. This crate is the registry and dispatch for those calls — and nothing else. It contains no Cog, no GLib, no WebKit, no PocketJS, and no Tokio runtime state, so product services can be written and tested without a browser or native UI runtime present.
Rendering backends are adapter crates:
| Adapter | Runtime |
|---|---|
consortium-hmi-webkit |
Cog / WPE WebKit (Linux) |
consortium-hmi-pocket |
PocketJS in QuickJS (native) |
Input events travel the other direction and live in
consortium-hmi-input.
Usage
use Bridge;
let mut bridge = new;
// A synchronous handler.
bridge.handle;
// An async one — the call site is identical from the adapter's point of view.
bridge.handle_async;
// The adapter feeds envelopes in and polls the returned future.
let dispatch = bridge.dispatch?;
let json = dispatch.response.await;
send_back_to_guest;
Why it returns a future instead of a response
Bridge::dispatch does not poll. It cannot: a Cog/WebKit adapter runs on a GLib main
loop, a PocketJS adapter delivers responses at frame boundaries, and neither owns a Tokio
runtime this crate could assume.
So the bridge produces a ResponseFuture and the adapter decides where to poll it and
how to get the JSON back into its guest runtime. That single decision is what keeps this
crate dependency-free and lets one set of product services serve both backends unchanged.
Design notes
- Sync and async handlers are indistinguishable downstream. Both are erased into one
internal handler type and both produce a
ResponseFuture(the sync arm wrapped in an immediately-ready future). A handler can become async without the adapter changing. - Correlation ids belong to the guest.
Dispatch::idis echoed from the invocation envelope, not generated here — the guest runtime owns request/response matching, because it is the side with concurrent outstanding calls. - Payloads stay JSON strings end to end. The bridge never deserializes a payload; a
handler does, with whatever type it wants. That keeps
serdeschema decisions out of the transport. - A malformed envelope still parses.
idandcmdare both#[serde(default)], so a partial envelope becomes a dispatch that fails on lookup rather than failing to parse — which keeps the failure reportable back to the guest with an id where one was supplied.
Envelope format
License
Apache-2.0. See LICENSE.