Surfaces ↓
System
client.system Daemon -> webapp system events and replies. Version replies to
VersionRequest with the daemon's BridgeThingMeta. DiagnosticsReply,
LogsTailReply, LogsSubscribeReply, and DeviceNickname are replies
to their matching requests. LogEntry streams matching lines to a live
LogsSubscribe subscription. OtaProgress / OtaError report OTA
orchestrator state. DeviceNicknameChanged broadcasts whenever the
nickname mutates, including from another surface.
Requests
you ask, the daemon answers; await the tagged result and check .ok
versionRequest(): Promise<TypedRequestResult<BridgeThingMeta, never>>
push form: onVersion (subscribe instead of awaiting)
Marker request: webapp asks the bridge for its BridgeThingMeta.
Pairs with BridgeToClientSystemMsg::Version.
const res = await client.system.versionRequest();
if (res.ok) {
console.log(res.response.bridgethingVersion);
} type Result =
| { ok: true; response: BridgeThingMeta }
| { ok: false; kind: 'domain'; error: never }
| { ok: false; kind: 'protocol'; error: WireError }; diagnosticsGet(): Promise<TypedRequestResult<DiagnosticsReply, never>>
push form: onDiagnosticsReply (subscribe instead of awaiting)
Marker request: webapp asks for a one-shot Diagnostics snapshot
(disk/memory usage, uptime, SoC temp, load average, versions).
Pairs with BridgeToClientSystemMsg::DiagnosticsReply.
const res = await client.system.diagnosticsGet();
if (res.ok) {
console.log(res.response.diagnostics);
} type Result =
| { ok: true; response: DiagnosticsReply }
| { ok: false; kind: 'domain'; error: never }
| { ok: false; kind: 'protocol'; error: WireError }; logsTail(LogsTail): Promise<TypedRequestResult<LogsTailReply, never>>
push form: onLogsTailReply (subscribe instead of awaiting)
Pull a one-shot batch of recent daemon log entries over the gateway.
Mirrors the client LogsTail; both feed the same LogTap ring.
const res = await client.system.logsTail({ source: 'daemon', levels: 'trace', maxLines: 0 });
if (res.ok) {
console.log(res.response.entries);
} type Result =
| { ok: true; response: LogsTailReply }
| { ok: false; kind: 'domain'; error: never }
| { ok: false; kind: 'protocol'; error: WireError }; logsSubscribe(LogsSubscribe): Promise<TypedRequestResult<LogsSubscribeReply, never>>
push form: onLogsSubscribeReply (subscribe instead of awaiting)
Open a streaming daemon-log subscription over the gateway. The daemon
returns an opaque token; the companion releases it via LogsUnsubscribe.
Scoped to the gateway peer - auto-released when the peer disconnects.
const res = await client.system.logsSubscribe({ source: 'daemon', levels: 'trace' });
if (res.ok) {
console.log(res.response.token);
} type Result =
| { ok: true; response: LogsSubscribeReply }
| { ok: false; kind: 'domain'; error: never }
| { ok: false; kind: 'protocol'; error: WireError }; deviceGetNickname(): Promise<TypedRequestResult<DeviceNicknameReply, never>>
push form: onDeviceNickname (subscribe instead of awaiting)
Gateway-side read of the device nickname. Returns the current value
(or None when unset). Daemon also broadcasts DeviceNicknameChanged
to gateway peers on mutation so the companion stays in sync without
polling.
const res = await client.system.deviceGetNickname();
if (res.ok) {
console.log(res.response.nickname);
} type Result =
| { ok: true; response: DeviceNicknameReply }
| { ok: false; kind: 'domain'; error: never }
| { ok: false; kind: 'protocol'; error: WireError }; Commands
fire-and-forget; the promise resolves once the daemon has taken the message
logsUnsubscribe(LogsUnsubscribe): Promise<void>
await client.system.logsUnsubscribe({ token: '...' }); reboot(): Promise<void>
await client.system.reboot(); powerOff(): Promise<void>
await client.system.powerOff(); factoryReset(): Promise<void>
await client.system.factoryReset(); Events
the daemon pushes these unprompted; subscribing returns an unsubscribe function
onLogEntry(handler: (LogEntry) => void): () => void
const off = client.system.onLogEntry((entry) => {
console.log(entry.tsUnixS);
});
// call off() to unsubscribe onOtaProgress(handler: (OtaProgress) => void): () => void
const off = client.system.onOtaProgress((otaProgress) => {
console.log(otaProgress.phase);
});
// call off() to unsubscribe onOtaError(handler: (OtaError) => void): () => void
const off = client.system.onOtaError((otaError) => {
console.log(otaError.code);
});
// call off() to unsubscribe onDeviceNicknameChanged(handler: (DeviceNicknameReply) => void): () => void
event broadcast when the nickname changes, including changes made from another surface
const off = client.system.onDeviceNicknameChanged((reply) => {
console.log(reply.nickname);
});
// call off() to unsubscribe Types
shapes referenced above, as the SDK types them
One log record. ts_unix_s is unix-epoch seconds. target is the
tracing target / unit name; message is the rendered single-line
body. Pre-filtered at subscription time so wire-bloating trace
events don't reach webapps.
type LogEntry = {
tsUnixS: number;
level: LogLevel;
target: string;
message: string;
}; Per-phase progress tick. percent is 0-100 within the current
phase, not the overall flow. eta_ms is best-effort remaining time
for the phase when the orchestrator can compute it.
type OtaProgress = {
phase: OtaPhase;
percent: number;
etaMs?: number;
}; type OtaError = {
code: OtaErrorCode;
msg: string;
}; Current device nickname. Reply to DeviceGetNickname and
DeviceSetNickname, plus the event payload for
DeviceNicknameChanged broadcasts.
type DeviceNicknameReply = {
nickname?: string;
}; Bridge-side identity announce. Daemon sends one of these to every gateway on connect so the companion knows what daemon it's talking to and can opt out of unsupported surfaces.
type BridgeThingMeta = {
bridgethingVersion: string;
libbridgethingVersion: string;
appName: string;
nickname?: string; // User-set display name for this device. None when the user hasn't set one yet; consumers fall back to `model_name` / `serial_number`. Set via the gateway-side `system.device.setNickname` surface.
appVersion: string; // Daemon semver (no leading `v`), e.g. `0.8.4`. Compared directly to the manifest's daemon-component version for OTA hot-swap decisions.
osName: string;
osVersion: string;
osDescription: string;
btMac: string;
serialNumber: string;
fccId: string;
icId: string;
modelName: string;
channel: string; // OTA channel the running image was cut on, e.g. `stable` or `dev`. The companion's poll loop only auto-pushes when its configured channel matches; a mismatch surfaces a "channel switch needs full flash" event rather than swapping channels in-band.
imageVariant: string; // Image variant the running image was cut as, e.g. `prod` or `dev`. Maps to the yocto image recipe name `bridgething-<variant>-image`, which is what the companion uses to construct the OTA artifact URL `images/<channel>/<image_version>/bridgething-<variant>-image.{swu,zck}`.
imageVersion: string; // Canonical image version (CalVer, e.g. `2026.05.0`). What the companion compares to the manifest's image-component version.
imageBuildId: string;
imageBuildDate: string;
imageDistro: string;
imageMachine: string;
discord: string;
credits: string;
}; Reply to DiagnosticsGet.
type DiagnosticsReply = {
diagnostics: Diagnostics;
}; Pull a one-shot batch of recent daemon log entries over the gateway.
Mirrors the client LogsTail; both feed the same LogTap ring.
type LogsTail = {
source: LogSource;
levels: LogLevel[];
filter?: string;
maxLines: number;
}; One-shot reply to a gateway LogsTail.
type LogsTailReply = {
entries: LogEntry[];
}; Open a streaming daemon-log subscription over the gateway. The daemon
returns an opaque token; the companion releases it via LogsUnsubscribe.
Scoped to the gateway peer - auto-released when the peer disconnects.
type LogsSubscribe = {
source: LogSource;
levels: LogLevel[];
filter?: string;
}; Reply to a gateway LogsSubscribe: the opaque token to pass back to
LogsUnsubscribe.
type LogsSubscribeReply = {
token: string;
}; type LogsUnsubscribe = {
token: string;
}; type LogLevel = 'trace' | 'debug' | 'info' | 'warn' | 'error'; Stage of the OTA orchestrator. The phase set is shared between
kinds, with non-image kinds emitting a subset.
Image: Streaming -> Verifying -> Writing (libswupdate to slot)
-> Confirming (try-counter reset) -> Reboot.
Daemon and BuiltinWebapp: Streaming -> Verifying -> Writing,
where Writing/100 means the piece is validated and staged on the
bandaid (not yet live). The atomic rotate and the single systemctl restart happen later, on OtaActivate, which emits the terminal
Reboot for the whole batch. Confirming is image-only.
InstalledWebapp: Streaming -> Verifying -> Writing/0 while the
bundle installs into the writable registry. There is no Writing/100,
no Confirming, and no Reboot; the terminal signal is the
WebappInstalled event (or an OtaError).
type OtaPhase =
| 'streaming'
| 'verifying'
| 'writing'
| 'confirming'
| 'reboot'; Terminal error from the OTA orchestrator. After an OtaError the
orchestrator is back to idle and a fresh OtaBegin may be sent.
type OtaErrorCode =
| 'unknownUpdate' // Companion sent fragments for an `update_id` that was never begun (or was abandoned mid-stream).
| 'offsetMismatch' // A fragment's offset did not match the daemon's `received`.
| 'hashMismatch' // Streamed total's sha256 did not match `OtaBegin.transfer.sha256`.
| 'sizeMismatch' // Streamed total's byte length did not match `OtaBegin.transfer.total_size`.
| 'cancelled' // `CancelUpdate` arrived during a cancelable phase.
| 'writeFailed' // libswupdate rejected the .swu (parse / handler / I/O failure).
| 'confirmFailed' // Slot-flip / try-counter reset failed after a successful write.
| 'internal' // Anything else (transfer-cache I/O, internal channel close, etc.).; Daemon health snapshot. load_avg is the unix 1/5/15-minute load.
soc_temp_c may be None on builds where the SoC thermal probe is
not exposed by the kernel.
type Diagnostics = {
diskUsedBytes: number;
diskFreeBytes: number;
memUsedBytes: number;
memAvailBytes: number;
uptimeS: number;
socTempC?: number;
loadAvg: [f32 ; 3];
daemonVersion: string;
kernelVersion: string;
bootId: string;
}; What stream of log records a subscription pulls from. Daemon is the
bridgething tracing subscriber; System is the journald view; All
merges both in arrival order.
type LogSource = 'daemon' | 'system' | 'all';