Demo-Ansicht (Capability Lab)¶
Damit sich alle Capability-Renderer der App ohne ein echtes, verbundenes Gerät ansehen lassen, gibt es ein lokales Demo-Thing: das Capability Lab. Es ist kein echtes BLE- oder MQTT-Gerät, sondern eine statisch definierte Thing-Model-Datei, die von der App genauso behandelt wird wie ein reguläres, registriertes Thing.
graph LR
JSON["capability-lab.json"] --> Demo["demoDevice.ts"]
Demo --> Thing["DEMO_THING<br/>(Thing)"]
Demo --> Record["DEMO_DEVICE_RECORD<br/>(DeviceRecord)"]
Thing --> UI["Capability-Renderer<br/>in der App"]
Record --> UI
Erkennung des Demo-Geräts¶
Jede UUID, die mit demo- beginnt (oder exakt der festen Demo-ID entspricht), wird von der App als Demo-Gerät erkannt und über DEMO_THING / DEMO_DEVICE_RECORD bedient statt über REST/BLE/MQTT.
export const DEMO_DEVICE_ID = 'demo-capability-lab-01';
export function isDemoId(uuid: string): boolean {
return uuid === DEMO_DEVICE_ID || uuid.startsWith('demo-');
}
Thing Model des Demo-Geräts¶
Das Capability Lab deckt alle unterstützten Capability-Typen und Display-Varianten in einem einzigen Thing ab: Toggle, Slider, Graph, Gauge, Progress, Indicator, Buttons (Enum), Impulse-Button, Dual-Joystick-Controller, Text-Input, Color-Picker, Time-Picker und Message.
{
"success": true,
"data": {
"uuid": "demo-capability-lab-01",
"device_id": "demo",
"mac": "",
"source": "demo",
"connection_status": "online",
"last_seen": "2026-06-01T12:00:00.000Z",
"thing_model": {
"device_id": "demo-capability-lab-01",
"mac": "",
"metadata": {
"label": "Capability Demo",
"category": "Demo",
"description": "Local demo device that shows every supported capability renderer"
},
"capabilities": [
{ "id": "power", "type": "ON_OFF", "label": "Power Toggle", "display": "toggle", "direction": "both" },
{ "id": "brightness", "type": "NUMBER", "label": "Brightness", "direction": "both", "display": "slider", "min": 0, "max": 255, "step": 1, "unit": "PWM" },
{ "id": "temperature", "type": "NUMBER", "label": "Temperature", "direction": "readable", "display": "graph", "min": -20, "max": 80, "step": 0.1, "unit": "C" },
{ "id": "humidity", "type": "NUMBER", "label": "Luftfeuchte", "direction": "readable", "display": "gauge", "min": 0, "max": 100, "step": 1, "unit": "%" },
{ "id": "water_tank", "type": "NUMBER", "label": "Wassertank", "direction": "both", "display": "progress", "min": 0, "max": 100, "step": 1, "unit": "%" },
{ "id": "battery", "type": "NUMBER", "label": "Akku", "direction": "readable", "display": "progress", "min": 0, "max": 100, "step": 1, "unit": "%" },
{ "id": "pump_status", "type": "ON_OFF", "label": "Pumpe", "direction": "readable", "display": "indicator" },
{
"id": "mode",
"type": "ENUM",
"label": "Operating Mode",
"direction": "both",
"display": "buttons",
"values": [
{ "id": "auto", "label": "Auto" },
{ "id": "boost", "label": "Boost" },
{ "id": "eco", "label": "Eco" }
]
},
{ "id": "calibrate", "type": "IMPULSE", "label": "Calibration Pulse", "direction": "writable", "display": "button" },
{ "id": "controller", "type": "CONTROLLER", "label": "Dual Joystick Controller", "direction": "both", "display": "dual_joystick" },
{ "id": "device_note", "type": "TEXT", "label": "Device Note", "direction": "both", "display": "input" },
{ "id": "led_color", "type": "TEXT", "label": "LED Farbe", "direction": "both", "display": "color-picker" },
{ "id": "wake_time", "type": "TEXT", "label": "Weckzeit", "direction": "both", "display": "time-picker" },
{ "id": "status_message", "type": "MESSAGE", "label": "Status Message", "direction": "readable", "display": "text" }
],
"state": {
"power": false,
"brightness": 64,
"temperature": 22.4,
"humidity": 58,
"water_tank": 72,
"battery": 41,
"pump_status": true,
"mode": "auto",
"controller": { "joystick1": { "x": 0, "y": 0 }, "joystick2": { "x": 0, "y": 0 } },
"device_note": "Demo ready",
"led_color": "#33aaff",
"wake_time": "07:30",
"status_message": "All capability renderers are visible."
}
}
}
}
Aufbereitung als Thing / DeviceRecord¶
Die App liest die JSON-Datei einmalig ein und wandelt sie in ein Thing- sowie ein DeviceRecord-Objekt um. Beide teilen dieselben Capabilities und Metadaten, damit sich das Demo-Gerät in jeder Ansicht der App identisch verhält wie ein echtes Gerät. Ein fixes last_seen-Datum sorgt dafür, dass die Demo-Ansicht immer denselben, reproduzierbaren Stand zeigt.
import type { Capability, Thing, ThingState, StateValue } from '../types';
import type { DeviceRecord } from '../RestAPI';
import demoJson from '../../assets/demo/capability-lab.json';
export const DEMO_DEVICE_ID = 'demo-capability-lab-01';
export function isDemoId(uuid: string): boolean {
return uuid === DEMO_DEVICE_ID || uuid.startsWith('demo-');
}
const raw = demoJson.data;
const FIXED_DATE = '2026-06-01T12:00:00.000Z';
const thingState: ThingState = Object.fromEntries(
Object.entries(raw.thing_model.state).map(([key, value]) => [
key,
{ value: value as StateValue, last_update: FIXED_DATE },
]),
);
export const DEMO_THING: Thing = {
uuid: raw.uuid,
thing_id: raw.uuid,
device_id: raw.uuid,
mac: raw.mac,
metadata: { label: raw.thing_model.metadata.label, category: raw.thing_model.metadata.category },
capabilities: raw.thing_model.capabilities as unknown as Capability[],
state: thingState,
connection: { status: 'ONLINE', last_seen: FIXED_DATE },
};
export const DEMO_DEVICE_RECORD: DeviceRecord = {
uuid: raw.uuid,
device_id: raw.device_id,
mac: raw.mac,
last_seen: FIXED_DATE,
connection_status: 'ONLINE',
thing_model: {
metadata: { label: raw.thing_model.metadata.label, category: raw.thing_model.metadata.category },
capabilities: raw.thing_model.capabilities as unknown as Capability[],
},
};
Nutzen für Entwicklung und Vorführung¶
Da das Capability Lab jede unterstützte Display-Variante in einem einzigen Thing kombiniert, dient es als:
- Schnelltest beim Entwickeln neuer Capability-Renderer, ohne dass Hardware angeschlossen sein muss
- Referenz dafür, welche
type/display-Kombinationen im Thing Model gültig sind - Vorführ-Gerät für Demos, da es ohne WLAN, MQTT-Broker oder ESP32 sofort funktioniert