Roadmap planen

This commit is contained in:
chk
2026-06-14 16:23:18 +02:00
parent 2e622de801
commit 275ab083fa
13 changed files with 251 additions and 3339 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -1,595 +0,0 @@
{
"coordinateSystem": {
"handedness": "right",
"x": "right",
"y": "backward",
"z": "up"
},
"units": {
"length": "mm",
"rotation": "degree"
},
"vision_config": {
"MarkerType": "DICT_4X4_250",
"MarkerSize": 0.025
},
"renderingInfo": {
"width": 1280,
"height": 720,
"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":[300, -500,1450],
"cameraPosition_c":[600, -500,600],
"cameraTarget": [210, -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.70, 0.85, 1.0],
"backgroundStrength": 0.20,
"sunEnergy": 0.35,
"areaEnergy": 120,
"exposure": -1.5,
"lensDirt": true,
"lensDirtStrength": 0.08,
"dofEnabled": true,
"dofFStop": 11,
"arucoDust": false,
"arucoDustStrength": 0.00005,
"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.20, 0.20],
"roughness": 0.35,
"metallic": 0.0
},
"markerBlack": {
"baseColor": [0.04, 0.04, 0.04],
"roughness": 0.80,
"metallic": 0.0
}
},
"skeletonDefaults": {
"radius": 4,
"color": [0.85, 0.20, 0.20]
},
"markerDefaults": {
"size": 25,
"thickness": 1,
"color": [0.04, 0.04, 0.04]
}
},
"defaultPosition__": {
"x": 10,
"y": 4,
"z": 20,
"a": 10,
"b": 2,
"c": 9,
"e": 1
},
"defaultPosition": {
"x": 180,
"y": 86,
"z": -120,
"a": -60,
"b": 22,
"c": 91,
"e": 10
},
"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
},
"robot_test_poses": {
"sim04": {"x": 70, "y": 50,"z": -70,"a": 120,"b": 50,"c": 30,"e": 20},
"sim05": {"x": 180,"y": 86,"z": -120,"a": -60,"b": 22,"c": 91,"e": 10},
"sim06": {"x": 80, "y": 20, "z": 80, "a": -120, "b": 23, "c": 9, "e": 3},
"sim07": {"x": 30, "y": -2, "z": 95, "a": 20, "b": 23, "c": 9, "e": 9},
"sim08": {"x": 50, "y": -2, "z": 95, "a": 20, "b": 60, "c": 9, "e": 3},
"sim09": {"x": 60, "y": -2, "z": 95, "a": 200, "b": 60, "c": 9, "e": 8},
"sim09a": {"x": 60, "y": -2, "z": 95, "a": 200, "b": 60, "c": 9, "e": 8},
"sim09b": {"x": 60, "y": -2, "z": 95, "a": 200, "b": 60, "c": 9, "e": 8},
"sim010": {"x": 120, "y": 60, "z": -110, "a": 20, "b": 30, "c": 180, "e": 4},
"sim011": {"x": 50, "y": 4, "z": 176, "a": 20, "b": 60, "c": 9, "e": 5},
"sim012": {"x": 50, "y": 0, "z": 178, "a": 210, "b": 80, "c": 90, "e": 6}
},
"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": 4,
"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": {
"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.20, 0.20]
},
"markers":[
{"id":210,"position":[20, -20, 0.3], "normal":[0,0,1]},
{"id":211,"position":[250, -10, 0.3], "normal":[0,0,1]},
{"id":215,"position":[250, -90, 0.3], "normal":[0,0,1]},
{"id":214,"position":[350, -10, 0.3], "normal":[0,0,1]},
{"id":208,"position":[350, -90, 0.3], "normal":[0,0,1]},
{"id":206,"position":[650, -10, 0.3], "normal":[0,0,1]},
{"id":205,"position":[750, -90, 0.3], "normal":[0,0,1]},
{"id":207,"position":[750, -10, 0.3], "normal":[0,0,1]},
{"id":217,"position":[650, -90, 0.3], "normal":[0,0,1]},
{"id": 46, "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, "position": [344.23, -286.54, -27.3], "normal": [0, 0, 1], "spin": 90},
{"id": 48, "position": [688.69, -320.72, -27.3], "normal": [0, 0, 1], "spin": 90},
{"id": 49, "position": [1006.0, 158.33, -27.3], "normal": [0, 0, 1], "spin": 90},
{"id": 50, "position": [573.41, 211.86, -27.3], "normal": [0, 0, 1], "spin": 90},
{"id": 51, "position": [167.8, -172.08, -27.3], "normal": [0, 0, 1], "spin": 90},
{"id": 52, "position": [94.68, 208.66, -27.3], "normal": [0, 0, 1], "spin": 90},
{"id": 53, "position": [486.25, 212.24, -27.3], "normal": [0, 0, 1], "spin": 90},
{"id": 54, "position": [342.27, -330.59, -27.3], "normal": [0, 0, 1], "spin": 90},
{"id": 55, "position": [283.72, -262.58, -27.3], "normal": [0, 0, 1], "spin": 90},
{"id": 56, "position": [498.68, 168.67, -27.3], "normal": [0, 0, 1], "spin": 90},
{"id": 57, "position": [602.86, -364.05, -27.3], "normal": [0, 0, 1], "spin": 90},
{"id": 58, "position": [50.09, -218.11, -27.3], "normal": [0, 0, 1], "spin": 90},
{"id": 59, "position": [626.21, -278.75, -27.3], "normal": [0, 0, 1], "spin": 90},
{"id": 60, "position": [434.36, 283.81, -27.3], "normal": [0, 0, 1], "spin": 90},
{"id": 61, "position": [-22.42, 335.83, -27.3], "normal": [0, 0, 1], "spin": 90},
{"id": 62, "position": [404.7, -175.1, -27.3], "normal": [0, 0, 1], "spin": 90},
{"id": 63, "position": [777.4, -236.15, -27.3], "normal": [0, 0, 1], "spin": 90},
{"id": 64, "position": [-21.27, -188.23, -27.3], "normal": [0, 0, 1], "spin": 90},
{"id": 65, "position": [803.39, -297.37, -27.3], "normal": [0, 0, 1], "spin": 90},
{"id": 66, "position": [209.75, -363.23, -27.3], "normal": [0, 0, 1], "spin": 90},
{"id": 67, "position": [523.07, 267.04, -27.3], "normal": [0, 0, 1], "spin": 90},
{"id": 68, "position": [573.73, 170.64, -27.3], "normal": [0, 0, 1], "spin": 90},
{"id": 69, "position": [7.61, -281.21, -27.3], "normal": [0, 0, 1], "spin": 90},
{"id": 70, "position": [601.87, 300.33, -27.3], "normal": [0, 0, 1], "spin": 90},
{"id": 71, "position": [749.75, -284.01, -27.3], "normal": [0, 0, 1], "spin": 90},
{"id": 72, "position": [440.99, 194.32, -27.3], "normal": [0, 0, 1], "spin": 90},
{"id": 73, "position": [221.73, 333.11, -27.3], "normal": [0, 0, 1], "spin": 90},
{"id": 74, "position": [93.78, 144.5, -27.3], "normal": [0, 0, 1], "spin": 90},
{"id": 75, "position": [-25.7, 194.58, -27.3], "normal": [0, 0, 1], "spin": 90},
{"id": 76, "position": [685.21, 166.8, -27.3], "normal": [0, 0, 1], "spin": 90},
{"id": 77, "position": [18.19, 191.57, -27.3], "normal": [0, 0, 1], "spin": 90},
{"id": 78, "position": [823.11, -344.38, -27.3], "normal": [0, 0, 1], "spin": 90},
{"id": 79, "position": [312.3, -159.11, -27.3], "normal": [0, 0, 1], "spin": 90},
{"id": 80, "position": [863.59, -335.92, -27.3], "normal": [0, 0, 1], "spin": 90},
{"id": 81, "position": [132.14, 169.03, -27.3], "normal": [0, 0, 1], "spin": 90},
{"id": 82, "position": [219.16, 297.24, -27.3], "normal": [0, 0, 1], "spin": 90},
{"id": 83, "position": [44.16, 339.22, -27.3], "normal": [0, 0, 1], "spin": 90},
{"id": 84, "position": [407.49, 258.42, -27.3], "normal": [0, 0, 1], "spin": 90},
{"id": 85, "position": [504.58, -312.75, -27.3], "normal": [0, 0, 1], "spin": 90},
{"id": 86, "position": [362.89, 292.01, -27.3], "normal": [0, 0, 1], "spin": 90},
{"id": 87, "position": [943.63, -245.76, -27.3], "normal": [0, 0, 1], "spin": 90},
{"id": 88, "position": [765.87, 316.04, -27.3], "normal": [0, 0, 1], "spin": 90},
{"id": 89, "position": [988.02, -369.14, -27.3], "normal": [0, 0, 1], "spin": 90},
{"id": 90, "position": [643.17, 316.43, -27.3], "normal": [0, 0, 1], "spin": 90},
{"id": 91, "position": [723.35, 328.05, -27.3], "normal": [0, 0, 1], "spin": 90},
{"id": 92, "position": [645.09, -184.84, -27.3], "normal": [0, 0, 1], "spin": 90},
{"id": 93, "position": [934.88, 143.6, -27.3], "normal": [0, 0, 1], "spin": 90},
{"id": 94, "position": [875.7, 173.65, -27.3], "normal": [0, 0, 1], "spin": 90},
{"id": 95, "position": [186.04, -274.07, -27.3], "normal": [0, 0, 1], "spin": 90},
{"id": 96, "position": [369.77, -186.49, -27.3], "normal": [0, 0, 1], "spin": 90},
{"id": 97, "position": [304.35, -359.67, -27.3], "normal": [0, 0, 1], "spin": 90},
{"id": 98, "position": [575.27, 315.06, -27.3], "normal": [0, 0, 1], "spin": 90},
{"id": 99, "position": [959.16, -321.55, -27.3], "normal": [0, 0, 1], "spin": 90},
{"id": 100, "position": [803.25, 172.36, -27.3], "normal": [0, 0, 1], "spin": 90},
{"id": 101, "position": [117.7, 298.66, -27.3], "normal": [0, 0, 1], "spin": 90},
{"id": 102, "position": [649.69, -223.0, -27.3], "normal": [0, 0, 1], "spin": 90},
{"id": 103, "position": [105.71, -187.71, -27.3], "normal": [0, 0, 1], "spin": 90},
{"id": 104, "position": [826.71, 239.16, -27.3], "normal": [0, 0, 1], "spin": 90},
{"id": 105, "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"
},
"skeleton": {
"from": [0, 108, 45],
"to": [110, 108, 45],
"radius": 4,
"color": [0.20, 0.80, 0.20]
},
"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"
},
"skeleton": {
"from": [0, 0, 0],
"to": [0, -250, 0],
"radius": 4,
"color": [0.20, 0.20, 0.90]
},
"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"
},
"skeleton": {
"from": [0, 0, 0],
"to": [90, 0, 0],
"radius": 4,
"color": [0.90, 0.20, 0.20]
},
"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"
},
"skeleton": {
"from": [0, 0, 0],
"to": [0, -250, 0],
"radius": 4,
"color": [0.95, 0.85, 0.20]
},
"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"
},
"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"
},
"skeleton": {
"from": [-50, -35, 0],
"to": [50, -35, 0],
"radius": 7,
"color": [0.95, 0.20, 0.20]
}
},
"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"
},
"skeleton": {
"from": [0, 0,0],
"to": [0, -60, 0],
"radius": 4,
"color": [0.20, 0.80, 0.20]
},
"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.40], "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"
},
"skeleton": {
"from": [0, 0,0],
"to": [0, -60, 0],
"radius": 4,
"color": [0.20, 0.80, 0.20]
},
"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.40], "spin": -27}
],
"model": [
{
"stlFile": "surfaces/Finger.stl",
"originOfModel": [-24,0,9.1],
"rotationOfModelDegree": [90, 90,0],
"material": "defaultPlastic"
}
]
}
}
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 MiB

