Recipe: Graph + Request/Reply Content
Goal: a force-directed graph (Pro) whose nodes, when tapped, either fire an
action or fetch content from your server and open it in a markdown sheet. This
is the canonical use of mode: "request" and route_msg. Concepts:
Data Flow — sync & actions, The MeshSocket Protocol.
Feeding the graph (server → device)
The graph reads its data as a nodes/edges object via sync:
{ "type":"graph", "id":"topology", "position":[0,0], "span":[4,4], "label":"Network",
"sync":[{ "method":"meshsocket","type":"listen","event":"broadcast",
"filter":{"msg_type":"graph"}, "valuePath":"data" }],
"graphConfig": {
"nodeAction": { "method":"meshsocket","mode":"request","event":"route_msg",
"payload": { "target_name":"Graph Server","type":"get_doc",
"payload": { "id":"{{value}}" } } }
} }
Server broadcasts the graph:
await socket.emit("broadcast_request", { "msg_type":"graph", "data": {
"nodes":[{"id":"hub","label":"Hub","group":"core"},
{"id":"cam1","label":"Camera 1","group":"device"}],
"edges":[{"from":"hub","to":"cam1"}] }})
Node taps: action vs content
A node tap resolves in this order:
- Per-node
onTap(set on the node data) — overrides the graph default. graphConfig.nodeAction— the default for every node.
{{value}} in either is the tapped node's id. Two onTap shapes:
- Action — fire a MeshSocket action (no panel):
json
"onTap": { "type":"action","method":"meshsocket","mode":"request","event":"route_msg",
"payload": { "target_name":"Graph Server","type":"select","payload": { "id":"{{value}}" } } }
- Content — request markdown and open it in a sheet:
json
"onTap": { "type":"content","event":"get_doc","payload": { "id":"{{value}}" } }
Answering the request (server)
A mode: "request" action makes the device await a reply. Register a handler
for the request type and return the response; the library turns your return
value into the reply (raw clients echo reply_to — see Building a Server in Any Language (Raw Protocol)).
@socket.on("get_doc")
async def on_get_doc(payload):
node_id = (payload or {}).get("id", "")
return { "title": node_id.title(),
"content": f"# {node_id}\n\nStatus: online\n\n- uptime: 4d\n- load: 0.3" }
The device accepts either:
- an object
{ "title", "content" }→ titled markdown sheet, or - a bare string → markdown sheet titled with the node's label.
Why request/reply here
Telemetry is broadcast (fan-out, no reply). But "give me the details for this
node" is a question with one answer for one caller — that's route_msg +
mode: "request". Use request/reply whenever the device needs a value back
(content panels, validations, one-shot queries); use broadcast for streaming
state.
Next: generate UI from data — Recipe: Server-Driven (Dynamic) UI.