@umka-management/protocol
v2.26.3
Published
Umka kiosk wire/REST protocol — zod schemas, topic helpers, parse helpers. Embodies the STANDARD.
Maintainers
Readme
@umka-management/protocol
The Umka kiosk wire/REST protocol as one installable package: zod schemas
(with inferred TS types), MQTT topic builders/parsers, and validating parse
helpers. Embodies the STANDARD — currently
v2.26.0 (see UMKA_STANDARD_VERSION).
Where this package and the STANDARD disagree, the STANDARD governs — this is an implementation of the spec, not a second source of truth.
Install
pnpm add @umka-management/protocolUse
import { topics, parseWirePayload, KioskStatus } from '@umka-management/protocol'
client.subscribe(topics.status('main-hall'))
client.on('message', (t, buf) => {
const r = parseWirePayload('status', buf)
if (!r.ok) return console.warn('bad status', r.error)
// r.data: KioskStatus
})Entry points: . (everything), ./schemas, ./topics, ./parse.
dist/ and schema/ are committed so installs need no build step.
Command correlation (commandId) — since 1.6.0
Every command travels as a uniform envelope so the CMS can correlate what
actually executed with what it published. The inner value is the per-command
payload (the command kind is implicit in the topic); the envelope adds a
CMS-generated commandId (UUID) and issuedAt.
CommandEnvelope<T> = { commandId: string /*uuid*/, issuedAt: string /*iso*/, value: T }Build it with commandEnvelope(innerSchema); parseWirePayload returns
{ commandId, issuedAt, value } for every command kind (commandsApp,
commandsPlayback, commandsVolume, commandsLocale, commandsLoop,
commandsPower, commandsUpdate, iotCommand, wol). Bare payloads are no
longer accepted (greenfield).
Each executor echoes a CommandAck on its ack topic; the CMS subscribes,
updates ActionLog.deviceResult, and falls back to timeout if no ack arrives.
CommandAck = { commandId, source: 'sentinel'|'player'|'modbus-bridge'|'wol-relay',
result: 'received'|'applied'|'failed'|'unsupported', detail?, timestamp }Ack topics: topics.ack(slug) → umka/kiosks/{slug}/ack (Sentinel, Player,
wol-relay) and topics.iotAck(museumId, deviceId) →
museum/{museumId}/iot/{deviceId}/ack (modbus-bridge).
sequenceDiagram
actor Op as Operator
participant CMS
participant DB as ActionLogs (Postgres)
participant Broker as MQTT broker
participant Exec as Executor<br/>(Sentinel / Player / modbus-bridge / wol-relay)
Op->>CMS: POST command (power/app/volume/iot/wol)
Note over CMS: commandId = randomUUID()<br/>issuedAt = now
CMS->>Broker: publish {commandId, issuedAt, value}<br/>to commands/* | iot/+/command | wol
CMS->>DB: ActionLog { commandId, result:published, deviceResult:'pending' }
CMS-->>Op: 200 (published — not yet confirmed)
Broker->>Exec: deliver command envelope
Note over Exec: parse envelope → act on `value`
alt command applied
Exec->>Broker: CommandAck {commandId, source, result:'applied'}<br/>on ack(slug) | iot/+/ack
Broker->>CMS: ack
CMS->>DB: update by commandId → deviceResult:'applied', deviceResultAt
else command failed / unsupported
Exec->>Broker: CommandAck {commandId, result:'failed'|'unsupported', detail}
Broker->>CMS: ack
CMS->>DB: update → deviceResult:'failed'|'unsupported'
opt source = modbus-bridge AND result = failed
CMS->>DB: recordSystemEvent('iot_command_failed')
end
else kiosk offline / executor never acks
Note over CMS: monitorTick (10s) sweep:<br/>pending rows older than ACK_TIMEOUT (30s)
CMS->>DB: update → deviceResult:'timeout'
endPower-off caveat: the Sentinel publishes its CommandAck before the OS
shutdown call (the process is about to exit), so "applied" means "shutdown
initiated", not "machine confirmed down".
License
MIT — see LICENSE. Developed by Multimedia Solutions Lab Ltd.
