foodforthought-cli 0.2.1__py3-none-any.whl → 0.2.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. ate/__init__.py +1 -1
  2. ate/bridge_server.py +622 -0
  3. ate/cli.py +2625 -242
  4. ate/compatibility.py +580 -0
  5. ate/generators/__init__.py +19 -0
  6. ate/generators/docker_generator.py +461 -0
  7. ate/generators/hardware_config.py +469 -0
  8. ate/generators/ros2_generator.py +617 -0
  9. ate/generators/skill_generator.py +783 -0
  10. ate/marketplace.py +524 -0
  11. ate/mcp_server.py +1341 -107
  12. ate/primitives.py +1016 -0
  13. ate/robot_setup.py +2222 -0
  14. ate/skill_schema.py +537 -0
  15. ate/telemetry/__init__.py +33 -0
  16. ate/telemetry/cli.py +455 -0
  17. ate/telemetry/collector.py +444 -0
  18. ate/telemetry/context.py +318 -0
  19. ate/telemetry/fleet_agent.py +419 -0
  20. ate/telemetry/formats/__init__.py +18 -0
  21. ate/telemetry/formats/hdf5_serializer.py +503 -0
  22. ate/telemetry/formats/mcap_serializer.py +457 -0
  23. ate/telemetry/types.py +334 -0
  24. foodforthought_cli-0.2.3.dist-info/METADATA +300 -0
  25. foodforthought_cli-0.2.3.dist-info/RECORD +44 -0
  26. foodforthought_cli-0.2.3.dist-info/top_level.txt +6 -0
  27. mechdog_labeled/__init__.py +3 -0
  28. mechdog_labeled/primitives.py +113 -0
  29. mechdog_labeled/servo_map.py +209 -0
  30. mechdog_output/__init__.py +3 -0
  31. mechdog_output/primitives.py +59 -0
  32. mechdog_output/servo_map.py +203 -0
  33. test_autodetect/__init__.py +3 -0
  34. test_autodetect/primitives.py +113 -0
  35. test_autodetect/servo_map.py +209 -0
  36. test_full_auto/__init__.py +3 -0
  37. test_full_auto/primitives.py +113 -0
  38. test_full_auto/servo_map.py +209 -0
  39. test_smart_detect/__init__.py +3 -0
  40. test_smart_detect/primitives.py +113 -0
  41. test_smart_detect/servo_map.py +209 -0
  42. foodforthought_cli-0.2.1.dist-info/METADATA +0 -151
  43. foodforthought_cli-0.2.1.dist-info/RECORD +0 -9
  44. foodforthought_cli-0.2.1.dist-info/top_level.txt +0 -1
  45. {foodforthought_cli-0.2.1.dist-info → foodforthought_cli-0.2.3.dist-info}/WHEEL +0 -0
  46. {foodforthought_cli-0.2.1.dist-info → foodforthought_cli-0.2.3.dist-info}/entry_points.txt +0 -0
