Build for CAR-TER
CAR-TER turns an iPhone or iPad into any remote or dashboard you can describe in JSON. A layout is a JSON document — tabs, a grid, and controls — that the app renders as a swift UI. Those controls talk to your server over a persistent WebSocket mesh called MeshSocket.
Your job, as an integrator, is to write the program on the other end of that socket: the thing that feeds the displays and handles the inputs.
The mental model (five minutes)
There are three actors. See Architecture for the full picture.
- The app loads a layout and connects to a relay, joining a channel with a role.
- The relay is a small message router. It fans out broadcasts to everyone on a channel and routes point-to-point requests between members. You can run the open-source relay yourself, or use the hosted Connect+ gateway — see Connection & Pairing.
- Your server joins the same channel and does the real work: it broadcasts data the controls listen for, and receives the actions controls emit.
Two data directions, both covered in Data Flow — sync & actions:
- Server → device (
sync): a control subscribes to an event; your server broadcasts a payload; the control extracts its value via avaluePath. - Device → server (
action): a control emits an event when tapped/changed; your server handles it (and may reply).
What you'll build
A minimal integration is ~30 lines: connect, identify, and either broadcast a number on a timer or print the actions that arrive. From there you compose: telemetry dashboards, interactive controllers, chat, force-directed graphs, and dynamically-generated tabs.
- Copy-paste starting points: Recipe: Telemetry Dashboard, Recipe: Interactive Controls.
- Language guides: Building a Server in Python (uses the MeshSocket client library) or Building a Server in Any Language (Raw Protocol) (implement the frames yourself in any language).
A complete tiny example
A layout with one gauge that listens, and one button that sends:
{
"name": "Hello CAR-TER",
"version": 1,
"connection": {
"url": "ws://192.168.1.50:8765",
"identity": { "name": "Dashboard", "channel": "demo", "role": "viewer" }
},
"tabs": [{
"title": "Main", "icon": "gauge.with.dots.needle.67percent",
"grid": { "columns": 2, "rows": 2 },
"children": [
{ "type": "gauge", "id": "cpu", "position": [0,0], "label": "CPU",
"min": 0, "max": 100,
"sync": [{ "method": "meshsocket", "type": "listen",
"event": "broadcast", "filter": { "msg_type": "metrics" },
"valuePath": "cpu" }] },
{ "type": "button", "id": "refresh", "position": [0,1], "label": "Refresh",
"action": { "method": "meshsocket", "mode": "broadcast",
"event": "broadcast_request",
"payload": { "msg_type": "command", "command": "refresh" } } }
]
}]
}
Your server, on the demo channel, broadcasts { "msg_type": "metrics", "cpu": 73 }
to move the gauge, and listens for { "msg_type": "command", "command": "refresh" }
when the button is tapped. The exact frames are spelled out in
The MeshSocket Protocol and Data Flow — sync & actions; full schema in Layout Schema.
Next: Architecture.