View File

@@ -8,374 +8,322 @@
## Was ist Homing?
Homing = der Roboter weiss **nicht**, wo er ist.
Homing = der Roboter weiss **nicht**, wo er ist.
Die Kameras schauen auf das Board + die ArUco-Marker am Roboter und berechnen
daraus die vollständige Pose aller Gelenke — ohne mechanische Endschalter.
Homing läuft bei **jedem** Einschalten ab, also muss es schnell und robust sein.
Homing läuft bei **jedem** Einschalten ab: schnell, robust, vollautomatisch.
Kalibrierung hingegen läuft nur nach mechanischen Änderungen (≈ einmalig).
---
## Voraussetzungen (Kalibrierung muss abgeschlossen sein)
## Kinematik-Kette (aus `robot.json → links`)
| Was | Wo gespeichert | Status |
|-----|----------------|--------|
| Kamera-Intrinsik (NPZ) | `data/calibration/camX_calibration.npz` | ✅ |
| Board-Marker-Positionen | `robot.json → links.Board.markers[]` | ✅ |
| X-Achsen-Richtung | `robot.json → links.*.position` (rotiert) | ✅ |
| **Arm1-Gelenkursprung Y/Z** | `robot.json → links.Arm1.jointToParent.origin[1,2]` | 🔶 in Arbeit |
| Arm-Marker-Zuordnung | `robot.json → links.Arm1/Ellbow/Arm2/Hand.markers[]` | ❌ offen |
```
Board (ROOT, fest) ← Referenz aller Kameras
├── Base linear x axis=[1,0,0] ← Slider-Position
├── Arm1 revolute y axis=[-1,0,0] ← Schultergelenk
├── Ellbow revolute z axis=[-1,0,0] ← Ellbogen
├── Arm2 revolute a axis=[0,-1,0] ← Unterarm-Drehung
├── Hand revolute b axis=[1,0,0] ← Handgelenk
├── Palm revolute c axis=[0,-1,0] ← Handfläche
└── FingerA/B linear e axis=±[1,0,0] ← Greifer (symmetrisch)
```
> **Schlüssel-Erkenntnis:** Sobald `Arm1.jointToParent.origin` korrekt gesetzt ist (aus
> der Y-Achsen-Kalibrierung), ist die gesamte Kinematikkette in `robot.json` geometrisch
> definiert. Dann kann Homing starten.
**Resultat-State:** `{ x_mm, y_deg, z_deg, a_deg, b_deg, c_deg, e_mm }`
---
## Kinematik-Kette (aus `robot.json`)
## Voraussetzungen (Kalibrierung)
```
Board (ROOT, fest)
├── Base linear variable=x axis=[1,0,0] origin=[0,0,16]
│ → Slider-Position entlang Board-X
├── Arm1 revolute variable=y axis=[-1,0,0] origin=[110, 108*, 45*]
│ → Schultergelenk (heben/senken) *aus Y-Achsen-Kalib.
├── Ellbow revolute variable=z axis=[-1,0,0] origin=[0,-250,0]
│ → Ellbogen (relativ zu Arm1-Ende)
├── Arm2 revolute variable=a axis=[0,-1,0] origin=[90,0,0]
│ → Unterarm-Drehung
├── Hand revolute variable=b axis=[1,0,0] origin=[0,-250,0]
│ → Handgelenk
├── Palm revolute variable=c axis=[0,-1,0] origin=[0,0,0]
│ → Handflächen-Rotation
└── FingerA/B linear variable=e axis=±[1,0,0] origin=[±4,-35,0]
→ Greifer (symmetrisch)
```
| Was | Mechanismus | Status |
|-----|-------------|--------|
| Kamera-Intrinsik (NPZ) | `calibration.html` → Tab Camera NPZ | ✅ fertig |
| Board-Marker-Positionen | `calibration.html` → Tab Board | ✅ fertig |
| X-Achsen-Richtung | `calibration.html` → Tab Robot X-Axis | ✅ fertig |
| **Arm1 Joint-Origin Y/Z** | `calibration.html` → Tab Arm1 → Button „Joint-Origin Y/Z übernehmen" | ✅ **Button vorhanden, ausführbar** |
| Arm-Marker in robot.json | Manuell eintragen (links.Arm1/Ellbow/Arm2/Hand.markers) | 🔶 Nutzer trägt ein |
**Gelenkvariablen:** `x` (mm), `y z a b c` (Grad), `e` (mm)
> **Kalibrierung gilt als abgeschlossen** sobald der Arm1-Button geklickt und
> die Arm-Marker eingetragen sind.
---
## Homing-Ablauf (Schritt für Schritt)
## robot.json Ladestrategie
### Schritt 1 — Snapshot aufnehmen
**Was:** Alle Kameras gleichzeitig ein Foto.
**Script:** — (direkt via Webcam-API)
**UI-Aktion:** Button `[Foto aufnehmen]`
**Ausgabe:**
- `cam0.jpg`, `cam1.jpg`, `cam2.jpg` im aktuellen Run-Verzeichnis
- **Snapshots-Sektion:** Kamerabilder erscheinen
---
### Schritt 2 — ArUco-Marker erkennen
**Was:** Pixel-Koordinaten aller sichtbaren Marker in jedem Kamerabild.
**Script:** `1_detect_aruco_observations.py`
```bash
python 1_detect_aruco_observations.py \
-i cam0.jpg cam1.jpg cam2.jpg \
-robot robot.json \
-outDir data/homing/TIMESTAMP/
### Aktuell: lokale Datei
```
ROBOT_JSON = process.env.ROBOT_JSON || 'scripts/robot_1781069752019.json'
```
**Output-Dateien:**
- `cam0_aruco_detection.json` — Marker-IDs + Pixel-Ecken + Kamera-Pose-Kandidaten
### Geplant: vom Driver per API
Der Driver-Service (ROBOT_URL) kennt die aktuelle Roboter-Konfiguration.
Lademechanismus (bereits implementiertes Muster aus `ROBOT_URL`/`BODYTRACKER_URL`):
**Snapshot CSV:** Tabelle: MarkerID | Kamera | px-Koordinaten | confidence
**Fehlerfälle:**
- Marker verdeckt → weniger Marker in CSV sichtbar
- Schlechte Beleuchtung → confidence niedrig
---
### Schritt 3 — Kamera-Posen schätzen
**Was:** Aus den Board-Markern (fix im Raum) die extrinsische Lage jeder Kamera
berechnen.
**Script:** `2_estimate_camera_from_observations.py`
```bash
python 2_estimate_camera_from_observations.py \
-i data/homing/TIMESTAMP/ \
-robot robot.json \
-outDir data/homing/TIMESTAMP/
```
GET ROBOT_URL/api/robot/config → robot.json Inhalt
```
**Output-Dateien:**
- `cam0_camera_pose.json` — 4×4 Transformationsmatrix Kamera→Welt
**Analysis & Reasoning:** Reprojektions-Fehler pro Kamera (sollte < 3 px)
**Fehlerfälle:**
- Zu wenig Board-Marker sichtbar (< 3) → Kamera-Pose nicht berechenbar
- Hoher Reprojektions-Fehler → Kalibrierung möglicherweise veraltet
---
### Schritt 3b — 3D-Marker-Positionen triangulieren
**Was:** Aus den Kamera-Posen und den Pixel-Koordinaten die echten 3D-Positionen
aller Marker berechnen (inkl. Arm-Marker).
**Script:** `3b_corner_marker_poses.py`
```bash
python 3b_corner_marker_poses.py \
-i data/homing/TIMESTAMP/ \
-robot robot.json \
--outDir data/homing/TIMESTAMP/
```
**Output-Dateien:**
- `aruco_marker_poses.json` — für jeden Marker: `position_mm`, `normal`, `corners_m`, `link`
**Snapshot CSV:** Vollständige Marker-Tabelle mit triangulierten 3D-Positionen
**Fehlerfälle:**
- Marker nur in 1 Kamera → keine Triangulation möglich (braucht ≥ 2)
---
### Schritt 4 — Gelenkwinkel bestimmen
Zwei Methoden — **4b** ist sequenziell und robuster:
#### Methode A — Vollständige State-Schätzung (schnell)
**Script:** `4_robotState_estimation_v6.py`
```bash
python 4_robotState_estimation_v6.py \
aruco_marker_poses.json \
--robot robot.json \
--output homing_state.json
```
Schätzt alle Gelenke in einem Durchlauf mit geometrischen Gleichungen
(Kabsch-Fit, Projektions-Residuen, 2D-Winkel).
#### Methode B — Sequenziell Gelenk für Gelenk (robuster)
**Script:** `4b_revolute_angle.py` — einmal pro Gelenk, von root nach tip:
```bash
# Slider-Position (x) aus Board-Markern bekannt → manuell oder aus 4a
python 4b_revolute_angle.py --robot robot.json --aruco aruco_marker_poses.json \
--link Arm1 --x-mm 180 --output state_arm1.json
python 4b_revolute_angle.py --robot robot.json --aruco aruco_marker_poses.json \
--link Ellbow --from-state state_arm1.json --output state_ellbow.json
python 4b_revolute_angle.py --robot robot.json --aruco aruco_marker_poses.json \
--link Arm2 --from-state state_ellbow.json --output state_arm2.json
python 4b_revolute_angle.py --robot robot.json --aruco aruco_marker_poses.json \
--link Hand --from-state state_arm2.json --output state_hand.json
```
Jedes `--from-state` enthält den akkumulierten Gelenk-State aus allen
vorherigen Schritten.
**Warum sequenziell?** Arm2-Winkel kann nur korrekt berechnet werden wenn
Arm1 und Ellbow bereits bekannt sind (der Weltachsenvektor des Arm2-Joints
ändert sich mit den vorherigen Gelenkwinkeln).
**Output-JSON-Struktur (pro Schritt):**
```json
{
"link": "Arm1",
"joint": "y",
"mean_angle_deg": 23.4,
"circular_std_deg": 0.8,
"num_pairs": 6,
"joint_origin_world_mm": [290, 108, 45],
"joint_axis_world": [-1, 0, 0],
"accumulated_state": { "x": 180, "y": 23.4, "z": null, "a": null, ... }
**Implementierung (Backend `server.js`):**
```javascript
async function loadRobotConfig() {
if (ROBOT_URL) {
// Vom Driver holen
const res = await fetch(new URL('/api/robot/config', ROBOT_URL));
return res.json();
}
// Fallback: lokale Datei
return JSON.parse(await fs.readFile(ROBOT_JSON, 'utf8'));
}
```
**Analysis & Reasoning:** Gelenk-für-Gelenk-Ergebnis als JSON-Baum
**Konsequenz für Homing:** Das Homing-Script bekommt robot.json als
temporäre Datei (bereits vorhandenes Muster: `ROBOT_JSON` als Pfad an Python).
Falls ROBOT_URL konfiguriert: zuerst fetch → temp-Datei schreiben → Script aufrufen.
**Fehlerfälle:**
- `circular_std_deg` > 5° → Marker-Konfiguration fragwürdig, Messung wiederholen
- `num_pairs` < 2 → Arm-Marker nicht genug sichtbar
**Priorität:** Kann nach dem restlichen Homing implementiert werden.
Solange ROBOT_URL nicht konfiguriert, läuft alles mit der lokalen Datei.
---
### Schritt 5 (optional) — Kamera-Z verfeinern
## X-Position (Slider) Bestimmung
**Was:** Kamera-Höhe (Z) ist aus Board-Markern schlecht bestimmt (Board ist flach).
Wenn Ellbow-Winkel bekannt → FK-Vorhersage als Z-Referenz nutzen.
Die Slider-Position `x` wird **nicht** manuell eingegeben, sondern aus den
triangulierten Marker-Positionen berechnet (nach Schritt 3b).
**Script:** `5_camera_z_refine.py`
**Ansatz:** Die absolute X-Position eines bekannten Arm-Markers im
Board-Koordinatensystem enthält direkt die Slider-Information —
alle anderen Gelenke sind rotatorisch und verschieben den Marker nicht
entlang der X-Achse des Boards.
```bash
python 5_camera_z_refine.py \
--angle state_ellbow.json \
--robot robot.json \
--aruco aruco_marker_poses.json \
-pose cam0_camera_pose.json \
-pose cam1_camera_pose.json \
--outDir data/homing/TIMESTAMP/
Alternativ: Schwerpunkt der Board-nahen A0-Marker projiziert auf die X-Achse
(robust, braucht keine Arm-Marker).
**In `4b_revolute_angle.py`:** `--x-mm` wird aus `aruco_marker_poses.json`
berechnet und als erstes Argument übergeben. Alle weiteren 4b-Aufrufe
nutzen `--from-state` des vorherigen Schritts.
---
## Homing-Ablauf (Script-Kette)
```
[Foto] → [1_detect] → [2_camera] → [3b_poses]
[4b Arm1]
[4b Ellbow]
[4b Arm2]
[4b Hand]
State-JSON
{x,y,z,a,b,c,e}
POST ROBOT_URL/api/state
```
**Output:** Korrigierte `*_camera_pose_v8.json` + `z_correction.json`
**Wann nötig:** Wenn Residuen in Schritt 4 systematisch in Z-Richtung driften.
**Strategie:** `4b_revolute_angle.py` sequenziell, Link für Link von root nach tip.
X-Position (`--x-mm`) wird aus den triangulierten Board-Marker-Positionen bestimmt.
---
### Schritt 6 — Ergebnis senden
## Implementierungsplan: Homing-UI
**Was:** Vollständigen Joint-State an Roboter-Controller übermitteln.
### Phase 1 — Backend-Route `POST /api/homing/run`
**UI-Aktion:** Button `[An Roboter senden]`
**Datei:** `server/server.js` (neue Route) + `server/homingOrchestrator.js` (neue Datei)
**Body:** `{ x, y, z, a, b, c, e }`
`POST ROBOT_URL/api/state` (oder Motion-Controller-Protokoll)
**Ablauf als SSE-Stream:**
**Result Raw JSON:**
```json
{
"status": "ok",
"timestamp": "2026-06-13T19:00:00",
"state": { "x": 180.0, "y": 23.4, "z": -12.1, "a": 5.0, "b": 0.0, "c": 0.0, "e": 0.0 },
"confidence": { "y_std_deg": 0.8, "z_std_deg": 1.2 }
```javascript
// server/homingOrchestrator.js
export async function runHoming({ robotJsonPath, boardDataDir, send, options }) {
// 1. Snapshot (Webcam-API)
send({ type: 'step', step: 1, text: 'Foto aufnehmen …' });
const runDir = await takeSnapshot(); // WEBCAM_URL
// 2. Marker erkennen
send({ type: 'step', step: 2, text: 'Marker erkennen …' });
await runScript(['1_detect_aruco_observations.py',
'-i', ...camImages, '-robot', robotJsonPath, '-outDir', runDir], send);
// 3. Kamera-Posen
send({ type: 'step', step: 3, text: 'Kamera-Posen schätzen …' });
await runScript(['2_estimate_camera_from_observations.py',
'-i', runDir, '-robot', robotJsonPath, '-outDir', runDir], send);
// 3b. 3D-Triangulation
send({ type: 'step', step: 4, text: '3D-Positionen triangulieren …' });
await runScript(['3b_corner_marker_poses.py',
'-i', runDir, '-robot', robotJsonPath, '--outDir', runDir], send);
// 4. X-Position aus triangulierten Board-Markern bestimmen
const xMm = estimateXFromMarkers(arucoJson); // aus position_mm der A0-Marker
// 4b. Gelenkwinkel sequenziell, Link für Link
const links = ['Arm1', 'Ellbow', 'Arm2', 'Hand'];
let fromState = null;
for (const link of links) {
send({ type: 'step', text: `Gelenkwinkel ${link}` });
const args = ['4b_revolute_angle.py',
'--robot', robotJsonPath, '--aruco', arucoJson,
'--link', link,
'--output', path.join(runDir, `state_${link}.json`),
];
if (fromState) args.push('--from-state', fromState);
else args.push('--x-mm', String(xMm));
fromState = await runScript(args, send);
}
// Ergebnis
const finalState = JSON.parse(fs.readFileSync(fromState));
send({ type: 'done', state: finalState.accumulated_state, runDir });
}
```
**Result Tree View:** Gelenk-Werte als lesbare Tabelle
**Route:**
```javascript
app.post('/api/homing/run', async (req, res) => {
// SSE-Header
res.setHeader('Content-Type', 'text/event-stream');
const send = (data) => res.write(`data: ${JSON.stringify(data)}\n\n`);
await runHoming({ robotJsonPath: ROBOT_JSON, boardDataDir, send });
res.end();
});
---
## UI-Seitenstruktur (analog index.html)
```
┌─────────────────────────────────────────┐
│ AKTIONEN │
│ [Foto aufnehmen] [Homing berechnen] │
│ [An Roboter senden] │
├─────────────────────────────────────────┤
│ AUSGABE / LOG │
│ Schritt-für-Schritt Log aller Scripts │
├─────────────────────────────────────────┤
│ ANALYSIS & REASONING │
│ Kamera-Posen, Reprojektionsfehler, │
│ Gelenk-Schätzungen je Schritt als JSON │
├──────────────────┬──────────────────────┤
│ RESULT RAW JSON │ RESULT TREE │
│ Vollst. State-JSON │ Gelenk-Tabelle │
├─────────────────────────────────────────┤
│ SNAPSHOT CSV │
│ Tabelle aller Marker: ID, Position, │
│ Link, Residual, num_cameras │
├─────────────────────────────────────────┤
│ SNAPSHOTS (Bilder) │
│ Annotierte Kamerabilder mit Markern │
└─────────────────────────────────────────┘
app.post('/api/homing/send-state', async (req, res) => {
// Sendet { x, y, z, a, b, c, e } an ROBOT_URL/api/state
});
```
---
## Offene Punkte (Voraussetzungen für Homing)
### Phase 2 — Frontend `public/homing.html`
### 🔶 A — Arm-Marker in robot.json eintragen
Neue Seite (zugänglich von `index.html` via Link-Button, wie `calibration.html`).
Die Arm-Links haben noch **keine Marker** in `robot.json` (links.Arm1/Ellbow/Arm2/Hand.markers).
**Sektionen (identisches Muster wie `index.html`):**
Mögliche Quellen:
- Aus der Y-Achsen-Kalibrierung: Marker 197, 218, 219 wurden als rotierend erkannt
→ diese können mit relativen Positionen in den jeweiligen Links eingetragen werden
- Mit `4b_revolute_angle.py` lässt sich pro Link prüfen, welche Marker
"zu diesem Link gehören" (paarweise Baseline-Methode)
```
┌──────────────────────────────────────────────────────┐
AKTIONEN │
│ [📷 Foto & Homing berechnen] │
│ [✅ An Roboter senden] (disabled bis Ergebnis) │
│ Status-Badge: ○ Warte ● Läuft ✓ Fertig ✗ Fehler │
├──────────────────────────────────────────────────────┤
│ AUSGABE / LOG │
│ Schritt-für-Schritt Log aller Scripts (SSE-Stream) │
│ Fortschritt: ──── Schritt 3/6 ──── │
├──────────────────────────────────────────────────────┤
│ ANALYSIS & REASONING │
│ Zwischenergebnisse je Script als JSON │
│ { camera_reprojection_px, arm1_std_deg, … } │
├──────────────────┬───────────────────────────────────┤
│ RESULT RAW JSON │ RESULT TREE VIEW │
│ { │ x (Slider): 180.0 mm │
│ "x": 180.0, │ y (Arm1): +23.4° │
│ "y": 23.4, │ z (Ellbow): -12.1° │
│ ... │ a (Arm2): +5.0° │
│ } │ b (Hand): 0.0° │
│ │ c (Palm): 0.0° │
│ │ e (Greifer): 0.0 mm │
├──────────────────┴───────────────────────────────────┤
│ SNAPSHOT CSV (Marker-Tabelle) │
│ ID │ Link │ x mm │ y mm │ z mm │ Residual │ │
│ 218 │ Arm2 │ 229.1 │ 118.5 │ 48.3 │ 2.1 mm │ │
├──────────────────────────────────────────────────────┤
│ SNAPSHOTS (annotierte Kamerabilder) │
│ [cam0] [cam1] [cam2] │
└──────────────────────────────────────────────────────┘
```
**Aktion:** Für jedes Arm-Segment mind. 2 Marker bestimmen und in robot.json eintragen.
**Schlüssel-Implementierungsdetails:**
### 🔶 B — Arm1.jointToParent.origin[Y, Z] finalisieren
```javascript
// homing.js (client)
Aus der Arm1-Y-Achsen-Kalibrierung (calibration_arm.html):
- Berechneter Wert: Y ≈ 115.6 mm, Z ≈ 50.3 mm
- Aktueller Wert in robot.json: origin = [110, 108, 45]
- **Aktion:** Button „Joint-Origin Y/Z übernehmen" klicken → schreibt in robot.json
// SSE-Stream vom Backend empfangen
async function runHoming() {
const response = await fetch('/api/homing/run', { method: 'POST' });
await readSseStream(response, appendLog, (evt) => {
if (evt.type === 'step') { updateProgress(evt); }
if (evt.type === 'analysis') { showAnalysis(evt.data); }
if (evt.type === 'done') {
showResult(evt.state);
enableSendButton(evt.state);
}
});
}
### 🔶 C — X-Position (Slider) im Homing
Die Slider-Position `x` ist für `4b_revolute_angle.py --x-mm` nötig.
Optionen:
1. Mechanischer Anschlag / Encoder → immer bekannt
2. Aus Board-Markern schätzen (funktioniert wenn A0-Marker sichtbar)
3. Manuell eingeben (Fallback)
### 🔶 D — Homing-Seite implementieren
Eine neue `homing.html`-Seite (oder Tab in `calibration.html`) die:
- Die Script-Kette 1 → 2 → 3b → 4b(je Link) → (5) als SSE-Stream orchestriert
- Alle Ausgabe-Sektionen wie in index.html befüllt
- „An Roboter senden" Button mit finalem State-JSON verdrahtet
// Ergebnis an Roboter senden
async function sendToRobot(state) {
await fetch('/api/homing/send-state', {
method: 'POST',
body: JSON.stringify({ state }),
});
}
```
---
## Script-Abhängigkeitsgraph
### Phase 3 — robot.json via Driver-API
**Voraussetzung:** ROBOT_URL ist konfiguriert und der Driver hat `GET /api/robot/config`.
**Implementierung in `server.js`:**
```javascript
// Beim Start oder on-demand: robot.json vom Driver laden
async function fetchRobotConfig() {
if (!ROBOT_URL) return; // lokale Datei reicht
const res = await fetch(new URL('/api/robot/config', ROBOT_URL));
if (!res.ok) return; // Fallback auf lokale Datei
const data = await res.json();
// Temporär in data/robot/robot_live.json cachen
await fs.writeFile(ROBOT_JSON_LIVE, JSON.stringify(data, null, 2));
}
```
**Auswirkung:** Nur `ROBOT_JSON` Variable ändern — alle Scripts bekommen
automatisch die aktuelle Konfiguration.
---
## Reihenfolge der Implementierung
```
Kamera-Snapshot
[1] detect_aruco → cam*_aruco_detection.json
[2] estimate_camera → cam*_camera_pose.json
│ (aus Board-Markern)
[3b] corner_marker_poses → aruco_marker_poses.json
│ (alle Marker trianguliert)
├──────────────────────────────────────┐
▼ ▼
[4b] revolute Arm1 [4] state_v6 (alternativ)
[4b] revolute Ellbow ──► [5] camera_z_refine (optional, nutzt Ellbow)
[4b] revolute Arm2
[4b] revolute Hand
State-JSON {x, y, z, a, b, c, e}
→ Roboter-Controller
[Jetzt] Arm-Marker eintragen (Nutzer)
→ Arm1 Joint-Origin Button klicken (bereits ausführbar)
[1] POST /api/homing/run + homingOrchestrator.js
→ Script-Kette als SSE orchestrieren
→ Minimale UI: nur Log + Raw JSON
[2] public/homing.html
→ Vollständige UI mit allen Sektionen
→ Link von index.html
[3] POST /api/homing/send-state
→ ROBOT_URL/api/state aufrufen (analog zu robotActions.js)
[4] robot.json via Driver-API (wenn ROBOT_URL verfügbar)
→ Nur wenn Driver den Endpunkt implementiert
```
---
## Status-Tabelle
| Schritt | Script | Status | Blockiert durch |
|---------|--------|--------|-----------------|
| 1 Snapshot | Webcam-API | ✅ vorhanden | — |
| 2 Marker erkennen | `1_detect_aruco.py` | ✅ vorhanden | — |
| 3 Kamera-Pose | `2_estimate_camera.py` | ✅ vorhanden | — |
| 3b Triangulation | `3b_corner_marker_poses.py` | ✅ vorhanden | — |
| 4 State-Schätzung | `4_robotState_estimation_v6.py` | ✅ vorhanden | Arm-Marker fehlen |
| 4b Sequenziell | `4b_revolute_angle.py` | ✅ vorhanden | Arm-Marker fehlen |
| 5 Z-Verfeinern | `5_camera_z_refine.py` | ✅ vorhanden | Ellbow-Marker fehlen |
| **Arm1-Origin** | calibration_arm.html | 🔶 bereit | Joint-Origin Y/Z übernehmen |
| **Arm-Marker** | robot.json | ❌ fehlen | manuelle Zuordnung |
| **Homing-UI** | homing.html | ❌ fehlt | erst nach Markern sinnvoll |
| Schritt | Was | Status |
|---------|-----|--------|
| Scripts 1, 2, 3b, 4b | Homing-Scripts | ✅ vorhanden |
| Kalibrierung | Kamera, Board, X-Achse | ✅ fertig |
| Arm1 Joint-Origin | Button in calibration_arm.html | ✅ **ausführbar** |
| Arm-Marker | robot.json links.Arm1/… .markers | 🔶 Nutzer trägt ein |
| `/api/homing/run` | Backend-Orchestrierung | ❌ zu implementieren |
| `homing.html` | Frontend-UI | ❌ zu implementieren |
| `/api/homing/send-state` | State an Roboter | ❌ zu implementieren |
| robot.json via API | Driver-Integration | ⏳ nach allem anderen |