ate/primitives.py ADDED
@@ -0,0 +1,1016 @@
1
+ """
2
+ Primitive Registry - Catalog of all available robot primitives.
3
+
4
+ Primitives are the atomic building blocks of robot skills. This module
5
+ provides a registry of all available primitives with their signatures,
6
+ hardware requirements, and documentation.
7
+ """
8
+
9
+ from dataclasses import dataclass, field
10
+ from enum import Enum
11
+ from typing import Any, Dict, List, Optional, Set
12
+
13
+
14
+ class PrimitiveCategory(str, Enum):
15
+ """Categories of primitives."""
16
+ MOTION = "motion" # Movement primitives
17
+ GRIPPER = "gripper" # Gripper/end-effector actions
18
+ SENSING = "sensing" # Sensor reading primitives
19
+ WAIT = "wait" # Timing and condition waits
20
+ CONTROL = "control" # Control flow primitives
21
+ COMMUNICATION = "communication" # Inter-system communication
22
+
23
+
24
+ @dataclass
25
+ class ParameterDefinition:
26
+ """Definition of a primitive parameter."""
27
+ type: str
28
+ description: str
29
+ required: bool = True
30
+ default: Optional[Any] = None
31
+ range: Optional[tuple] = None
32
+ units: Optional[str] = None
33
+
34
+ def to_dict(self) -> Dict[str, Any]:
35
+ d = {"type": self.type, "description": self.description}
36
+ if not self.required:
37
+ d["required"] = False
38
+ if self.default is not None:
39
+ d["default"] = self.default
40
+ if self.range:
41
+ d["range"] = list(self.range)
42
+ if self.units:
43
+ d["units"] = self.units
44
+ return d
45
+
46
+
47
+ @dataclass
48
+ class PrimitiveDefinition:
49
+ """Complete definition of a primitive."""
50
+ name: str
51
+ description: str
52
+ category: PrimitiveCategory
53
+ parameters: Dict[str, ParameterDefinition]
54
+ returns: str
55
+ hardware: List[str]
56
+ blocking: bool = True # Whether this primitive blocks execution
57
+ timeout_default: Optional[float] = None
58
+ example: Optional[str] = None
59
+
60
+ def validate_parameters(self, provided: Dict[str, Any]) -> List[str]:
61
+ """Validate provided parameters against this primitive's signature."""
62
+ errors = []
63
+
64
+ # Check required parameters
65
+ for name, param_def in self.parameters.items():
66
+ if param_def.required and name not in provided:
67
+ errors.append(f"Missing required parameter: {name}")
68
+
69
+ # Check for unknown parameters
70
+ for name in provided:
71
+ if name not in self.parameters:
72
+ errors.append(f"Unknown parameter: {name}")
73
+
74
+ # Validate types and ranges
75
+ for name, value in provided.items():
76
+ if name not in self.parameters:
77
+ continue
78
+ param_def = self.parameters[name]
79
+
80
+ # Range validation
81
+ if param_def.range and isinstance(value, (int, float)):
82
+ min_val, max_val = param_def.range
83
+ if value < min_val or value > max_val:
84
+ errors.append(
85
+ f"Parameter '{name}' value {value} out of range "
86
+ f"[{min_val}, {max_val}]"
87
+ )
88
+
89
+ return errors
90
+
91
+ def get_signature(self) -> str:
92
+ """Get a function signature string for this primitive."""
93
+ params = []
94
+ for name, param_def in self.parameters.items():
95
+ if param_def.required:
96
+ params.append(f"{name}: {param_def.type}")
97
+ else:
98
+ default = repr(param_def.default)
99
+ params.append(f"{name}: {param_def.type} = {default}")
100
+ return f"{self.name}({', '.join(params)}) -> {self.returns}"
101
+
102
+ def to_dict(self) -> Dict[str, Any]:
103
+ return {
104
+ "description": self.description,
105
+ "category": self.category.value,
106
+ "parameters": {
107
+ name: param.to_dict()
108
+ for name, param in self.parameters.items()
109
+ },
110
+ "returns": self.returns,
111
+ "hardware": self.hardware,
112
+ "blocking": self.blocking,
113
+ "timeout_default": self.timeout_default,
114
+ "example": self.example,
115
+ }
116
+
117
+
118
+ # =============================================================================
119
+ # PRIMITIVE REGISTRY
120
+ # =============================================================================
121
+ # All available primitives organized by category
122
+
123
+ PRIMITIVE_REGISTRY: Dict[str, PrimitiveDefinition] = {}
124
+
125
+
126
+ def register_primitive(prim: PrimitiveDefinition) -> PrimitiveDefinition:
127
+ """Register a primitive in the global registry."""
128
+ PRIMITIVE_REGISTRY[prim.name] = prim
129
+ return prim
130
+
131
+
132
+ # -----------------------------------------------------------------------------
133
+ # Motion Primitives
134
+ # -----------------------------------------------------------------------------
135
+
136
+ register_primitive(PrimitiveDefinition(
137
+ name="move_to_pose",
138
+ description="Move end effector to target pose in Cartesian space",
139
+ category=PrimitiveCategory.MOTION,
140
+ parameters={
141
+ "pose": ParameterDefinition(
142
+ type="Pose",
143
+ description="Target pose with position (x, y, z) and orientation (quaternion)",
144
+ required=True
145
+ ),
146
+ "speed": ParameterDefinition(
147
+ type="float",
148
+ description="Movement speed as fraction of max (0.0-1.0)",
149
+ required=False,
150
+ default=0.5,
151
+ range=(0.0, 1.0)
152
+ ),
153
+ "acceleration": ParameterDefinition(
154
+ type="float",
155
+ description="Acceleration as fraction of max (0.0-1.0)",
156
+ required=False,
157
+ default=0.3,
158
+ range=(0.0, 1.0)
159
+ ),
160
+ "blend_radius": ParameterDefinition(
161
+ type="float",
162
+ description="Blend radius for smooth path (meters)",
163
+ required=False,
164
+ default=0.0,
165
+ units="meters"
166
+ ),
167
+ },
168
+ returns="bool",
169
+ hardware=["arm"],
170
+ blocking=True,
171
+ timeout_default=30.0,
172
+ example='move_to_pose(pose={"position": [0.5, 0.0, 0.3], "orientation": [0, 0, 0, 1]})'
173
+ ))
174
+
175
+ register_primitive(PrimitiveDefinition(
176
+ name="move_to_joints",
177
+ description="Move to target joint configuration",
178
+ category=PrimitiveCategory.MOTION,
179
+ parameters={
180
+ "joint_positions": ParameterDefinition(
181
+ type="array",
182
+ description="Target joint positions in radians",
183
+ required=True
184
+ ),
185
+ "speed": ParameterDefinition(
186
+ type="float",
187
+ description="Movement speed as fraction of max (0.0-1.0)",
188
+ required=False,
189
+ default=0.5,
190
+ range=(0.0, 1.0)
191
+ ),
192
+ "acceleration": ParameterDefinition(
193
+ type="float",
194
+ description="Acceleration as fraction of max (0.0-1.0)",
195
+ required=False,
196
+ default=0.3,
197
+ range=(0.0, 1.0)
198
+ ),
199
+ },
200
+ returns="bool",
201
+ hardware=["arm"],
202
+ blocking=True,
203
+ timeout_default=30.0,
204
+ example="move_to_joints(joint_positions=[0, -1.57, 1.57, 0, 0, 0])"
205
+ ))
206
+
207
+ register_primitive(PrimitiveDefinition(
208
+ name="move_linear",
209
+ description="Move end effector in a straight line to target pose",
210
+ category=PrimitiveCategory.MOTION,
211
+ parameters={
212
+ "pose": ParameterDefinition(
213
+ type="Pose",
214
+ description="Target pose",
215
+ required=True
216
+ ),
217
+ "speed": ParameterDefinition(
218
+ type="float",
219
+ description="Linear speed (m/s)",
220
+ required=False,
221
+ default=0.1,
222
+ range=(0.001, 1.0),
223
+ units="m/s"
224
+ ),
225
+ },
226
+ returns="bool",
227
+ hardware=["arm"],
228
+ blocking=True,
229
+ timeout_default=60.0,
230
+ example='move_linear(pose={"position": [0.5, 0.0, 0.3], "orientation": [0, 0, 0, 1]}, speed=0.05)'
231
+ ))
232
+
233
+ register_primitive(PrimitiveDefinition(
234
+ name="move_relative",
235
+ description="Move end effector relative to current pose",
236
+ category=PrimitiveCategory.MOTION,
237
+ parameters={
238
+ "delta_position": ParameterDefinition(
239
+ type="array",
240
+ description="Relative position change [dx, dy, dz] in meters",
241
+ required=False,
242
+ default=[0, 0, 0]
243
+ ),
244
+ "delta_rotation": ParameterDefinition(
245
+ type="array",
246
+ description="Relative rotation [rx, ry, rz] in radians",
247
+ required=False,
248
+ default=[0, 0, 0]
249
+ ),
250
+ "frame": ParameterDefinition(
251
+ type="string",
252
+ description="Reference frame: 'base', 'tool', or 'world'",
253
+ required=False,
254
+ default="tool"
255
+ ),
256
+ "speed": ParameterDefinition(
257
+ type="float",
258
+ description="Movement speed as fraction of max (0.0-1.0)",
259
+ required=False,
260
+ default=0.3,
261
+ range=(0.0, 1.0)
262
+ ),
263
+ },
264
+ returns="bool",
265
+ hardware=["arm"],
266
+ blocking=True,
267
+ timeout_default=30.0,
268
+ example="move_relative(delta_position=[0, 0, -0.1], frame='tool')"
269
+ ))
270
+
271
+ register_primitive(PrimitiveDefinition(
272
+ name="move_servo",
273
+ description="Move a single servo to target position",
274
+ category=PrimitiveCategory.MOTION,
275
+ parameters={
276
+ "servo_id": ParameterDefinition(
277
+ type="int",
278
+ description="Servo ID",
279
+ required=True
280
+ ),
281
+ "position": ParameterDefinition(
282
+ type="float",
283
+ description="Target position (unit depends on servo type)",
284
+ required=True
285
+ ),
286
+ "time_ms": ParameterDefinition(
287
+ type="int",
288
+ description="Movement duration in milliseconds",
289
+ required=False,
290
+ default=500
291
+ ),
292
+ },
293
+ returns="bool",
294
+ hardware=["arm"],
295
+ blocking=True,
296
+ timeout_default=5.0,
297
+ example="move_servo(servo_id=1, position=512, time_ms=1000)"
298
+ ))
299
+
300
+ register_primitive(PrimitiveDefinition(
301
+ name="move_servos",
302
+ description="Move multiple servos simultaneously",
303
+ category=PrimitiveCategory.MOTION,
304
+ parameters={
305
+ "commands": ParameterDefinition(
306
+ type="array",
307
+ description="List of {servo_id, position, time_ms} commands",
308
+ required=True
309
+ ),
310
+ },
311
+ returns="bool",
312
+ hardware=["arm"],
313
+ blocking=True,
314
+ timeout_default=5.0,
315
+ example="move_servos(commands=[{'servo_id': 1, 'position': 512}, {'servo_id': 2, 'position': 600}])"
316
+ ))
317
+
318
+ register_primitive(PrimitiveDefinition(
319
+ name="execute_trajectory",
320
+ description="Execute a pre-planned trajectory",
321
+ category=PrimitiveCategory.MOTION,
322
+ parameters={
323
+ "trajectory": ParameterDefinition(
324
+ type="Trajectory",
325
+ description="Trajectory with waypoints and timing",
326
+ required=True
327
+ ),
328
+ "speed_scale": ParameterDefinition(
329
+ type="float",
330
+ description="Speed scaling factor (0.0-2.0)",
331
+ required=False,
332
+ default=1.0,
333
+ range=(0.1, 2.0)
334
+ ),
335
+ },
336
+ returns="bool",
337
+ hardware=["arm"],
338
+ blocking=True,
339
+ timeout_default=300.0,
340
+ example="execute_trajectory(trajectory=planned_traj, speed_scale=0.8)"
341
+ ))
342
+
343
+ register_primitive(PrimitiveDefinition(
344
+ name="home",
345
+ description="Move robot to home/neutral position",
346
+ category=PrimitiveCategory.MOTION,
347
+ parameters={
348
+ "speed": ParameterDefinition(
349
+ type="float",
350
+ description="Movement speed as fraction of max (0.0-1.0)",
351
+ required=False,
352
+ default=0.3,
353
+ range=(0.0, 1.0)
354
+ ),
355
+ },
356
+ returns="bool",
357
+ hardware=["arm"],
358
+ blocking=True,
359
+ timeout_default=30.0,
360
+ example="home(speed=0.5)"
361
+ ))
362
+
363
+ # -----------------------------------------------------------------------------
364
+ # Gripper Primitives
365
+ # -----------------------------------------------------------------------------
366
+
367
+ register_primitive(PrimitiveDefinition(
368
+ name="close_gripper",
369
+ description="Close gripper to grasp an object",
370
+ category=PrimitiveCategory.GRIPPER,
371
+ parameters={
372
+ "force": ParameterDefinition(
373
+ type="float",
374
+ description="Gripping force in Newtons",
375
+ required=False,
376
+ default=10.0,
377
+ range=(1.0, 100.0),
378
+ units="N"
379
+ ),
380
+ "width": ParameterDefinition(
381
+ type="float",
382
+ description="Target gripper width (optional, for partial close)",
383
+ required=False,
384
+ default=None,
385
+ units="meters"
386
+ ),
387
+ "speed": ParameterDefinition(
388
+ type="float",
389
+ description="Closing speed as fraction of max (0.0-1.0)",
390
+ required=False,
391
+ default=0.5,
392
+ range=(0.0, 1.0)
393
+ ),
394
+ },
395
+ returns="bool",
396
+ hardware=["gripper"],
397
+ blocking=True,
398
+ timeout_default=5.0,
399
+ example="close_gripper(force=20.0)"
400
+ ))
401
+
402
+ register_primitive(PrimitiveDefinition(
403
+ name="open_gripper",
404
+ description="Open gripper to release an object",
405
+ category=PrimitiveCategory.GRIPPER,
406
+ parameters={
407
+ "width": ParameterDefinition(
408
+ type="float",
409
+ description="Target opening width",
410
+ required=False,
411
+ default=None,
412
+ units="meters"
413
+ ),
414
+ "speed": ParameterDefinition(
415
+ type="float",
416
+ description="Opening speed as fraction of max (0.0-1.0)",
417
+ required=False,
418
+ default=0.5,
419
+ range=(0.0, 1.0)
420
+ ),
421
+ },
422
+ returns="bool",
423
+ hardware=["gripper"],
424
+ blocking=True,
425
+ timeout_default=5.0,
426
+ example="open_gripper(width=0.08)"
427
+ ))
428
+
429
+ register_primitive(PrimitiveDefinition(
430
+ name="set_gripper_position",
431
+ description="Set gripper to specific position",
432
+ category=PrimitiveCategory.GRIPPER,
433
+ parameters={
434
+ "position": ParameterDefinition(
435
+ type="float",
436
+ description="Target position (0.0=closed, 1.0=fully open)",
437
+ required=True,
438
+ range=(0.0, 1.0)
439
+ ),
440
+ "speed": ParameterDefinition(
441
+ type="float",
442
+ description="Movement speed as fraction of max (0.0-1.0)",
443
+ required=False,
444
+ default=0.5,
445
+ range=(0.0, 1.0)
446
+ ),
447
+ },
448
+ returns="bool",
449
+ hardware=["gripper"],
450
+ blocking=True,
451
+ timeout_default=5.0,
452
+ example="set_gripper_position(position=0.5)"
453
+ ))
454
+
455
+ # -----------------------------------------------------------------------------
456
+ # Sensing Primitives
457
+ # -----------------------------------------------------------------------------
458
+
459
+ register_primitive(PrimitiveDefinition(
460
+ name="read_position",
461
+ description="Read current end effector pose",
462
+ category=PrimitiveCategory.SENSING,
463
+ parameters={
464
+ "frame": ParameterDefinition(
465
+ type="string",
466
+ description="Reference frame: 'base' or 'world'",
467
+ required=False,
468
+ default="base"
469
+ ),
470
+ },
471
+ returns="Pose",
472
+ hardware=["arm"],
473
+ blocking=False,
474
+ example="current_pose = read_position()"
475
+ ))
476
+
477
+ register_primitive(PrimitiveDefinition(
478
+ name="read_joints",
479
+ description="Read current joint positions",
480
+ category=PrimitiveCategory.SENSING,
481
+ parameters={},
482
+ returns="array",
483
+ hardware=["arm"],
484
+ blocking=False,
485
+ example="joint_positions = read_joints()"
486
+ ))
487
+
488
+ register_primitive(PrimitiveDefinition(
489
+ name="read_force",
490
+ description="Read force/torque sensor values",
491
+ category=PrimitiveCategory.SENSING,
492
+ parameters={
493
+ "frame": ParameterDefinition(
494
+ type="string",
495
+ description="Reference frame: 'sensor', 'base', or 'world'",
496
+ required=False,
497
+ default="sensor"
498
+ ),
499
+ },
500
+ returns="array",
501
+ hardware=["force_sensor"],
502
+ blocking=False,
503
+ example="forces = read_force() # Returns [fx, fy, fz, tx, ty, tz]"
504
+ ))
505
+
506
+ register_primitive(PrimitiveDefinition(
507
+ name="read_gripper_state",
508
+ description="Read gripper position and force",
509
+ category=PrimitiveCategory.SENSING,
510
+ parameters={},
511
+ returns="dict",
512
+ hardware=["gripper"],
513
+ blocking=False,
514
+ example="state = read_gripper_state() # Returns {'position': 0.04, 'force': 5.2, 'object_detected': True}"
515
+ ))
516
+
517
+ register_primitive(PrimitiveDefinition(
518
+ name="read_servo_state",
519
+ description="Read state of a single servo",
520
+ category=PrimitiveCategory.SENSING,
521
+ parameters={
522
+ "servo_id": ParameterDefinition(
523
+ type="int",
524
+ description="Servo ID to read",
525
+ required=True
526
+ ),
527
+ },
528
+ returns="dict",
529
+ hardware=["arm"],
530
+ blocking=False,
531
+ example="state = read_servo_state(servo_id=1)"
532
+ ))
533
+
534
+ register_primitive(PrimitiveDefinition(
535
+ name="detect_object",
536
+ description="Detect objects in camera view",
537
+ category=PrimitiveCategory.SENSING,
538
+ parameters={
539
+ "object_type": ParameterDefinition(
540
+ type="string",
541
+ description="Type of object to detect (or 'any')",
542
+ required=False,
543
+ default="any"
544
+ ),
545
+ "confidence_threshold": ParameterDefinition(
546
+ type="float",
547
+ description="Minimum detection confidence (0.0-1.0)",
548
+ required=False,
549
+ default=0.7,
550
+ range=(0.0, 1.0)
551
+ ),
552
+ },
553
+ returns="array",
554
+ hardware=["camera"],
555
+ blocking=False,
556
+ example="objects = detect_object(object_type='cube', confidence_threshold=0.8)"
557
+ ))
558
+
559
+ register_primitive(PrimitiveDefinition(
560
+ name="get_object_pose",
561
+ description="Get pose of a detected object",
562
+ category=PrimitiveCategory.SENSING,
563
+ parameters={
564
+ "object_id": ParameterDefinition(
565
+ type="string",
566
+ description="ID of detected object",
567
+ required=True
568
+ ),
569
+ "frame": ParameterDefinition(
570
+ type="string",
571
+ description="Reference frame for pose",
572
+ required=False,
573
+ default="base"
574
+ ),
575
+ },
576
+ returns="Pose",
577
+ hardware=["camera"],
578
+ blocking=False,
579
+ example="object_pose = get_object_pose(object_id='cube_1')"
580
+ ))
581
+
582
+ # -----------------------------------------------------------------------------
583
+ # Wait Primitives
584
+ # -----------------------------------------------------------------------------
585
+
586
+ register_primitive(PrimitiveDefinition(
587
+ name="wait",
588
+ description="Wait for specified duration",
589
+ category=PrimitiveCategory.WAIT,
590
+ parameters={
591
+ "duration": ParameterDefinition(
592
+ type="float",
593
+ description="Wait duration in seconds",
594
+ required=True,
595
+ range=(0.0, 300.0),
596
+ units="seconds"
597
+ ),
598
+ },
599
+ returns="bool",
600
+ hardware=[],
601
+ blocking=True,
602
+ example="wait(duration=2.0)"
603
+ ))
604
+
605
+ register_primitive(PrimitiveDefinition(
606
+ name="wait_for_contact",
607
+ description="Wait until force threshold is detected",
608
+ category=PrimitiveCategory.WAIT,
609
+ parameters={
610
+ "force_threshold": ParameterDefinition(
611
+ type="float",
612
+ description="Force threshold in Newtons",
613
+ required=False,
614
+ default=5.0,
615
+ range=(0.1, 100.0),
616
+ units="N"
617
+ ),
618
+ "timeout": ParameterDefinition(
619
+ type="float",
620
+ description="Maximum wait time in seconds",
621
+ required=False,
622
+ default=10.0,
623
+ units="seconds"
624
+ ),
625
+ "direction": ParameterDefinition(
626
+ type="string",
627
+ description="Force direction to monitor: 'any', 'x', 'y', 'z', '-x', '-y', '-z'",
628
+ required=False,
629
+ default="any"
630
+ ),
631
+ },
632
+ returns="bool",
633
+ hardware=["force_sensor"],
634
+ blocking=True,
635
+ timeout_default=10.0,
636
+ example="contact = wait_for_contact(force_threshold=3.0, timeout=5.0)"
637
+ ))
638
+
639
+ register_primitive(PrimitiveDefinition(
640
+ name="wait_for_motion_complete",
641
+ description="Wait until current motion is complete",
642
+ category=PrimitiveCategory.WAIT,
643
+ parameters={
644
+ "timeout": ParameterDefinition(
645
+ type="float",
646
+ description="Maximum wait time in seconds",
647
+ required=False,
648
+ default=30.0,
649
+ units="seconds"
650
+ ),
651
+ },
652
+ returns="bool",
653
+ hardware=["arm"],
654
+ blocking=True,
655
+ timeout_default=30.0,
656
+ example="wait_for_motion_complete()"
657
+ ))
658
+
659
+ register_primitive(PrimitiveDefinition(
660
+ name="wait_for_gripper",
661
+ description="Wait until gripper motion is complete",
662
+ category=PrimitiveCategory.WAIT,
663
+ parameters={
664
+ "timeout": ParameterDefinition(
665
+ type="float",
666
+ description="Maximum wait time in seconds",
667
+ required=False,
668
+ default=5.0,
669
+ units="seconds"
670
+ ),
671
+ },
672
+ returns="bool",
673
+ hardware=["gripper"],
674
+ blocking=True,
675
+ timeout_default=5.0,
676
+ example="wait_for_gripper()"
677
+ ))
678
+
679
+ register_primitive(PrimitiveDefinition(
680
+ name="wait_for_condition",
681
+ description="Wait until a condition is met",
682
+ category=PrimitiveCategory.WAIT,
683
+ parameters={
684
+ "condition": ParameterDefinition(
685
+ type="string",
686
+ description="Condition expression to evaluate",
687
+ required=True
688
+ ),
689
+ "timeout": ParameterDefinition(
690
+ type="float",
691
+ description="Maximum wait time in seconds",
692
+ required=False,
693
+ default=30.0,
694
+ units="seconds"
695
+ ),
696
+ "poll_rate": ParameterDefinition(
697
+ type="float",
698
+ description="Condition check frequency in Hz",
699
+ required=False,
700
+ default=10.0,
701
+ units="Hz"
702
+ ),
703
+ },
704
+ returns="bool",
705
+ hardware=[],
706
+ blocking=True,
707
+ timeout_default=30.0,
708
+ example="wait_for_condition(condition='gripper.object_detected', timeout=10.0)"
709
+ ))
710
+
711
+ # -----------------------------------------------------------------------------
712
+ # Control Primitives
713
+ # -----------------------------------------------------------------------------
714
+
715
+ register_primitive(PrimitiveDefinition(
716
+ name="set_torque",
717
+ description="Enable or disable servo torque",
718
+ category=PrimitiveCategory.CONTROL,
719
+ parameters={
720
+ "enabled": ParameterDefinition(
721
+ type="bool",
722
+ description="Enable (True) or disable (False) torque",
723
+ required=True
724
+ ),
725
+ "servo_ids": ParameterDefinition(
726
+ type="array",
727
+ description="List of servo IDs (empty for all)",
728
+ required=False,
729
+ default=[]
730
+ ),
731
+ },
732
+ returns="bool",
733
+ hardware=["arm"],
734
+ blocking=False,
735
+ example="set_torque(enabled=False) # Disable all servos"
736
+ ))
737
+
738
+ register_primitive(PrimitiveDefinition(
739
+ name="set_compliance",
740
+ description="Set servo compliance/stiffness mode",
741
+ category=PrimitiveCategory.CONTROL,
742
+ parameters={
743
+ "mode": ParameterDefinition(
744
+ type="string",
745
+ description="Compliance mode: 'stiff', 'soft', 'free'",
746
+ required=True
747
+ ),
748
+ "servo_ids": ParameterDefinition(
749
+ type="array",
750
+ description="List of servo IDs (empty for all)",
751
+ required=False,
752
+ default=[]
753
+ ),
754
+ },
755
+ returns="bool",
756
+ hardware=["arm"],
757
+ blocking=False,
758
+ example="set_compliance(mode='soft', servo_ids=[1, 2, 3])"
759
+ ))
760
+
761
+ register_primitive(PrimitiveDefinition(
762
+ name="set_speed_limit",
763
+ description="Set maximum speed for subsequent motions",
764
+ category=PrimitiveCategory.CONTROL,
765
+ parameters={
766
+ "max_speed": ParameterDefinition(
767
+ type="float",
768
+ description="Maximum speed as fraction of absolute max (0.0-1.0)",
769
+ required=True,
770
+ range=(0.0, 1.0)
771
+ ),
772
+ },
773
+ returns="bool",
774
+ hardware=["arm"],
775
+ blocking=False,
776
+ example="set_speed_limit(max_speed=0.3)"
777
+ ))
778
+
779
+ register_primitive(PrimitiveDefinition(
780
+ name="stop",
781
+ description="Immediately stop all motion",
782
+ category=PrimitiveCategory.CONTROL,
783
+ parameters={
784
+ "deceleration": ParameterDefinition(
785
+ type="float",
786
+ description="Deceleration rate (0.0=instant, 1.0=smooth)",
787
+ required=False,
788
+ default=0.5,
789
+ range=(0.0, 1.0)
790
+ ),
791
+ },
792
+ returns="bool",
793
+ hardware=["arm"],
794
+ blocking=True,
795
+ timeout_default=2.0,
796
+ example="stop(deceleration=0.0) # Emergency stop"
797
+ ))
798
+
799
+ register_primitive(PrimitiveDefinition(
800
+ name="enable_force_control",
801
+ description="Enable force control mode",
802
+ category=PrimitiveCategory.CONTROL,
803
+ parameters={
804
+ "target_force": ParameterDefinition(
805
+ type="array",
806
+ description="Target force/torque [fx, fy, fz, tx, ty, tz]",
807
+ required=True
808
+ ),
809
+ "stiffness": ParameterDefinition(
810
+ type="array",
811
+ description="Stiffness for each axis",
812
+ required=False,
813
+ default=[1000, 1000, 1000, 100, 100, 100]
814
+ ),
815
+ },
816
+ returns="bool",
817
+ hardware=["arm", "force_sensor"],
818
+ blocking=False,
819
+ example="enable_force_control(target_force=[0, 0, -10, 0, 0, 0])"
820
+ ))
821
+
822
+ register_primitive(PrimitiveDefinition(
823
+ name="disable_force_control",
824
+ description="Disable force control mode",
825
+ category=PrimitiveCategory.CONTROL,
826
+ parameters={},
827
+ returns="bool",
828
+ hardware=["arm"],
829
+ blocking=False,
830
+ example="disable_force_control()"
831
+ ))
832
+
833
+
834
+ # =============================================================================
835
+ # REGISTRY FUNCTIONS
836
+ # =============================================================================
837
+
838
+ def get_primitive(name: str) -> Optional[PrimitiveDefinition]:
839
+ """Get a primitive definition by name."""
840
+ return PRIMITIVE_REGISTRY.get(name)
841
+
842
+
843
+ def list_primitives(
844
+ category: Optional[PrimitiveCategory] = None,
845
+ hardware: Optional[str] = None
846
+ ) -> List[PrimitiveDefinition]:
847
+ """
848
+ List all primitives, optionally filtered by category or hardware.
849
+
850
+ Args:
851
+ category: Filter by primitive category
852
+ hardware: Filter by required hardware type
853
+
854
+ Returns:
855
+ List of matching primitive definitions
856
+ """
857
+ primitives = list(PRIMITIVE_REGISTRY.values())
858
+
859
+ if category:
860
+ primitives = [p for p in primitives if p.category == category]
861
+
862
+ if hardware:
863
+ primitives = [p for p in primitives if hardware in p.hardware]
864
+
865
+ return primitives
866
+
867
+
868
+ def get_primitive_names() -> List[str]:
869
+ """Get all registered primitive names."""
870
+ return list(PRIMITIVE_REGISTRY.keys())
871
+
872
+
873
+ def validate_primitive_chain(primitives: List[str]) -> List[str]:
874
+ """
875
+ Validate a chain of primitives for a skill.
876
+
877
+ Checks that:
878
+ - All primitives exist
879
+ - Hardware requirements are consistent
880
+ - No obvious sequencing issues
881
+
882
+ Args:
883
+ primitives: List of primitive names
884
+
885
+ Returns:
886
+ List of validation errors (empty if valid)
887
+ """
888
+ errors = []
889
+
890
+ # Check all primitives exist
891
+ for name in primitives:
892
+ if name not in PRIMITIVE_REGISTRY:
893
+ errors.append(f"Unknown primitive: {name}")
894
+
895
+ if errors:
896
+ return errors
897
+
898
+ # Collect hardware requirements
899
+ all_hardware: Set[str] = set()
900
+ for name in primitives:
901
+ prim = PRIMITIVE_REGISTRY[name]
902
+ all_hardware.update(prim.hardware)
903
+
904
+ # Check for common issues
905
+ prim_sequence = [PRIMITIVE_REGISTRY[name] for name in primitives]
906
+
907
+ # Warn if force sensing used without force sensor
908
+ uses_force = any(
909
+ "force_sensor" in p.hardware or "force" in p.name.lower()
910
+ for p in prim_sequence
911
+ )
912
+ has_force_sensor = "force_sensor" in all_hardware
913
+ if uses_force and not has_force_sensor:
914
+ errors.append(
915
+ "Skill uses force-related primitives but no force_sensor "
916
+ "in hardware requirements"
917
+ )
918
+
919
+ # Warn if gripper used without gripper hardware
920
+ uses_gripper = any("gripper" in p.hardware for p in prim_sequence)
921
+ has_gripper = "gripper" in all_hardware
922
+ if uses_gripper and not has_gripper:
923
+ errors.append(
924
+ "Skill uses gripper primitives but no gripper "
925
+ "in hardware requirements"
926
+ )
927
+
928
+ return errors
929
+
930
+
931
+ def get_required_hardware(primitives: List[str]) -> Set[str]:
932
+ """
933
+ Get the set of all hardware required by a list of primitives.
934
+
935
+ Args:
936
+ primitives: List of primitive names
937
+
938
+ Returns:
939
+ Set of required hardware types
940
+ """
941
+ hardware: Set[str] = set()
942
+ for name in primitives:
943
+ prim = PRIMITIVE_REGISTRY.get(name)
944
+ if prim:
945
+ hardware.update(prim.hardware)
946
+ return hardware
947
+
948
+
949
+ def get_primitives_by_hardware(hardware: str) -> List[PrimitiveDefinition]:
950
+ """Get all primitives that require specific hardware."""
951
+ return [
952
+ p for p in PRIMITIVE_REGISTRY.values()
953
+ if hardware in p.hardware
954
+ ]
955
+
956
+
957
+ def format_primitive_docs(name: str) -> str:
958
+ """Format documentation for a primitive."""
959
+ prim = PRIMITIVE_REGISTRY.get(name)
960
+ if not prim:
961
+ return f"Unknown primitive: {name}"
962
+
963
+ lines = [
964
+ f"## {prim.name}",
965
+ "",
966
+ f"{prim.description}",
967
+ "",
968
+ f"**Category:** {prim.category.value}",
969
+ f"**Hardware:** {', '.join(prim.hardware) or 'None'}",
970
+ f"**Blocking:** {'Yes' if prim.blocking else 'No'}",
971
+ "",
972
+ "### Signature",
973
+ f"```python",
974
+ f"{prim.get_signature()}",
975
+ f"```",
976
+ "",
977
+ "### Parameters",
978
+ ]
979
+
980
+ for param_name, param_def in prim.parameters.items():
981
+ required = "(required)" if param_def.required else "(optional)"
982
+ default = f" = {param_def.default}" if param_def.default is not None else ""
983
+ lines.append(f"- **{param_name}** `{param_def.type}` {required}{default}")
984
+ lines.append(f" - {param_def.description}")
985
+ if param_def.range:
986
+ lines.append(f" - Range: {param_def.range}")
987
+ if param_def.units:
988
+ lines.append(f" - Units: {param_def.units}")
989
+
990
+ if prim.example:
991
+ lines.extend([
992
+ "",
993
+ "### Example",
994
+ f"```python",
995
+ f"{prim.example}",
996
+ f"```",
997
+ ])
998
+
999
+ return "\n".join(lines)
1000
+
1001
+
1002
+ # Export commonly used items
1003
+ __all__ = [
1004
+ "PrimitiveCategory",
1005
+ "ParameterDefinition",
1006
+ "PrimitiveDefinition",
1007
+ "PRIMITIVE_REGISTRY",
1008
+ "get_primitive",
1009
+ "list_primitives",
1010
+ "get_primitive_names",
1011
+ "validate_primitive_chain",
1012
+ "get_required_hardware",
1013
+ "get_primitives_by_hardware",
1014
+ "format_primitive_docs",
1015
+ "register_primitive",
1016
+ ]