G92 senden
This commit is contained in:
@@ -85,9 +85,10 @@ X-Position aus Marker-Positionen schätzen
|
||||
│ → state_Arm2.json
|
||||
▼
|
||||
4b_revolute_angle.py --link Hand --from-state state_Arm2.json
|
||||
│ → state_Hand.json ← accumulated_state enthält x,y,z,a,b,c,e
|
||||
│ → state_Hand.json ← accumulated_state enthält x,y,z,a,b
|
||||
│ (c/Palm + e/Greifer werden nicht bestimmt → für G92 als 0 ergänzt)
|
||||
▼
|
||||
POST ROBOT_URL/api/state
|
||||
G92 über Driver-WebSocket (DRIVER_WS_URL) — setzt Motorposition ohne Bewegung
|
||||
```
|
||||
|
||||
**Schritte 1–3b** sind dieselbe Board-Pipeline wie in der Kalibrierung.
|
||||
@@ -107,7 +108,7 @@ X-Slider-Position über `--x-mm`.
|
||||
| X-Schätzung | `server/homingOrchestrator.js` → `estimateXFromMarkers()` | Pro Arm-Marker `beobachtetes_x − Modell_x(slider=0)`, gemittelt — rechnet den kinematischen Gelenk-Offset (z.B. Arm1.origin.x=110) heraus. Nur x-zuverlässige Ketten (x-Rotation: Arm1/Ellbow). Fallback: roher Mittelwert |
|
||||
| Homing-Orchestrator | `server/homingOrchestrator.js` → `runHoming()` | Kompletter Ablauf als SSE-Stream |
|
||||
| Backend-Route | `POST /api/homing/run` | SSE-Stream, startet `runHoming()` |
|
||||
| State senden | `POST /api/homing/send-state` | Weiterleitung an `ROBOT_URL/api/state` |
|
||||
| State senden | `POST /api/homing/send-state` | Baut `G92` (fehlende c/e → 0) und sendet es als Plain-Text-G-Code über den Driver-WebSocket (`DRIVER_WS_URL`, `server/driverClient.js`). Der Driver verarbeitet G92 intern als M92 = Motorposition setzen ohne Bewegung. Kein HTTP `/api/state` (gibt es am Driver nicht) |
|
||||
| Run-Daten | `GET /api/homing/run-data?run=ts` | Debug-Bilder (base64) + finalState |
|
||||
| Frontend | `public/index.html` + `public/client.js` | Homing-Buttons, Fortschrittsbalken, Tree View; schreibt Teil-Pose als `G92`-GCode ins Eingabefeld |
|
||||
| Board-Viewer (Homing) | `public/boardViewer.html?mode=homing` | Skelett + Arm-Marker per FK (Three.js): Marker-Quadrat spin-korrekt rotiert + Orientierungszeiger zu Ecke 0 (Modell-Seite); gemessene Marker als Kugeln + Fehlerlinien; progressiver Update je erkanntem Gelenk |
|
||||
|
||||
@@ -11,6 +11,10 @@ services:
|
||||
- PYTHON_BIN=python3
|
||||
- WEBCAM_URL=http://host.docker.internal:8444
|
||||
- BODYTRACKER_URL=http://host.docker.internal:8446
|
||||
# Driver-WebSocket (Plain-Text-G-Code, self-signed). Homing sendet G92
|
||||
# hierhin (= Motorposition setzen ohne Bewegung). Host-Port lt.
|
||||
# appRobotDriver/doc/API.md: 2096.
|
||||
- DRIVER_WS_URL=wss://host.docker.internal:2096
|
||||
extra_hosts:
|
||||
# Macht host.docker.internal auf Linux verfügbar (Standard auf macOS/Windows)
|
||||
- "host.docker.internal:host-gateway"
|
||||
|
||||
@@ -16,7 +16,8 @@
|
||||
"dependencies": {
|
||||
"dotenv": "^16.4.5",
|
||||
"express": "^4.19.2",
|
||||
"multer": "^2.2.0"
|
||||
"multer": "^2.2.0",
|
||||
"ws": "^8.20.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"jest": "^29.7.0",
|
||||
|
||||
@@ -346,11 +346,21 @@ function setHomingProgress(step, total, text) {
|
||||
if (txt) txt.textContent = text || `Schritt ${step} / ${total}`;
|
||||
}
|
||||
|
||||
function writePartialGCode(state) {
|
||||
// Schreibt das G92-Kommando ins Eingabefeld.
|
||||
// - progressiv (full=false): nur die bereits bestimmten Achsen, je Gelenk-Update
|
||||
// - final (full=true): alle 7 Achsen; fehlende c (Palm) / e (Greifer)
|
||||
// werden als 0 ergänzt — identisch zu dem, was
|
||||
// "An Roboter senden" via server/buildG92.cjs sendet.
|
||||
function writePartialGCode(state, { full = false } = {}) {
|
||||
const axisMap = { x: 'X', y: 'Y', z: 'Z', a: 'A', b: 'B', c: 'C', e: 'E' };
|
||||
const parts = [];
|
||||
for (const [key, axis] of Object.entries(axisMap)) {
|
||||
if (state[key] != null) parts.push(`${axis}${Number(state[key]).toFixed(2)}`);
|
||||
const num = Number(state[key]);
|
||||
if (state[key] != null && Number.isFinite(num)) {
|
||||
parts.push(`${axis}${num.toFixed(2)}`);
|
||||
} else if (full) {
|
||||
parts.push(`${axis}0.00`);
|
||||
}
|
||||
}
|
||||
if (!parts.length) return;
|
||||
const el = document.getElementById('gcodePayload');
|
||||
@@ -549,6 +559,9 @@ async function runHoming() {
|
||||
if (evt.state) {
|
||||
_homingState = evt.state;
|
||||
showHomingResult(evt.state);
|
||||
// Vollständiges G92 (inkl. C0/E0) ins Feld — exakt das, was
|
||||
// "An Roboter senden" schickt.
|
||||
writePartialGCode(evt.state, { full: true });
|
||||
if (btnSend) {
|
||||
btnSend.disabled = false;
|
||||
btnSend.style.opacity = '';
|
||||
@@ -593,7 +606,8 @@ async function sendHomingToRobot() {
|
||||
});
|
||||
const data = await res.json();
|
||||
if (res.ok) {
|
||||
appendLog('✅ State erfolgreich an Roboter gesendet');
|
||||
appendLog(`✅ An Roboter gesendet: ${data.gcode ?? ''}`);
|
||||
if (data.note) appendLog(`ℹ ${data.note}`);
|
||||
setHomingStatus('✓ Gesendet', 'done');
|
||||
} else {
|
||||
appendLog(`❌ Fehler beim Senden: ${data.error ?? JSON.stringify(data)}`);
|
||||
@@ -605,6 +619,23 @@ async function sendHomingToRobot() {
|
||||
}
|
||||
}
|
||||
|
||||
// Transport für die G-Code-/Befehl-Buttons (data-cmd). Schickt eine rohe
|
||||
// Zeile über das Backend an den Driver-WebSocket (POST /api/robot/gcode).
|
||||
// Liegt ein Payload-Feld vor (z.B. das G92 aus #gcodePayload), wird dessen
|
||||
// Inhalt gesendet, sonst der cmd-Name selbst. Ersetzt den toten WSS-Altpfad.
|
||||
window.sendCommand = async function (cmd, payload) {
|
||||
const line = (payload && payload.trim()) ? payload.trim() : String(cmd ?? '').trim();
|
||||
if (!line) throw new Error('Leere Befehlszeile');
|
||||
const res = await fetch('/api/robot/gcode', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ line }),
|
||||
});
|
||||
const data = await res.json().catch(() => ({}));
|
||||
if (!res.ok) throw new Error(data.error ?? `HTTP ${res.status}`);
|
||||
return data;
|
||||
};
|
||||
|
||||
async function onCommandClick(btn) {
|
||||
const cmd = btn.dataset.cmd;
|
||||
const payloadSelector = btn.dataset.payload;
|
||||
|
||||
@@ -675,51 +675,6 @@
|
||||
],
|
||||
"spin": 90
|
||||
},
|
||||
{
|
||||
"id": 55,
|
||||
"set": "A0",
|
||||
"position": [
|
||||
282.76,
|
||||
-261.86,
|
||||
-27.58
|
||||
],
|
||||
"normal": [
|
||||
0,
|
||||
0,
|
||||
1
|
||||
],
|
||||
"spin": 90
|
||||
},
|
||||
{
|
||||
"id": 56,
|
||||
"set": "A0",
|
||||
"position": [
|
||||
499.34,
|
||||
168.57,
|
||||
-27.26
|
||||
],
|
||||
"normal": [
|
||||
0,
|
||||
0,
|
||||
1
|
||||
],
|
||||
"spin": 90
|
||||
},
|
||||
{
|
||||
"id": 57,
|
||||
"set": "A0",
|
||||
"position": [
|
||||
601.52,
|
||||
-364.54,
|
||||
-27.11
|
||||
],
|
||||
"normal": [
|
||||
0,
|
||||
0,
|
||||
1
|
||||
],
|
||||
"spin": 90
|
||||
},
|
||||
{
|
||||
"id": 58,
|
||||
"set": "A0",
|
||||
@@ -1005,36 +960,6 @@
|
||||
],
|
||||
"spin": 90
|
||||
},
|
||||
{
|
||||
"id": 77,
|
||||
"set": "A0",
|
||||
"position": [
|
||||
18.94,
|
||||
193.28,
|
||||
-27.98
|
||||
],
|
||||
"normal": [
|
||||
0,
|
||||
0,
|
||||
1
|
||||
],
|
||||
"spin": 90
|
||||
},
|
||||
{
|
||||
"id": 78,
|
||||
"set": "A0",
|
||||
"position": [
|
||||
821.84,
|
||||
-345.7,
|
||||
-26.78
|
||||
],
|
||||
"normal": [
|
||||
0,
|
||||
0,
|
||||
1
|
||||
],
|
||||
"spin": 90
|
||||
},
|
||||
{
|
||||
"id": 79,
|
||||
"set": "A0",
|
||||
@@ -1335,21 +1260,6 @@
|
||||
],
|
||||
"spin": 90
|
||||
},
|
||||
{
|
||||
"id": 99,
|
||||
"set": "A0",
|
||||
"position": [
|
||||
957.97,
|
||||
-323.38,
|
||||
-26.58
|
||||
],
|
||||
"normal": [
|
||||
0,
|
||||
0,
|
||||
1
|
||||
],
|
||||
"spin": 90
|
||||
},
|
||||
{
|
||||
"id": 100,
|
||||
"set": "A0",
|
||||
@@ -1635,7 +1545,11 @@
|
||||
0,
|
||||
0
|
||||
],
|
||||
"origin": [110, 108.3154, 37.4964],
|
||||
"origin": [
|
||||
110,
|
||||
108.3154,
|
||||
37.4964
|
||||
],
|
||||
"rotation": [
|
||||
0,
|
||||
0,
|
||||
@@ -1741,6 +1655,96 @@
|
||||
],
|
||||
"size": 25,
|
||||
"spin": 0
|
||||
},
|
||||
{
|
||||
"id": 55,
|
||||
"set": "A0",
|
||||
"position": [
|
||||
282.76,
|
||||
-261.86,
|
||||
-27.58
|
||||
],
|
||||
"normal": [
|
||||
0,
|
||||
0,
|
||||
1
|
||||
],
|
||||
"spin": 90
|
||||
},
|
||||
{
|
||||
"id": 56,
|
||||
"set": "A0",
|
||||
"position": [
|
||||
499.34,
|
||||
168.57,
|
||||
-27.26
|
||||
],
|
||||
"normal": [
|
||||
0,
|
||||
0,
|
||||
1
|
||||
],
|
||||
"spin": 90
|
||||
},
|
||||
{
|
||||
"id": 57,
|
||||
"set": "A0",
|
||||
"position": [
|
||||
601.52,
|
||||
-364.54,
|
||||
-27.11
|
||||
],
|
||||
"normal": [
|
||||
0,
|
||||
0,
|
||||
1
|
||||
],
|
||||
"spin": 90
|
||||
},
|
||||
{
|
||||
"id": 77,
|
||||
"set": "A0",
|
||||
"position": [
|
||||
18.94,
|
||||
193.28,
|
||||
-27.98
|
||||
],
|
||||
"normal": [
|
||||
0,
|
||||
0,
|
||||
1
|
||||
],
|
||||
"spin": 90
|
||||
},
|
||||
{
|
||||
"id": 99,
|
||||
"set": "A0",
|
||||
"position": [
|
||||
957.97,
|
||||
-323.38,
|
||||
-26.58
|
||||
],
|
||||
"normal": [
|
||||
0,
|
||||
0,
|
||||
1
|
||||
],
|
||||
"spin": 90
|
||||
},
|
||||
{
|
||||
"id": 78,
|
||||
"set": "A0",
|
||||
"position": [
|
||||
821.84,
|
||||
-345.7,
|
||||
-26.78
|
||||
],
|
||||
"normal": [
|
||||
0,
|
||||
0,
|
||||
1
|
||||
],
|
||||
"spin": 90
|
||||
}
|
||||
],
|
||||
"model": [
|
||||
@@ -2306,9 +2310,48 @@
|
||||
]
|
||||
},
|
||||
"markers": [
|
||||
{"id": 147, "position": [12, -24, -17.1], "normal": [-10.98, 0, -23.56], "spin": 90},
|
||||
{"id": 196, "position": [1.5, -2.2, 25.8], "normal": [0, -25.6, 9.5], "spin":270 },
|
||||
{"id": 137, "position": [13.9, -40, 0], "normal": [1, -0.35, 0.4], "spin": 207}
|
||||
{
|
||||
"id": 147,
|
||||
"position": [
|
||||
12,
|
||||
-24,
|
||||
-17.1
|
||||
],
|
||||
"normal": [
|
||||
-10.98,
|
||||
0,
|
||||
-23.56
|
||||
],
|
||||
"spin": 90
|
||||
},
|
||||
{
|
||||
"id": 196,
|
||||
"position": [
|
||||
1.5,
|
||||
-2.2,
|
||||
25.8
|
||||
],
|
||||
"normal": [
|
||||
0,
|
||||
-25.6,
|
||||
9.5
|
||||
],
|
||||
"spin": 270
|
||||
},
|
||||
{
|
||||
"id": 137,
|
||||
"position": [
|
||||
13.9,
|
||||
-40,
|
||||
0
|
||||
],
|
||||
"normal": [
|
||||
1,
|
||||
-0.35,
|
||||
0.4
|
||||
],
|
||||
"spin": 207
|
||||
}
|
||||
],
|
||||
"model": [
|
||||
{
|
||||
@@ -2381,10 +2424,50 @@
|
||||
0.8,
|
||||
0.2
|
||||
]
|
||||
},"markers": [
|
||||
{"id": 142, "position": [-12, -24, 17.1], "normal": [10.98, 0, 23.56], "spin": 0},
|
||||
{"id": 179, "position": [-1.5, -2.2, -25.8], "normal": [0, -25.6, -9.5], "spin":90 },
|
||||
{"id": 178, "position": [-13.9, -40, 0], "normal": [-1, -0.35, -0.4], "spin": -117}
|
||||
},
|
||||
"markers": [
|
||||
{
|
||||
"id": 142,
|
||||
"position": [
|
||||
-12,
|
||||
-24,
|
||||
17.1
|
||||
],
|
||||
"normal": [
|
||||
10.98,
|
||||
0,
|
||||
23.56
|
||||
],
|
||||
"spin": 0
|
||||
},
|
||||
{
|
||||
"id": 179,
|
||||
"position": [
|
||||
-1.5,
|
||||
-2.2,
|
||||
-25.8
|
||||
],
|
||||
"normal": [
|
||||
0,
|
||||
-25.6,
|
||||
-9.5
|
||||
],
|
||||
"spin": 90
|
||||
},
|
||||
{
|
||||
"id": 178,
|
||||
"position": [
|
||||
-13.9,
|
||||
-40,
|
||||
0
|
||||
],
|
||||
"normal": [
|
||||
-1,
|
||||
-0.35,
|
||||
-0.4
|
||||
],
|
||||
"spin": -117
|
||||
}
|
||||
],
|
||||
"model": [
|
||||
{
|
||||
|
||||
502
scripts/robot_rendering.json
Normal file
502
scripts/robot_rendering.json
Normal file
@@ -0,0 +1,502 @@
|
||||
{
|
||||
"_label": "todo3_2026-06-11",
|
||||
"coordinateSystem": {"handedness": "right", "x": "right", "y": "backward", "z": "up"},
|
||||
"units": {"_owner": "appRobotDriver", "length": "mm", "rotation": "degree"},
|
||||
"kinematics": {
|
||||
"_owner": "appRobotDriver",
|
||||
"type": "arm3segmentlinearx"
|
||||
},
|
||||
"motion": {
|
||||
"_owner": "appRobotDriver",
|
||||
"defaultFeedrate": 2300,
|
||||
"speedMode": "legacy",
|
||||
"speedModeOptions": ["legacy", "correct"]
|
||||
},
|
||||
"controllers": {
|
||||
"_owner": "appRobotDriver",
|
||||
"base": { "ip": "fluidNcBase.local", "port": 2300, "protocol": "telnet", "axes": ["x", "y", "z"] },
|
||||
"elbow": { "ip": "fluidNcEllbow.local", "port": 5000, "protocol": "telnet", "axes": ["a", null, null] },
|
||||
"hand": { "ip": "fluidNcHand.local", "port": 5000, "protocol": "telnet", "axes": ["c", "e", "b"] }
|
||||
},
|
||||
"vision_config": {"MarkerType": "DICT_4X4_250", "MarkerSize": 0.025},
|
||||
"renderingInfo": {
|
||||
"width": 1280,
|
||||
"height": 720,
|
||||
"renderDefaults": {"width": 1280, "height": 720, "dofFStop": 11},
|
||||
"cameraPosition__1": [-10, -800, 500],
|
||||
"cameraPosition__2": [-500, 300, 1200],
|
||||
"cameraPosition__3": [-200, -900, 200],
|
||||
"cameraPosition__4": [1200, 200, 300],
|
||||
"cameraPosition_a": [-300, -800, 500],
|
||||
"cameraPosition": [-200, 200, 1400],
|
||||
"cameraPosition_c": [600, -500, 600],
|
||||
"cameraTarget": [200, -200, 180],
|
||||
"cameraUpVector": [0, 0, 1],
|
||||
"lightPosition": [-500, -500, 500],
|
||||
"lightTarget": [0, 0, 0],
|
||||
"lightUpVector": [0, 0, 1],
|
||||
"metric": "mm",
|
||||
"showSkeleton": true,
|
||||
"showMarkers": true,
|
||||
"backgroundColor": [0.7, 0.85, 1.0],
|
||||
"backgroundStrength": 0.2,
|
||||
"sunEnergy": 0.35,
|
||||
"areaEnergy": 120,
|
||||
"exposure": -1.5,
|
||||
"lensDirt": true,
|
||||
"lensDirtStrength": 0.08,
|
||||
"dofEnabled": true,
|
||||
"dofFStop": 11.0,
|
||||
"arucoDust": true,
|
||||
"arucoDustStrength": 1.6,
|
||||
"markerOffsetMaxMm": 4.0,
|
||||
"markerOffsetSeed": 0,
|
||||
"markerRotationMaxDeg": 3,
|
||||
"motionBlur": true,
|
||||
"motionBlurMaxPx": 5.5,
|
||||
"focalErrorPct": 0.5,
|
||||
"principalErrorPx": 3.0,
|
||||
"residualDistortion": [0.02, -0.01],
|
||||
"localizedBlur": false,
|
||||
"localizedBlurStrength": 0.15,
|
||||
"vignette": true,
|
||||
"vignetteStrength": 0.08,
|
||||
"sensorNoise": true,
|
||||
"sensorNoiseStrength": 0.01,
|
||||
"lensDistortion": true,
|
||||
"lensDistortionStrength": 0.002,
|
||||
"materials": {
|
||||
"wood": {"baseColor": [0.72, 0.52, 0.33], "roughness": 0.85, "metallic": 0.0},
|
||||
"plaWhite": {"baseColor": [0.95, 0.95, 0.95], "roughness": 0.45, "metallic": 0.0},
|
||||
"steel": {"baseColor": [0.72, 0.72, 0.75], "roughness": 0.25, "metallic": 1.0},
|
||||
"powderCoatBlue": {"baseColor": [0.15, 0.25, 0.7], "roughness": 0.55, "metallic": 0.0},
|
||||
"defaultPlastic": {"baseColor": [0.95, 0.95, 0.95], "roughness": 0.4, "metallic": 0.0},
|
||||
"skeletonRed": {"baseColor": [0.85, 0.2, 0.2], "roughness": 0.35, "metallic": 0.0},
|
||||
"markerBlack": {"baseColor": [0.04, 0.04, 0.04], "roughness": 0.8, "metallic": 0.0}
|
||||
},
|
||||
"skeletonDefaults": {"radius": 4, "color": [0.85, 0.2, 0.2]},
|
||||
"markerDefaults": {"size": 25, "thickness": 1, "color": [0.04, 0.04, 0.04]},
|
||||
"defaultPosition": {"x": 80, "y": 20, "z": 80, "a": -120, "b": 23, "c": 9, "e": 3}
|
||||
},
|
||||
"defaultPosition__": {"x": 10, "y": 4, "z": 20, "a": 10, "b": 2, "c": 9, "e": 1},
|
||||
"defaultPosition": {"x": 50, "y": 4, "z": 176, "a": 20, "b": 60, "c": 9, "e": 5},
|
||||
"recognized": {"x": null, "y": null, "z": null, "a": null, "b": null, "c": null, "e": null},
|
||||
"constraint_rules": {
|
||||
"rigid_distance": {"enabled": true, "mode": "mst", "weight": 1.0},
|
||||
"joint_axis_projection": {"enabled": true, "max_pairs": 2, "weight": 0.35},
|
||||
"chain_axis_projection": {"enabled": false, "max_depth": 3, "max_pairs": 2, "weight": 0.15},
|
||||
"axis_alignment_threshold": 0.95
|
||||
},
|
||||
"observation_weighting": {"enabled": true, "distance_weight": true, "marker_size_weight": true, "view_angle_weight": true},
|
||||
"multiview_calculation": {
|
||||
"combine_mode": "mean",
|
||||
"size_ref_px": 50.0,
|
||||
"border_ref_px": 120.0,
|
||||
"center_ref_norm": 0.01,
|
||||
"sharpness_ref": 2500.0,
|
||||
"homography_ref": 0.18,
|
||||
"size_factor": 0.3,
|
||||
"aspect_factor": 0.3,
|
||||
"border_factor": 0.01,
|
||||
"center_factor": 0.01,
|
||||
"sharpness_factor": 0.5,
|
||||
"homography_factor": 0.2,
|
||||
"normal_visibility_factor": 0.01,
|
||||
"spin_factor": 0.3,
|
||||
"weight_floor": 0.3
|
||||
},
|
||||
"pose_estimation": {
|
||||
"method": "hybrid",
|
||||
"marker_observation": "corner_pose",
|
||||
"use_normals": true,
|
||||
"normal_weight": 100.0,
|
||||
"robust_loss": "huber",
|
||||
"huber_delta_mm": 8.0,
|
||||
"max_iterations": 200,
|
||||
"min_cameras_per_marker": 2,
|
||||
"finger_block_joints": ["b", "c", "e"],
|
||||
"per_link_method": {}
|
||||
},
|
||||
"robot_test_poses": {
|
||||
"4": {"x": 70, "y": 50, "z": -70, "a": 120, "b": 50, "c": 30, "e": 20},
|
||||
"5": {"x": 180, "y": 86, "z": -120, "a": -60, "b": 22, "c": 91, "e": 10},
|
||||
"6": {"x": 80, "y": 20, "z": 80, "a": -120, "b": 23, "c": 9, "e": 3},
|
||||
"7": {"x": 30, "y": -2, "z": 95, "a": 20, "b": 23, "c": 9, "e": 9},
|
||||
"8": {"x": 50, "y": -2, "z": 95, "a": 20, "b": 60, "c": 9, "e": 3},
|
||||
"9": {"x": 60, "y": -2, "z": 95, "a": 200, "b": 60, "c": 9, "e": 8},
|
||||
"9a": {
|
||||
"x": 60,
|
||||
"y": -2,
|
||||
"z": 95,
|
||||
"a": 200,
|
||||
"b": 60,
|
||||
"c": 9,
|
||||
"e": 8,
|
||||
"rendering": {"width": 1440, "height": 1080, "dofFStop": 11}
|
||||
},
|
||||
"9b": {
|
||||
"x": 60,
|
||||
"y": -2,
|
||||
"z": 95,
|
||||
"a": 200,
|
||||
"b": 60,
|
||||
"c": 9,
|
||||
"e": 8,
|
||||
"rendering": {"width": 4896, "height": 3264, "dofFStop": 5.6}
|
||||
},
|
||||
"10": {"x": 120, "y": 60, "z": -110, "a": 20, "b": 30, "c": 180, "e": 4},
|
||||
"11": {"x": 50, "y": 4, "z": 176, "a": 20, "b": 60, "c": 9, "e": 5},
|
||||
"12": {"x": 50, "y": 0, "z": 178, "a": 210, "b": 80, "c": 90, "e": 6}
|
||||
},
|
||||
"test_camera_positions": {
|
||||
"a": [-300, -800, 800],
|
||||
"b": [300, -900, 1200],
|
||||
"c": [300, -900, 400],
|
||||
"d": [700, -800, 400],
|
||||
"e": [1200, -900, 400],
|
||||
"f": [500, -300, 1400],
|
||||
"g": [-200, 200, 1400]
|
||||
},
|
||||
"test_camera_targets": {
|
||||
"a": [210, -100, 180],
|
||||
"b": [310, -80, 180],
|
||||
"c": [210, -100, 150],
|
||||
"d": [210, -100, 150],
|
||||
"e": [210, -100, 50],
|
||||
"f": [200, -200, 180],
|
||||
"g": [200, -200, 180]
|
||||
},
|
||||
"movements": {"x": null, "y": null, "z": null, "a": null, "b": null, "c": null, "e": null},
|
||||
"state_pose_params": {
|
||||
"numbers_of_Elements_to_consider_start": 3,
|
||||
"numbers_of_Elements_to_consider_final": 5,
|
||||
"solver_in_between_geometrical": false,
|
||||
"solver_after_geometrical": false,
|
||||
"geometric_passes_per_stage": 2,
|
||||
"revolute_search_coarse_deg": 5.0,
|
||||
"revolute_search_fine_deg": 1.0,
|
||||
"root_pose_min_markers": 3,
|
||||
"use_marker_normals_flip_tiebreak": true,
|
||||
"normal_flip_weight": 0.05
|
||||
},
|
||||
"links": {
|
||||
"_owner": "appRobotDriver",
|
||||
"Board": {
|
||||
"parent": null,
|
||||
"size": [1000, 200, 25],
|
||||
"mountPosition": [0, 0, 0],
|
||||
"mountRotation": [0, 0, 0],
|
||||
"skeleton": {"from": [0, 0, 16], "to": [1000, 0, 16], "radius": 4, "color": [0.85, 0.2, 0.2]},
|
||||
"markers": [
|
||||
{"id": 210, "set": "Brett", "position": [20, -20, 0.3], "normal": [0, 0, 1]},
|
||||
{"id": 211, "set": "Brett", "position": [250, -10, 0.3], "normal": [0, 0, 1]},
|
||||
{"id": 215, "set": "Brett", "position": [250, -90, 0.3], "normal": [0, 0, 1]},
|
||||
{"id": 214, "set": "Brett", "position": [350, -10, 0.3], "normal": [0, 0, 1]},
|
||||
{"id": 208, "set": "Brett", "position": [350, -90, 0.3], "normal": [0, 0, 1]},
|
||||
{"id": 206, "set": "Brett", "position": [650, -10, 0.3], "normal": [0, 0, 1]},
|
||||
{"id": 205, "set": "Brett", "position": [750, -90, 0.3], "normal": [0, 0, 1]},
|
||||
{"id": 207, "set": "Brett", "position": [750, -10, 0.3], "normal": [0, 0, 1]},
|
||||
{"id": 217, "set": "Brett", "position": [650, -90, 0.3], "normal": [0, 0, 1]},
|
||||
{
|
||||
"id": 46,
|
||||
"set": "A0",
|
||||
"position": [536.71, 185.44, -27.3],
|
||||
"normal": [0, 0, 1],
|
||||
"spin": 90,
|
||||
"info": "is placed on a white paper, A0_60Arucos_25mm_Seet223.pdf, with the following marker placements:"
|
||||
},
|
||||
{"id": 47, "set": "A0", "position": [344.23, -286.54, -27.3], "normal": [0, 0, 1], "spin": 90},
|
||||
{"id": 48, "set": "A0", "position": [688.69, -320.72, -27.3], "normal": [0, 0, 1], "spin": 90},
|
||||
{"id": 49, "set": "A0", "position": [1006.0, 158.33, -27.3], "normal": [0, 0, 1], "spin": 90},
|
||||
{"id": 50, "set": "A0", "position": [573.41, 211.86, -27.3], "normal": [0, 0, 1], "spin": 90},
|
||||
{"id": 51, "set": "A0", "position": [167.8, -172.08, -27.3], "normal": [0, 0, 1], "spin": 90},
|
||||
{"id": 52, "set": "A0", "position": [94.68, 208.66, -27.3], "normal": [0, 0, 1], "spin": 90},
|
||||
{"id": 53, "set": "A0", "position": [486.25, 212.24, -27.3], "normal": [0, 0, 1], "spin": 90},
|
||||
{"id": 54, "set": "A0", "position": [342.27, -330.59, -27.3], "normal": [0, 0, 1], "spin": 90},
|
||||
{"id": 55, "set": "A0", "position": [283.72, -262.58, -27.3], "normal": [0, 0, 1], "spin": 90},
|
||||
{"id": 56, "set": "A0", "position": [498.68, 168.67, -27.3], "normal": [0, 0, 1], "spin": 90},
|
||||
{"id": 57, "set": "A0", "position": [602.86, -364.05, -27.3], "normal": [0, 0, 1], "spin": 90},
|
||||
{"id": 58, "set": "A0", "position": [50.09, -218.11, -27.3], "normal": [0, 0, 1], "spin": 90},
|
||||
{"id": 59, "set": "A0", "position": [626.21, -278.75, -27.3], "normal": [0, 0, 1], "spin": 90},
|
||||
{"id": 60, "set": "A0", "position": [434.36, 283.81, -27.3], "normal": [0, 0, 1], "spin": 90},
|
||||
{"id": 61, "set": "A0", "position": [-22.42, 335.83, -27.3], "normal": [0, 0, 1], "spin": 90},
|
||||
{"id": 62, "set": "A0", "position": [404.7, -175.1, -27.3], "normal": [0, 0, 1], "spin": 90},
|
||||
{"id": 63, "set": "A0", "position": [777.4, -236.15, -27.3], "normal": [0, 0, 1], "spin": 90},
|
||||
{"id": 64, "set": "A0", "position": [-21.27, -188.23, -27.3], "normal": [0, 0, 1], "spin": 90},
|
||||
{"id": 65, "set": "A0", "position": [803.39, -297.37, -27.3], "normal": [0, 0, 1], "spin": 90},
|
||||
{"id": 66, "set": "A0", "position": [209.75, -363.23, -27.3], "normal": [0, 0, 1], "spin": 90},
|
||||
{"id": 67, "set": "A0", "position": [523.07, 267.04, -27.3], "normal": [0, 0, 1], "spin": 90},
|
||||
{"id": 68, "set": "A0", "position": [573.73, 170.64, -27.3], "normal": [0, 0, 1], "spin": 90},
|
||||
{"id": 69, "set": "A0", "position": [7.61, -281.21, -27.3], "normal": [0, 0, 1], "spin": 90},
|
||||
{"id": 70, "set": "A0", "position": [601.87, 300.33, -27.3], "normal": [0, 0, 1], "spin": 90},
|
||||
{"id": 71, "set": "A0", "position": [749.75, -284.01, -27.3], "normal": [0, 0, 1], "spin": 90},
|
||||
{"id": 72, "set": "A0", "position": [440.99, 194.32, -27.3], "normal": [0, 0, 1], "spin": 90},
|
||||
{"id": 73, "set": "A0", "position": [221.73, 333.11, -27.3], "normal": [0, 0, 1], "spin": 90},
|
||||
{"id": 74, "set": "A0", "position": [93.78, 144.5, -27.3], "normal": [0, 0, 1], "spin": 90},
|
||||
{"id": 75, "set": "A0", "position": [-25.7, 194.58, -27.3], "normal": [0, 0, 1], "spin": 90},
|
||||
{"id": 76, "set": "A0", "position": [685.21, 166.8, -27.3], "normal": [0, 0, 1], "spin": 90},
|
||||
{"id": 77, "set": "A0", "position": [18.19, 191.57, -27.3], "normal": [0, 0, 1], "spin": 90},
|
||||
{"id": 78, "set": "A0", "position": [823.11, -344.38, -27.3], "normal": [0, 0, 1], "spin": 90},
|
||||
{"id": 79, "set": "A0", "position": [312.3, -159.11, -27.3], "normal": [0, 0, 1], "spin": 90},
|
||||
{"id": 80, "set": "A0", "position": [863.59, -335.92, -27.3], "normal": [0, 0, 1], "spin": 90},
|
||||
{"id": 81, "set": "A0", "position": [132.14, 169.03, -27.3], "normal": [0, 0, 1], "spin": 90},
|
||||
{"id": 82, "set": "A0", "position": [219.16, 297.24, -27.3], "normal": [0, 0, 1], "spin": 90},
|
||||
{"id": 83, "set": "A0", "position": [44.16, 339.22, -27.3], "normal": [0, 0, 1], "spin": 90},
|
||||
{"id": 84, "set": "A0", "position": [407.49, 258.42, -27.3], "normal": [0, 0, 1], "spin": 90},
|
||||
{"id": 85, "set": "A0", "position": [504.58, -312.75, -27.3], "normal": [0, 0, 1], "spin": 90},
|
||||
{"id": 86, "set": "A0", "position": [362.89, 292.01, -27.3], "normal": [0, 0, 1], "spin": 90},
|
||||
{"id": 87, "set": "A0", "position": [943.63, -245.76, -27.3], "normal": [0, 0, 1], "spin": 90},
|
||||
{"id": 88, "set": "A0", "position": [765.87, 316.04, -27.3], "normal": [0, 0, 1], "spin": 90},
|
||||
{"id": 89, "set": "A0", "position": [988.02, -369.14, -27.3], "normal": [0, 0, 1], "spin": 90},
|
||||
{"id": 90, "set": "A0", "position": [643.17, 316.43, -27.3], "normal": [0, 0, 1], "spin": 90},
|
||||
{"id": 91, "set": "A0", "position": [723.35, 328.05, -27.3], "normal": [0, 0, 1], "spin": 90},
|
||||
{"id": 92, "set": "A0", "position": [645.09, -184.84, -27.3], "normal": [0, 0, 1], "spin": 90},
|
||||
{"id": 93, "set": "A0", "position": [934.88, 143.6, -27.3], "normal": [0, 0, 1], "spin": 90},
|
||||
{"id": 94, "set": "A0", "position": [875.7, 173.65, -27.3], "normal": [0, 0, 1], "spin": 90},
|
||||
{"id": 95, "set": "A0", "position": [186.04, -274.07, -27.3], "normal": [0, 0, 1], "spin": 90},
|
||||
{"id": 96, "set": "A0", "position": [369.77, -186.49, -27.3], "normal": [0, 0, 1], "spin": 90},
|
||||
{"id": 97, "set": "A0", "position": [304.35, -359.67, -27.3], "normal": [0, 0, 1], "spin": 90},
|
||||
{"id": 98, "set": "A0", "position": [575.27, 315.06, -27.3], "normal": [0, 0, 1], "spin": 90},
|
||||
{"id": 99, "set": "A0", "position": [959.16, -321.55, -27.3], "normal": [0, 0, 1], "spin": 90},
|
||||
{"id": 100, "set": "A0", "position": [803.25, 172.36, -27.3], "normal": [0, 0, 1], "spin": 90},
|
||||
{"id": 101, "set": "A0", "position": [117.7, 298.66, -27.3], "normal": [0, 0, 1], "spin": 90},
|
||||
{"id": 102, "set": "A0", "position": [649.69, -223.0, -27.3], "normal": [0, 0, 1], "spin": 90},
|
||||
{"id": 103, "set": "A0", "position": [105.71, -187.71, -27.3], "normal": [0, 0, 1], "spin": 90},
|
||||
{"id": 104, "set": "A0", "position": [826.71, 239.16, -27.3], "normal": [0, 0, 1], "spin": 90},
|
||||
{"id": 105, "set": "A0", "position": [524.84, -266.25, -27.3], "normal": [0, 0, 1], "spin": 90}
|
||||
],
|
||||
"model": [
|
||||
{
|
||||
"stlFile": "surfaces/Board.stl",
|
||||
"originOfModel": [0, 0, 0],
|
||||
"rotationOfModelDegree": [0, 0, -90],
|
||||
"material": "wood"
|
||||
},
|
||||
{
|
||||
"stlFile": "surfaces/BoardRail.stl",
|
||||
"originOfModel": [0, 0, 0],
|
||||
"rotationOfModelDegree": [0, 0, -90],
|
||||
"material": "steel"
|
||||
}
|
||||
]
|
||||
},
|
||||
"Base": {
|
||||
"parent": "Board",
|
||||
"size": [150, 200, 150],
|
||||
"mountPosition": [0, 0, 0],
|
||||
"mountRotation": [0, 0, 0],
|
||||
"jointToParent": {
|
||||
"name": "Slider",
|
||||
"type": "linear",
|
||||
"axis": [1, 0, 0],
|
||||
"origin": [0, 0, 16],
|
||||
"rotation": [0, 0, 0],
|
||||
"variable": "x",
|
||||
"feedrate": 2000,
|
||||
"controller": "base"
|
||||
},
|
||||
"skeleton": {"from": [0, 108, 45], "to": [110, 108, 45], "radius": 4, "color": [0.2, 0.8, 0.2]},
|
||||
"markers": [],
|
||||
"model": [
|
||||
{
|
||||
"stlFile": "surfaces/Base.stl",
|
||||
"originOfModel": [-30, 0, -35],
|
||||
"rotationOfModelDegree": [0, 0, 0],
|
||||
"material": "plaWhite"
|
||||
}
|
||||
]
|
||||
},
|
||||
"Arm1": {
|
||||
"parent": "Base",
|
||||
"size": [70, 250, 70],
|
||||
"mountPosition": [0, 0, 0],
|
||||
"mountRotation": [0, 0, 0],
|
||||
"jointToParent": {
|
||||
"name": "Joint1",
|
||||
"type": "revolute",
|
||||
"axis": [-1, 0, 0],
|
||||
"origin": [110, 108, 45],
|
||||
"rotation": [0, 0, 0],
|
||||
"variable": "y",
|
||||
"feedrate": 2300,
|
||||
"controller": "base"
|
||||
},
|
||||
"skeleton": {"from": [0, 0, 0], "to": [0, -250, 0], "radius": 4, "color": [0.2, 0.2, 0.9]},
|
||||
"markers": [
|
||||
{"id": 198, "name": "aruco_198", "position": [0, -160, 35], "normal": [0, 0, 1], "size": 25, "spin": 0},
|
||||
{"id": 229, "name": "aruco_229", "position": [0, -250, 35], "normal": [0, 0, 1], "size": 25, "spin": 0},
|
||||
{"id": 242, "name": "aruco_242", "position": [0, -250, -35], "normal": [0, 0, -1], "size": 25, "spin": 0},
|
||||
{"id": 243, "name": "aruco_243", "position": [0, -285, 0], "normal": [0, -1, 0], "size": 25, "spin": 0}
|
||||
],
|
||||
"model": [
|
||||
{
|
||||
"stlFile": "surfaces/Holm.stl",
|
||||
"originOfModel__": [-25, 29, -28.5],
|
||||
"originOfModel": [-29, 25, 28.5],
|
||||
"rotationOfModelDegree__": [0, 0, 0],
|
||||
"rotationOfModelDegree": [180, 0, -90],
|
||||
"material": "powderCoatBlue"
|
||||
}
|
||||
]
|
||||
},
|
||||
"Ellbow": {
|
||||
"parent": "Arm1",
|
||||
"mountPosition": [0, 0, 0],
|
||||
"mountRotation": [0, 0, 0],
|
||||
"jointToParent": {
|
||||
"name": "Joint2",
|
||||
"type": "revolute",
|
||||
"axis": [-1, 0, 0],
|
||||
"origin": [0, -250, 0],
|
||||
"rotation": [0, 0, 0],
|
||||
"variable": "z",
|
||||
"feedrate": 2300,
|
||||
"controller": "base"
|
||||
},
|
||||
"skeleton": {"from": [0, 0, 0], "to": [90, 0, 0], "radius": 4, "color": [0.9, 0.2, 0.2]},
|
||||
"model": [
|
||||
{
|
||||
"stlFile": "surfaces/Ellebogen.stl",
|
||||
"originOfModel": [90, 0, 0],
|
||||
"rotationOfModelDegree": [0, -90, -90],
|
||||
"material": "defaultPlastic"
|
||||
}
|
||||
],
|
||||
"markers": [
|
||||
{"id": 244, "name": "aruco_244", "position": [125, 0, 0], "normal": [1, 0, 0], "size": 25, "spin": 0},
|
||||
{"id": 245, "name": "aruco_245", "position": [90, 0, -35], "normal": [0, 0, -1], "size": 25, "spin": 0},
|
||||
{"id": 246, "name": "aruco_246", "position": [90, 0, 35], "normal": [0, 0, 1], "size": 25},
|
||||
{"id": 247, "name": "aruco_247", "position": [52.5, 0, 35], "normal": [0, 0, 1], "size": 25},
|
||||
{"id": 248, "name": "aruco_248", "position": [52.5, 0, -35], "normal": [0, 0, -1], "size": 25},
|
||||
{"id": 232, "name": "aruco_232", "position": [90, 24.75, -24.75], "normal": [0, 1, -1], "size": 25},
|
||||
{"id": 231, "name": "aruco_231", "position": [90, 24.75, 24.75], "normal": [0, 1, 1], "size": 25}
|
||||
]
|
||||
},
|
||||
"Arm2": {
|
||||
"parent": "Ellbow",
|
||||
"mountPosition": [0, 0, 0],
|
||||
"mountRotation": [0, 0, 0],
|
||||
"jointToParent": {
|
||||
"name": "Joint3",
|
||||
"type": "revolute",
|
||||
"axis": [0, -1, 0],
|
||||
"origin": [90, 0, 0],
|
||||
"rotation": [0, 0, 0],
|
||||
"variable": "a",
|
||||
"feedrate": 2300,
|
||||
"controller": "elbow"
|
||||
},
|
||||
"skeleton": {"from": [0, 0, 0], "to": [0, -250, 0], "radius": 4, "color": [0.95, 0.85, 0.2]},
|
||||
"model": [
|
||||
{
|
||||
"stlFile": "surfaces/Unterarm.stl",
|
||||
"originOfModel": [0, -250, 0],
|
||||
"rotationOfModelDegree": [180, 0, -90],
|
||||
"material": "defaultPlastic"
|
||||
}
|
||||
],
|
||||
"markers": [
|
||||
{"id": 120, "position": [24.75, -112, -24.75], "normal": [1, 0, -1]},
|
||||
{"id": 122, "name": "aruco_122", "position": [-35, -112, 0], "normal": [-1, 0, 0]},
|
||||
{"id": 218, "name": "aruco_218", "position": [35, -112, 0], "normal": [1, 0, 0]},
|
||||
{"id": 113, "name": "aruco_113", "position": [0, -182, 30], "normal": [0, 0, 1]},
|
||||
{"id": 114, "name": "aruco_114", "position": [24.75, -182, -24.75], "normal": [1, 0, -1]},
|
||||
{"id": 115, "name": "aruco_115", "position": [-24.75, -182, -24.75], "normal": [-1, 0, -1]},
|
||||
{"id": 124, "name": "aruco_124", "position": [-35, -219, 0], "normal": [-1, 0, 0]},
|
||||
{"id": 219, "name": "aruco_219", "position": [35, -219, 0], "normal": [1, 0, 0]}
|
||||
]
|
||||
},
|
||||
"Hand": {
|
||||
"parent": "Arm2",
|
||||
"mountPosition": [0, 0, 0],
|
||||
"mountRotation": [0, 0, 0],
|
||||
"jointToParent": {
|
||||
"name": "Joint4",
|
||||
"type": "revolute",
|
||||
"axis": [1, 0, 0],
|
||||
"origin": [0, -250, 0],
|
||||
"rotation": [0, 0, 0],
|
||||
"variable": "b",
|
||||
"feedrate": 2300,
|
||||
"controller": "hand"
|
||||
},
|
||||
"skeleton": {"from": [0, 0, 0], "to": [0, -35, 0], "radius": 4, "color": [0.95, 0.55, 0.15]}
|
||||
},
|
||||
"Palm": {
|
||||
"parent": "Hand",
|
||||
"mountPosition": [0, 0, 0],
|
||||
"mountRotation": [0, 0, 0],
|
||||
"jointToParent": {
|
||||
"name": "Joint3",
|
||||
"type": "revolute",
|
||||
"axis": [0, -1, 0],
|
||||
"origin": [0, 0, 0],
|
||||
"rotation": [0, 0, 0],
|
||||
"variable": "c",
|
||||
"feedrate": 2300,
|
||||
"controller": "hand"
|
||||
},
|
||||
"skeleton": {"from": [-50, -35, 0], "to": [50, -35, 0], "radius": 7, "color": [0.95, 0.2, 0.2]}
|
||||
},
|
||||
"FingerA": {
|
||||
"parent": "Palm",
|
||||
"size": [80, 60, 20],
|
||||
"mountPosition": [0, 0, 0],
|
||||
"mountRotation": [0, 0, 0],
|
||||
"jointToParent": {
|
||||
"name": "Slider",
|
||||
"type": "linear",
|
||||
"axis": [1, 0, 0],
|
||||
"origin": [4, -35, 0],
|
||||
"rotation": [0, 0, 0],
|
||||
"variable": "e",
|
||||
"feedrate": 2000,
|
||||
"controller": "hand"
|
||||
},
|
||||
"skeleton": {"from": [0, 0, 0], "to": [0, -60, 0], "radius": 4, "color": [0.2, 0.8, 0.2]},
|
||||
"markers": [
|
||||
{"id": 40, "position": [12, -24, -17.1], "normal": [-10.98, 0, -23.56]},
|
||||
{"id": 41, "position": [1.5, -2.2, 25.8], "normal": [0, -25.6, 9.5]},
|
||||
{"id": 42, "position": [13.9, -40, 0], "normal": [1, -0.35, 0.4], "spin": 27}
|
||||
],
|
||||
"model": [
|
||||
{
|
||||
"stlFile": "surfaces/Finger.stl",
|
||||
"originOfModel": [24, 0, -9.1],
|
||||
"rotationOfModelDegree": [90, -90, 0],
|
||||
"material": "defaultPlastic"
|
||||
}
|
||||
]
|
||||
},
|
||||
"FingerB": {
|
||||
"parent": "Palm",
|
||||
"size": [80, 60, 20],
|
||||
"mountPosition": [0, 0, 0],
|
||||
"mountRotation": [0, 0, 0],
|
||||
"jointToParent": {
|
||||
"name": "Slider",
|
||||
"type": "linear",
|
||||
"axis": [-1, 0, 0],
|
||||
"origin": [-4, -35, 0],
|
||||
"rotation": [0, 0, 0],
|
||||
"variable": "e",
|
||||
"feedrate": 2000,
|
||||
"controller": "hand"
|
||||
},
|
||||
"skeleton": {"from": [0, 0, 0], "to": [0, -60, 0], "radius": 4, "color": [0.2, 0.8, 0.2]},
|
||||
"markers": [
|
||||
{"id": 43, "position": [-12, -24, 17.1], "normal": [10.98, 0, 23.56], "spin": 90},
|
||||
{"id": 44, "position": [-1.5, -2.2, -25.8], "normal": [0, -25.6, -9.5], "spin": 90},
|
||||
{"id": 45, "position": [-13.9, -40, 0], "normal": [-1, -0.35, -0.4], "spin": -27}
|
||||
],
|
||||
"model": [
|
||||
{
|
||||
"stlFile": "surfaces/Finger.stl",
|
||||
"originOfModel": [-24, 0, 9.1],
|
||||
"rotationOfModelDegree": [90, 90, 0],
|
||||
"material": "defaultPlastic"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
42
server/buildG92.cjs
Normal file
42
server/buildG92.cjs
Normal file
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* buildG92.cjs
|
||||
* Baut aus einem Homing-State {x,y,z,a,b,c,e} einen G92-G-Code-String.
|
||||
*
|
||||
* G92 setzt am appRobotDriver die Motorposition OHNE Bewegung (intern als M92
|
||||
* verarbeitet, siehe appRobotDriver/doc/API.md + robot/RobotController.js) —
|
||||
* exakt die Homing-Semantik. Die Achsbuchstaben bilden 1:1 auf die Motorachsen
|
||||
* ab: X→xMotor, Y→alpha, Z→beta, A→a, B→b, C→c, E→e.
|
||||
*
|
||||
* Die Homing-Kette (4b: Arm1→y, Ellbow→z, Arm2→a, Hand→b) bestimmt c (Palm) und
|
||||
* e (Greifer) nicht. Entscheidung: fehlende Achsen als 0 mitsenden
|
||||
* (`fillMissingWithZero`), damit G92 alle 7 Achsen trägt.
|
||||
*
|
||||
* CommonJS, damit Jest (CJS) und der ESM-Server dieselbe Funktion nutzen
|
||||
* (gleiches Muster wie spinNormalize.cjs / homingXEstimate.cjs).
|
||||
*/
|
||||
|
||||
// Reihenfolge + Achsbuchstaben wie vom Driver erwartet.
|
||||
const AXES = [
|
||||
['x', 'X'], ['y', 'Y'], ['z', 'Z'],
|
||||
['a', 'A'], ['b', 'B'], ['c', 'C'], ['e', 'E'],
|
||||
];
|
||||
|
||||
/**
|
||||
* @param {Record<string, number|null>} state flacher Joint-State (accumulated_state)
|
||||
* @param {{decimals?: number, fillMissingWithZero?: boolean}} [opts]
|
||||
* @returns {string} z.B. "G92 X192.73 Y35.99 Z-30.88 A-1.70 B12.34 C0.00 E0.00"
|
||||
*/
|
||||
function buildG92(state = {}, { decimals = 2, fillMissingWithZero = true } = {}) {
|
||||
const parts = [];
|
||||
for (const [key, axis] of AXES) {
|
||||
const num = Number(state?.[key]);
|
||||
if (state?.[key] != null && Number.isFinite(num)) {
|
||||
parts.push(`${axis}${num.toFixed(decimals)}`);
|
||||
} else if (fillMissingWithZero) {
|
||||
parts.push(`${axis}${(0).toFixed(decimals)}`);
|
||||
}
|
||||
}
|
||||
return `G92 ${parts.join(' ')}`;
|
||||
}
|
||||
|
||||
module.exports = { buildG92, AXES };
|
||||
77
server/driverClient.js
Normal file
77
server/driverClient.js
Normal file
@@ -0,0 +1,77 @@
|
||||
/**
|
||||
* driverClient.js – WebSocket-Transport zum appRobotDriver
|
||||
*
|
||||
* Der Driver nimmt Steuerbefehle als Plain-Text-G-Code über einen WebSocket
|
||||
* entgegen (wss://…:2096, self-signed), NICHT über HTTP — siehe
|
||||
* appRobotDriver/doc/API.md. Ein früher angenommenes `POST /api/state` existiert
|
||||
* dort nicht (war Platzhalter, vgl. doc/accessRobotAPI.md). G92 setzt am Driver
|
||||
* die Motorposition ohne Bewegung (intern M92) = exakt die Homing-Semantik.
|
||||
*
|
||||
* DRIVER_WS_URL nicht gesetzt → kein Kontakt, klarer 501-Fehler (analog zum
|
||||
* früheren ROBOT_URL-Verhalten).
|
||||
*/
|
||||
import { WebSocket } from 'ws';
|
||||
|
||||
const DRIVER_WS_URL = process.env.DRIVER_WS_URL || '';
|
||||
|
||||
/** true, wenn ein Driver-WebSocket konfiguriert ist. */
|
||||
export function isDriverConfigured() {
|
||||
return Boolean(DRIVER_WS_URL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Öffnet eine kurzlebige WS-Verbindung zum Driver, sendet eine G-Code-Zeile und
|
||||
* wartet auf die erste Antwort (Positions-JSON bzw. Fehler-Envelope). Der Driver
|
||||
* broadcastet nach jedem G-Code das aktuelle Positions-JSON an alle Clients —
|
||||
* der Sender ist selbst Client und bekommt es zurück.
|
||||
*
|
||||
* @param {string} line z.B. "G92 X1 Y2 …"
|
||||
* @param {{timeoutMs?: number}} [opts]
|
||||
* @returns {Promise<{ok:boolean, sent:string, response?:any, error?:string, note?:string}>}
|
||||
*/
|
||||
export function sendGcode(line, { timeoutMs = 4000 } = {}) {
|
||||
const text = String(line ?? '').trim();
|
||||
if (!text) {
|
||||
return Promise.reject(Object.assign(new Error('Leere G-Code-Zeile'), { statusCode: 400 }));
|
||||
}
|
||||
if (!DRIVER_WS_URL) {
|
||||
return Promise.reject(Object.assign(
|
||||
new Error('DRIVER_WS_URL ist nicht konfiguriert'), { statusCode: 501 }));
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
// Self-signed Cert am Driver → Zertifikatsprüfung deaktivieren (interner Hop).
|
||||
const ws = new WebSocket(DRIVER_WS_URL, { rejectUnauthorized: false });
|
||||
let settled = false;
|
||||
|
||||
const finish = (fn, arg) => {
|
||||
if (settled) return;
|
||||
settled = true;
|
||||
clearTimeout(timer);
|
||||
try { ws.close(); } catch { /* egal */ }
|
||||
fn(arg);
|
||||
};
|
||||
|
||||
// Gesendet, aber keine Antwort rechtzeitig: kein harter Fehler — der Befehl
|
||||
// ist raus, der Driver antwortet nur evtl. nicht broadcastfähig.
|
||||
const timer = setTimeout(() => {
|
||||
finish(resolve, { ok: true, sent: text, response: null, note: 'keine Antwort (Timeout)' });
|
||||
}, timeoutMs);
|
||||
|
||||
ws.on('open', () => ws.send(text));
|
||||
|
||||
ws.on('message', (data) => {
|
||||
const raw = data.toString();
|
||||
let parsed;
|
||||
try { parsed = JSON.parse(raw); } catch { parsed = raw; }
|
||||
if (parsed && typeof parsed === 'object' && parsed.type === 'error') {
|
||||
finish(resolve, { ok: false, sent: text, error: parsed.message || raw, response: parsed });
|
||||
} else {
|
||||
finish(resolve, { ok: true, sent: text, response: parsed });
|
||||
}
|
||||
});
|
||||
|
||||
ws.on('error', (err) => finish(reject, Object.assign(
|
||||
new Error(`Driver-WS-Fehler: ${err.message}`), { statusCode: 502 })));
|
||||
});
|
||||
}
|
||||
@@ -12,6 +12,8 @@ import { assignByZRange, removeMarkerAssignment, alignSetToMeasured, assignMarke
|
||||
import multer from 'multer';
|
||||
import { runHoming, runHomingOffline } from './homingOrchestrator.js';
|
||||
import { fetchRobot, robotCachePath } from './robotConfig.js';
|
||||
import { sendGcode, isDriverConfigured } from './driverClient.js';
|
||||
import { buildG92 } from './buildG92.cjs';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
@@ -24,7 +26,8 @@ const publicDir = path.join(__dirname, '..', 'public');
|
||||
const snapshotsDir = path.join(publicDir, 'snapshots');
|
||||
const WEBCAM_URL = process.env.WEBCAM_URL || '';
|
||||
const BODYTRACKER_URL = process.env.BODYTRACKER_URL || '';
|
||||
const ROBOT_URL = process.env.ROBOT_URL || '';
|
||||
// Roboter-Transport läuft über den Driver-WebSocket (DRIVER_WS_URL,
|
||||
// server/driverClient.js), nicht mehr über HTTP ROBOT_URL.
|
||||
const HTTPS_KEY_PATH = process.env.HTTPS_KEY_PATH || path.join(__dirname, '..', 'https', 'localhost.key');
|
||||
const HTTPS_CERT_PATH = process.env.HTTPS_CERT_PATH || path.join(__dirname, '..', 'https', 'localhost.pem');
|
||||
const HTTPS_PASSPHRASE = process.env.HTTPS_PASSPHRASE || 'abcd';
|
||||
@@ -912,29 +915,50 @@ app.post('/api/homing/run', async (req, res) => {
|
||||
|
||||
/**
|
||||
* POST /api/homing/send-state
|
||||
* Sendet { state: { x, y, z, a, b, c, e } } an ROBOT_URL/api/state.
|
||||
* Baut aus { state: { x, y, z, a, b[, c, e] } } ein G92 und sendet es als
|
||||
* Plain-Text-G-Code über den Driver-WebSocket (DRIVER_WS_URL). G92 setzt am
|
||||
* Driver die Motorposition ohne Bewegung (intern M92) = Homing.
|
||||
* Fehlende Achsen (c/Palm, e/Greifer werden vom Homing nicht bestimmt) werden
|
||||
* als 0 mitgesendet (siehe server/buildG92.cjs).
|
||||
*/
|
||||
app.post('/api/homing/send-state', async (req, res) => {
|
||||
try {
|
||||
const { state } = req.body ?? {};
|
||||
if (!state) return res.status(400).json({ error: '"state" fehlt' });
|
||||
if (!ROBOT_URL) return res.status(501).json({ error: 'ROBOT_URL ist nicht konfiguriert' });
|
||||
if (!isDriverConfigured())
|
||||
return res.status(501).json({ error: 'DRIVER_WS_URL ist nicht konfiguriert' });
|
||||
|
||||
const url = new URL('/api/state', ROBOT_URL).toString();
|
||||
const upstream = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(state),
|
||||
});
|
||||
if (!upstream.ok) {
|
||||
const text = await upstream.text();
|
||||
return res.status(upstream.status).json({ error: `Robot-Fehler: ${text}` });
|
||||
}
|
||||
const result = await upstream.json().catch(() => ({}));
|
||||
return res.json({ ok: true, result });
|
||||
const gcode = buildG92(state);
|
||||
const result = await sendGcode(gcode);
|
||||
if (!result.ok)
|
||||
return res.status(502).json({ error: `Robot-Fehler: ${result.error}`, gcode });
|
||||
return res.json({ ok: true, gcode, result: result.response, note: result.note });
|
||||
} catch (err) {
|
||||
console.error('homing/send-state error:', err);
|
||||
return res.status(500).json({ error: String(err) });
|
||||
return res.status(err.statusCode || 500).json({ error: String(err.message || err) });
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* POST /api/robot/gcode { line: "G92 X… Y…" }
|
||||
* Sendet eine beliebige G-Code-Zeile über den Driver-WebSocket. Transport für
|
||||
* die G-Code-/Befehl-Buttons im Frontend (window.sendCommand) — ersetzt den
|
||||
* toten WSS-Altpfad.
|
||||
*/
|
||||
app.post('/api/robot/gcode', async (req, res) => {
|
||||
try {
|
||||
const line = (req.body?.line ?? '').toString().trim();
|
||||
if (!line) return res.status(400).json({ error: '"line" fehlt' });
|
||||
if (!isDriverConfigured())
|
||||
return res.status(501).json({ error: 'DRIVER_WS_URL ist nicht konfiguriert' });
|
||||
|
||||
const result = await sendGcode(line);
|
||||
if (!result.ok)
|
||||
return res.status(502).json({ error: `Robot-Fehler: ${result.error}`, line });
|
||||
return res.json({ ok: true, line, result: result.response, note: result.note });
|
||||
} catch (err) {
|
||||
console.error('robot/gcode error:', err);
|
||||
return res.status(err.statusCode || 500).json({ error: String(err.message || err) });
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
48
test/buildG92.test.js
Normal file
48
test/buildG92.test.js
Normal file
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* buildG92.test.js
|
||||
* Unit-Tests für server/buildG92.cjs
|
||||
*
|
||||
* Sichert ab, dass aus dem Homing-State der korrekte G92-String entsteht und —
|
||||
* gemäß Entscheidung — fehlende Achsen c (Palm) / e (Greifer) als 0 mitgesendet
|
||||
* werden. Achsbuchstaben + Reihenfolge müssen zur Driver-Erwartung passen
|
||||
* (X→xMotor, Y→alpha, Z→beta, A→a, B→b, C→c, E→e).
|
||||
*/
|
||||
|
||||
const { buildG92 } = require('../server/buildG92.cjs');
|
||||
|
||||
describe('buildG92', () => {
|
||||
test('typischer Homing-State (x,y,z,a,b) → c/e als 0 ergänzt, alle 7 Achsen', () => {
|
||||
const state = { x: 192.72935, y: 35.99125, z: -30.87771, a: -1.69522, b: 12.34 };
|
||||
expect(buildG92(state)).toBe('G92 X192.73 Y35.99 Z-30.88 A-1.70 B12.34 C0.00 E0.00');
|
||||
});
|
||||
|
||||
test('Reihenfolge ist immer x,y,z,a,b,c,e (unabhängig von Key-Reihenfolge)', () => {
|
||||
const state = { b: 1, a: 2, x: 3, e: 4, z: 5, y: 6, c: 7 };
|
||||
expect(buildG92(state)).toBe('G92 X3.00 Y6.00 Z5.00 A2.00 B1.00 C7.00 E4.00');
|
||||
});
|
||||
|
||||
test('null- und undefined-Achsen werden als 0 gesendet', () => {
|
||||
const state = { x: 10, y: null, z: undefined, a: 0, b: -0.0 };
|
||||
expect(buildG92(state)).toBe('G92 X10.00 Y0.00 Z0.00 A0.00 B0.00 C0.00 E0.00');
|
||||
});
|
||||
|
||||
test('fillMissingWithZero=false lässt fehlende Achsen weg', () => {
|
||||
const state = { x: 10, y: 20 };
|
||||
expect(buildG92(state, { fillMissingWithZero: false })).toBe('G92 X10.00 Y20.00');
|
||||
});
|
||||
|
||||
test('decimals steuert die Nachkommastellen', () => {
|
||||
expect(buildG92({ x: 1.23456 }, { decimals: 3 }))
|
||||
.toBe('G92 X1.235 Y0.000 Z0.000 A0.000 B0.000 C0.000 E0.000');
|
||||
});
|
||||
|
||||
test('leerer State → alle Achsen 0', () => {
|
||||
expect(buildG92({})).toBe('G92 X0.00 Y0.00 Z0.00 A0.00 B0.00 C0.00 E0.00');
|
||||
expect(buildG92()).toBe('G92 X0.00 Y0.00 Z0.00 A0.00 B0.00 C0.00 E0.00');
|
||||
});
|
||||
|
||||
test('nicht-numerische Werte (NaN/Strings) werden als 0 behandelt', () => {
|
||||
expect(buildG92({ x: 'abc', y: NaN, z: 5 }))
|
||||
.toBe('G92 X0.00 Y0.00 Z5.00 A0.00 B0.00 C0.00 E0.00');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user