add the node manifest, dataflow, CI and docs
dora-node.yml is the typed contract a consumer wires against, and the only one they get: no source package is shared between node repositories. The jog, request and ui ports stay untyped, because dora treats a port carrying request_id as pattern-correlated and several Arrow schemas legitimately flow over each — one per service, one per console message. dataflow.yml runs the whole operator stack from checkouts: this node, the WebSocket bridge and the arm. web/ holds a placeholder page, not the real console. It exists so the dataflow has something to serve and so the node can be checked end to end before the Control section is ported across from the monolithic repo. It reads telemetry and can take, release and stop; nothing else.
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>teleop — placeholder console</title>
|
||||
<style>
|
||||
:root { color-scheme: dark; }
|
||||
body { margin: 0; padding: 24px; background: #15151c; color: #e6e6ef;
|
||||
font: 14px/1.5 ui-monospace, SFMono-Regular, Menlo, monospace; }
|
||||
h1 { font-size: 16px; margin: 0 0 4px; }
|
||||
p.note { color: #8a8a9e; margin: 0 0 20px; max-width: 60ch; }
|
||||
.card { background: #1d1d26; border: 1px solid #2c2c3a; border-radius: 8px;
|
||||
padding: 16px; margin-bottom: 16px; max-width: 60ch; }
|
||||
.row { display: flex; justify-content: space-between; gap: 16px; padding: 3px 0; }
|
||||
.row span:last-child { color: #7fd1e8; }
|
||||
button { background: #2c2c3a; color: #e6e6ef; border: 1px solid #3c3c4e;
|
||||
border-radius: 6px; padding: 8px 14px; font: inherit; cursor: pointer; }
|
||||
button:hover { background: #363648; }
|
||||
button.stop { background: #5a1f27; border-color: #7d2b35; }
|
||||
#link { color: #8a8a9e; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>teleop — placeholder console</h1>
|
||||
<p class="note">
|
||||
Not the real console. This page exists so the dataflow has something to serve and so the
|
||||
node can be checked end to end before the Control section is ported across from the
|
||||
monolithic repo. It reads telemetry and can take, release and stop — nothing else.
|
||||
</p>
|
||||
|
||||
<div class="card" id="state"></div>
|
||||
|
||||
<div class="card">
|
||||
<button onclick="send({type:'control', action:'take', preempt:true})">Take control</button>
|
||||
<button onclick="send({type:'control', action:'release'})">Release</button>
|
||||
<button class="stop" onclick="send({type:'estop'})">E-STOP</button>
|
||||
<button onclick="send({type:'reset'})">Reset</button>
|
||||
</div>
|
||||
|
||||
<div id="link"></div>
|
||||
|
||||
<script>
|
||||
"use strict";
|
||||
const KEYS = ["arm_state", "owner", "connected", "ready", "estop", "vacuum", "freedrive"];
|
||||
let socket = null;
|
||||
|
||||
function send(payload) {
|
||||
if (socket && socket.readyState === WebSocket.OPEN) {
|
||||
socket.send(JSON.stringify({ type: "data", payload }));
|
||||
}
|
||||
}
|
||||
|
||||
function render(telemetry) {
|
||||
const rows = KEYS.map((key) =>
|
||||
`<div class="row"><span>${key}</span><span>${telemetry[key]}</span></div>`);
|
||||
const pose = telemetry.position;
|
||||
if (pose) {
|
||||
const shown = ["x", "y", "z"].map((axis) => pose[axis].toFixed(1)).join(", ");
|
||||
rows.push(`<div class="row"><span>position</span><span>${shown} mm</span></div>`);
|
||||
}
|
||||
document.getElementById("state").innerHTML = rows.join("");
|
||||
}
|
||||
|
||||
function connect() {
|
||||
// The socket is the bridge's port, not this page's — the page is served over
|
||||
// HTTP on a different one.
|
||||
const url = `ws://${location.hostname}:9000`;
|
||||
document.getElementById("link").textContent = `socket: ${url}`;
|
||||
socket = new WebSocket(url);
|
||||
socket.onmessage = (event) => {
|
||||
const message = JSON.parse(event.data);
|
||||
if (message.type !== "data" || !message.payload) return;
|
||||
if (message.payload.type === "telemetry") render(message.payload);
|
||||
};
|
||||
socket.onclose = () => {
|
||||
document.getElementById("link").textContent = `socket: ${url} — closed, retrying`;
|
||||
setTimeout(connect, 2000);
|
||||
};
|
||||
}
|
||||
|
||||
connect();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user