foodforthought-cli 0.2.7__py3-none-any.whl → 0.3.0__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 (131) hide show
  1. ate/__init__.py +6 -0
  2. ate/__main__.py +16 -0
  3. ate/auth/__init__.py +1 -0
  4. ate/auth/device_flow.py +141 -0
  5. ate/auth/token_store.py +96 -0
  6. ate/behaviors/__init__.py +100 -0
  7. ate/behaviors/approach.py +399 -0
  8. ate/behaviors/common.py +686 -0
  9. ate/behaviors/tree.py +454 -0
  10. ate/cli.py +855 -3995
  11. ate/client.py +90 -0
  12. ate/commands/__init__.py +168 -0
  13. ate/commands/auth.py +389 -0
  14. ate/commands/bridge.py +448 -0
  15. ate/commands/data.py +185 -0
  16. ate/commands/deps.py +111 -0
  17. ate/commands/generate.py +384 -0
  18. ate/commands/memory.py +907 -0
  19. ate/commands/parts.py +166 -0
  20. ate/commands/primitive.py +399 -0
  21. ate/commands/protocol.py +288 -0
  22. ate/commands/recording.py +524 -0
  23. ate/commands/repo.py +154 -0
  24. ate/commands/simulation.py +291 -0
  25. ate/commands/skill.py +303 -0
  26. ate/commands/skills.py +487 -0
  27. ate/commands/team.py +147 -0
  28. ate/commands/workflow.py +271 -0
  29. ate/detection/__init__.py +38 -0
  30. ate/detection/base.py +142 -0
  31. ate/detection/color_detector.py +399 -0
  32. ate/detection/trash_detector.py +322 -0
  33. ate/drivers/__init__.py +39 -0
  34. ate/drivers/ble_transport.py +405 -0
  35. ate/drivers/mechdog.py +942 -0
  36. ate/drivers/wifi_camera.py +477 -0
  37. ate/interfaces/__init__.py +187 -0
  38. ate/interfaces/base.py +273 -0
  39. ate/interfaces/body.py +267 -0
  40. ate/interfaces/detection.py +282 -0
  41. ate/interfaces/locomotion.py +422 -0
  42. ate/interfaces/manipulation.py +408 -0
  43. ate/interfaces/navigation.py +389 -0
  44. ate/interfaces/perception.py +362 -0
  45. ate/interfaces/sensors.py +247 -0
  46. ate/interfaces/types.py +371 -0
  47. ate/llm_proxy.py +239 -0
  48. ate/mcp_server.py +387 -0
  49. ate/memory/__init__.py +35 -0
  50. ate/memory/cloud.py +244 -0
  51. ate/memory/context.py +269 -0
  52. ate/memory/embeddings.py +184 -0
  53. ate/memory/export.py +26 -0
  54. ate/memory/merge.py +146 -0
  55. ate/memory/migrate/__init__.py +34 -0
  56. ate/memory/migrate/base.py +89 -0
  57. ate/memory/migrate/pipeline.py +189 -0
  58. ate/memory/migrate/sources/__init__.py +13 -0
  59. ate/memory/migrate/sources/chroma.py +170 -0
  60. ate/memory/migrate/sources/pinecone.py +120 -0
  61. ate/memory/migrate/sources/qdrant.py +110 -0
  62. ate/memory/migrate/sources/weaviate.py +160 -0
  63. ate/memory/reranker.py +353 -0
  64. ate/memory/search.py +26 -0
  65. ate/memory/store.py +548 -0
  66. ate/recording/__init__.py +83 -0
  67. ate/recording/demonstration.py +378 -0
  68. ate/recording/session.py +415 -0
  69. ate/recording/upload.py +304 -0
  70. ate/recording/visual.py +416 -0
  71. ate/recording/wrapper.py +95 -0
  72. ate/robot/__init__.py +221 -0
  73. ate/robot/agentic_servo.py +856 -0
  74. ate/robot/behaviors.py +493 -0
  75. ate/robot/ble_capture.py +1000 -0
  76. ate/robot/ble_enumerate.py +506 -0
  77. ate/robot/calibration.py +668 -0
  78. ate/robot/calibration_state.py +388 -0
  79. ate/robot/commands.py +3735 -0
  80. ate/robot/direction_calibration.py +554 -0
  81. ate/robot/discovery.py +441 -0
  82. ate/robot/introspection.py +330 -0
  83. ate/robot/llm_system_id.py +654 -0
  84. ate/robot/locomotion_calibration.py +508 -0
  85. ate/robot/manager.py +270 -0
  86. ate/robot/marker_generator.py +611 -0
  87. ate/robot/perception.py +502 -0
  88. ate/robot/primitives.py +614 -0
  89. ate/robot/profiles.py +281 -0
  90. ate/robot/registry.py +322 -0
  91. ate/robot/servo_mapper.py +1153 -0
  92. ate/robot/skill_upload.py +675 -0
  93. ate/robot/target_calibration.py +500 -0
  94. ate/robot/teach.py +515 -0
  95. ate/robot/types.py +242 -0
  96. ate/robot/visual_labeler.py +1048 -0
  97. ate/robot/visual_servo_loop.py +494 -0
  98. ate/robot/visual_servoing.py +570 -0
  99. ate/robot/visual_system_id.py +906 -0
  100. ate/transports/__init__.py +121 -0
  101. ate/transports/base.py +394 -0
  102. ate/transports/ble.py +405 -0
  103. ate/transports/hybrid.py +444 -0
  104. ate/transports/serial.py +345 -0
  105. ate/urdf/__init__.py +30 -0
  106. ate/urdf/capture.py +582 -0
  107. ate/urdf/cloud.py +491 -0
  108. ate/urdf/collision.py +271 -0
  109. ate/urdf/commands.py +708 -0
  110. ate/urdf/depth.py +360 -0
  111. ate/urdf/inertial.py +312 -0
  112. ate/urdf/kinematics.py +330 -0
  113. ate/urdf/lifting.py +415 -0
  114. ate/urdf/meshing.py +300 -0
  115. ate/urdf/models/__init__.py +110 -0
  116. ate/urdf/models/depth_anything.py +253 -0
  117. ate/urdf/models/sam2.py +324 -0
  118. ate/urdf/motion_analysis.py +396 -0
  119. ate/urdf/pipeline.py +468 -0
  120. ate/urdf/scale.py +256 -0
  121. ate/urdf/scan_session.py +411 -0
  122. ate/urdf/segmentation.py +299 -0
  123. ate/urdf/synthesis.py +319 -0
  124. ate/urdf/topology.py +336 -0
  125. ate/urdf/validation.py +371 -0
  126. {foodforthought_cli-0.2.7.dist-info → foodforthought_cli-0.3.0.dist-info}/METADATA +9 -1
  127. foodforthought_cli-0.3.0.dist-info/RECORD +166 -0
  128. {foodforthought_cli-0.2.7.dist-info → foodforthought_cli-0.3.0.dist-info}/WHEEL +1 -1
  129. foodforthought_cli-0.2.7.dist-info/RECORD +0 -44
  130. {foodforthought_cli-0.2.7.dist-info → foodforthought_cli-0.3.0.dist-info}/entry_points.txt +0 -0
  131. {foodforthought_cli-0.2.7.dist-info → foodforthought_cli-0.3.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,614 @@
1
+ """
2
+ Robot Skill Primitives - Building blocks for complex behaviors.
3
+
4
+ Primitives are the lowest-level reusable skills:
5
+ - Single servo movements or simple transitions
6
+ - Parameterized for flexibility
7
+ - Composable into higher-level behaviors
8
+
9
+ Skill Hierarchy:
10
+ Primitive → Compound → Behavior
11
+
12
+ Primitive: gripper_open(), arm_up()
13
+ Compound: pickup() = [arm_down, gripper_open, gripper_close, arm_up]
14
+ Behavior: fetch() = detect + approach + pickup + carry + place
15
+ """
16
+
17
+ import time
18
+ from dataclasses import dataclass, field
19
+ from typing import Dict, List, Optional, Callable, Any, Union
20
+ from enum import Enum
21
+ from pathlib import Path
22
+ import json
23
+
24
+
25
+ class SkillType(Enum):
26
+ """Classification of skill complexity."""
27
+ PRIMITIVE = "primitive" # Single action (open gripper)
28
+ COMPOUND = "compound" # Sequence of primitives (pickup)
29
+ BEHAVIOR = "behavior" # Complex with perception/logic (fetch)
30
+
31
+
32
+ class HardwareRequirement(Enum):
33
+ """Hardware capabilities required by a skill."""
34
+ GRIPPER = "gripper"
35
+ ARM = "arm"
36
+ LEGS = "legs"
37
+ CAMERA = "camera"
38
+ DISTANCE_SENSOR = "distance_sensor"
39
+
40
+
41
+ @dataclass
42
+ class SkillParameter:
43
+ """Parameter definition for a skill."""
44
+ name: str
45
+ param_type: str # "float", "int", "bool", "str"
46
+ default: Any
47
+ min_value: Optional[float] = None
48
+ max_value: Optional[float] = None
49
+ description: str = ""
50
+
51
+
52
+ @dataclass
53
+ class Primitive:
54
+ """A primitive skill - the building block."""
55
+ name: str
56
+ description: str
57
+ servo_targets: Dict[int, int] # servo_id -> target position
58
+ duration_ms: int = 500
59
+ parameters: List[SkillParameter] = field(default_factory=list)
60
+ hardware: List[HardwareRequirement] = field(default_factory=list)
61
+
62
+ def to_dict(self) -> Dict:
63
+ return {
64
+ "name": self.name,
65
+ "skill_type": SkillType.PRIMITIVE.value,
66
+ "description": self.description,
67
+ "servo_targets": self.servo_targets,
68
+ "duration_ms": self.duration_ms,
69
+ "parameters": [
70
+ {"name": p.name, "type": p.param_type, "default": p.default}
71
+ for p in self.parameters
72
+ ],
73
+ "hardware": [h.value for h in self.hardware],
74
+ }
75
+
76
+
77
+ @dataclass
78
+ class CompoundSkill:
79
+ """A compound skill - sequence of primitives."""
80
+ name: str
81
+ description: str
82
+ steps: List[str] # Names of primitives to execute
83
+ wait_between_ms: int = 200
84
+ parameters: List[SkillParameter] = field(default_factory=list)
85
+ hardware: List[HardwareRequirement] = field(default_factory=list)
86
+
87
+ def to_dict(self) -> Dict:
88
+ return {
89
+ "name": self.name,
90
+ "skill_type": SkillType.COMPOUND.value,
91
+ "description": self.description,
92
+ "steps": self.steps,
93
+ "wait_between_ms": self.wait_between_ms,
94
+ "parameters": [
95
+ {"name": p.name, "type": p.param_type, "default": p.default}
96
+ for p in self.parameters
97
+ ],
98
+ "hardware": [h.value for h in self.hardware],
99
+ }
100
+
101
+
102
+ @dataclass
103
+ class Behavior:
104
+ """A behavior - complex skill with perception and logic."""
105
+ name: str
106
+ description: str
107
+ requires_perception: bool = True
108
+ perception_hook: Optional[str] = None # Function name for detection
109
+ verify_hook: Optional[str] = None # Function to verify success
110
+ steps: List[Union[str, Dict]] = field(default_factory=list) # Primitives or conditions
111
+ parameters: List[SkillParameter] = field(default_factory=list)
112
+ hardware: List[HardwareRequirement] = field(default_factory=list)
113
+
114
+ def to_dict(self) -> Dict:
115
+ return {
116
+ "name": self.name,
117
+ "skill_type": SkillType.BEHAVIOR.value,
118
+ "description": self.description,
119
+ "requires_perception": self.requires_perception,
120
+ "perception_hook": self.perception_hook,
121
+ "verify_hook": self.verify_hook,
122
+ "steps": self.steps,
123
+ "parameters": [
124
+ {"name": p.name, "type": p.param_type, "default": p.default}
125
+ for p in self.parameters
126
+ ],
127
+ "hardware": [h.value for h in self.hardware],
128
+ }
129
+
130
+
131
+ # =============================================================================
132
+ # MechDog Primitives - Based on known servo configuration
133
+ # =============================================================================
134
+
135
+ # Servo mapping for HiWonder MechDog
136
+ MECHDOG_SERVOS = {
137
+ # Legs (quadruped)
138
+ 1: {"name": "front_right_hip", "group": "front_right_leg"},
139
+ 2: {"name": "front_right_thigh", "group": "front_right_leg"},
140
+ 3: {"name": "front_left_hip", "group": "front_left_leg"},
141
+ 4: {"name": "front_left_thigh", "group": "front_left_leg"},
142
+ 5: {"name": "rear_right_hip", "group": "rear_right_leg"},
143
+ 6: {"name": "rear_right_thigh", "group": "rear_right_leg"},
144
+ 7: {"name": "rear_left_hip", "group": "rear_left_leg"},
145
+ 8: {"name": "rear_left_thigh", "group": "rear_left_leg"},
146
+ # Arm (optional attachment)
147
+ 9: {"name": "arm_shoulder", "group": "arm"},
148
+ 10: {"name": "arm_elbow", "group": "arm"},
149
+ 11: {"name": "gripper", "group": "arm"},
150
+ }
151
+
152
+ # Known positions from calibration
153
+ MECHDOG_POSITIONS = {
154
+ # Standing pose (from previous discovery)
155
+ "stand": {1: 2096, 2: 1621, 3: 2170, 4: 1611, 5: 904, 6: 1379, 7: 830, 8: 1389},
156
+
157
+ # Arm positions
158
+ "arm_up": {9: 500, 10: 1500, 11: 2500},
159
+ "arm_down": {9: 1800, 10: 1200, 11: 2500},
160
+ "arm_forward": {9: 1200, 10: 1500, 11: 2500},
161
+
162
+ # Gripper
163
+ "gripper_open": {11: 2500},
164
+ "gripper_closed": {11: 200},
165
+
166
+ # Body tilts (adjusting leg positions)
167
+ "tilt_forward": {
168
+ 1: 2096, 2: 1900, # Front right - extend
169
+ 3: 2170, 4: 1900, # Front left - extend
170
+ 5: 904, 6: 1100, # Rear right - retract
171
+ 7: 830, 8: 1100, # Rear left - retract
172
+ },
173
+ "tilt_back": {
174
+ 1: 2096, 2: 1400, # Front right - retract
175
+ 3: 2170, 4: 1400, # Front left - retract
176
+ 5: 904, 6: 1600, # Rear right - extend
177
+ 7: 830, 8: 1600, # Rear left - extend
178
+ },
179
+ }
180
+
181
+
182
+ def create_mechdog_primitives() -> Dict[str, Primitive]:
183
+ """Create the primitive library for MechDog."""
184
+ primitives = {}
185
+
186
+ # Gripper primitives
187
+ primitives["gripper_open"] = Primitive(
188
+ name="gripper_open",
189
+ description="Open the gripper fully",
190
+ servo_targets={11: 2500},
191
+ duration_ms=400,
192
+ hardware=[HardwareRequirement.GRIPPER],
193
+ )
194
+
195
+ primitives["gripper_close"] = Primitive(
196
+ name="gripper_close",
197
+ description="Close the gripper fully",
198
+ servo_targets={11: 200},
199
+ duration_ms=400,
200
+ hardware=[HardwareRequirement.GRIPPER],
201
+ )
202
+
203
+ primitives["gripper_half"] = Primitive(
204
+ name="gripper_half",
205
+ description="Gripper at half position",
206
+ servo_targets={11: 1350},
207
+ duration_ms=300,
208
+ hardware=[HardwareRequirement.GRIPPER],
209
+ )
210
+
211
+ # Arm primitives
212
+ primitives["arm_up"] = Primitive(
213
+ name="arm_up",
214
+ description="Raise arm to up position",
215
+ servo_targets={9: 500, 10: 1500},
216
+ duration_ms=500,
217
+ hardware=[HardwareRequirement.ARM],
218
+ )
219
+
220
+ primitives["arm_down"] = Primitive(
221
+ name="arm_down",
222
+ description="Lower arm to down/reaching position",
223
+ servo_targets={9: 1800, 10: 1200},
224
+ duration_ms=600,
225
+ hardware=[HardwareRequirement.ARM],
226
+ )
227
+
228
+ primitives["arm_forward"] = Primitive(
229
+ name="arm_forward",
230
+ description="Extend arm forward",
231
+ servo_targets={9: 1200, 10: 1500},
232
+ duration_ms=500,
233
+ hardware=[HardwareRequirement.ARM],
234
+ )
235
+
236
+ primitives["arm_carry"] = Primitive(
237
+ name="arm_carry",
238
+ description="Arm in carrying position (up and tucked)",
239
+ servo_targets={9: 800, 10: 1800},
240
+ duration_ms=500,
241
+ hardware=[HardwareRequirement.ARM],
242
+ )
243
+
244
+ # Body tilt primitives
245
+ primitives["tilt_forward"] = Primitive(
246
+ name="tilt_forward",
247
+ description="Tilt body forward (front legs extend, rear retract)",
248
+ servo_targets=MECHDOG_POSITIONS["tilt_forward"],
249
+ duration_ms=600,
250
+ hardware=[HardwareRequirement.LEGS],
251
+ )
252
+
253
+ primitives["tilt_back"] = Primitive(
254
+ name="tilt_back",
255
+ description="Tilt body backward",
256
+ servo_targets=MECHDOG_POSITIONS["tilt_back"],
257
+ duration_ms=600,
258
+ hardware=[HardwareRequirement.LEGS],
259
+ )
260
+
261
+ primitives["stand"] = Primitive(
262
+ name="stand",
263
+ description="Return to neutral standing position",
264
+ servo_targets=MECHDOG_POSITIONS["stand"],
265
+ duration_ms=500,
266
+ hardware=[HardwareRequirement.LEGS],
267
+ )
268
+
269
+ # Combined arm+gripper primitives
270
+ primitives["ready_to_grab"] = Primitive(
271
+ name="ready_to_grab",
272
+ description="Arm down with gripper open, ready to grab",
273
+ servo_targets={9: 1800, 10: 1200, 11: 2500},
274
+ duration_ms=600,
275
+ hardware=[HardwareRequirement.ARM, HardwareRequirement.GRIPPER],
276
+ )
277
+
278
+ primitives["grab_and_lift"] = Primitive(
279
+ name="grab_and_lift",
280
+ description="Close gripper and lift arm",
281
+ servo_targets={9: 800, 10: 1800, 11: 200},
282
+ duration_ms=600,
283
+ hardware=[HardwareRequirement.ARM, HardwareRequirement.GRIPPER],
284
+ )
285
+
286
+ return primitives
287
+
288
+
289
+ def create_mechdog_compounds(primitives: Dict[str, Primitive]) -> Dict[str, CompoundSkill]:
290
+ """Create compound skills from primitives."""
291
+ compounds = {}
292
+
293
+ # Pickup sequence
294
+ compounds["pickup"] = CompoundSkill(
295
+ name="pickup",
296
+ description="Pick up an object from the ground",
297
+ steps=["arm_up", "gripper_open", "arm_down", "gripper_close", "arm_carry"],
298
+ wait_between_ms=200,
299
+ hardware=[HardwareRequirement.ARM, HardwareRequirement.GRIPPER],
300
+ )
301
+
302
+ # Place sequence
303
+ compounds["place"] = CompoundSkill(
304
+ name="place",
305
+ description="Place held object on the ground",
306
+ steps=["arm_down", "gripper_open", "arm_up"],
307
+ wait_between_ms=200,
308
+ hardware=[HardwareRequirement.ARM, HardwareRequirement.GRIPPER],
309
+ )
310
+
311
+ # Pickup with tilt (for objects further away)
312
+ compounds["tilt_pickup"] = CompoundSkill(
313
+ name="tilt_pickup",
314
+ description="Tilt forward and pick up object",
315
+ steps=["tilt_forward", "ready_to_grab", "gripper_close", "arm_carry", "stand"],
316
+ wait_between_ms=300,
317
+ hardware=[HardwareRequirement.ARM, HardwareRequirement.GRIPPER, HardwareRequirement.LEGS],
318
+ )
319
+
320
+ # Wave greeting
321
+ compounds["wave"] = CompoundSkill(
322
+ name="wave",
323
+ description="Wave the arm as a greeting",
324
+ steps=["arm_up", "gripper_open", "arm_forward", "arm_up", "arm_forward", "arm_up"],
325
+ wait_between_ms=150,
326
+ hardware=[HardwareRequirement.ARM, HardwareRequirement.GRIPPER],
327
+ )
328
+
329
+ # Show object (lift and present)
330
+ compounds["present"] = CompoundSkill(
331
+ name="present",
332
+ description="Present a held object",
333
+ steps=["arm_forward", "gripper_half", "arm_up"],
334
+ wait_between_ms=300,
335
+ hardware=[HardwareRequirement.ARM, HardwareRequirement.GRIPPER],
336
+ )
337
+
338
+ return compounds
339
+
340
+
341
+ def create_mechdog_behaviors() -> Dict[str, Behavior]:
342
+ """Create behaviors that integrate perception."""
343
+ behaviors = {}
344
+
345
+ # Fetch behavior - find, approach, pickup, return
346
+ behaviors["fetch"] = Behavior(
347
+ name="fetch",
348
+ description="Find an object, approach it, pick it up, and return",
349
+ requires_perception=True,
350
+ perception_hook="detect_target",
351
+ verify_hook="verify_holding_object",
352
+ steps=[
353
+ {"action": "detect", "target": "object"},
354
+ {"action": "while", "condition": "target.distance > 0.15", "do": "approach"},
355
+ {"action": "skill", "name": "tilt_pickup"},
356
+ {"action": "verify", "check": "holding_object"},
357
+ {"action": "skill", "name": "stand"},
358
+ ],
359
+ hardware=[
360
+ HardwareRequirement.ARM,
361
+ HardwareRequirement.GRIPPER,
362
+ HardwareRequirement.LEGS,
363
+ HardwareRequirement.CAMERA,
364
+ ],
365
+ )
366
+
367
+ # Approach and pickup green ball
368
+ behaviors["pickup_green_ball"] = Behavior(
369
+ name="pickup_green_ball",
370
+ description="Detect green ball, approach, and pick it up",
371
+ requires_perception=True,
372
+ perception_hook="detect_green_object",
373
+ verify_hook="verify_gripper_closed",
374
+ steps=[
375
+ {"action": "detect", "target": "green_ball", "color": "green"},
376
+ {"action": "align", "target": "green_ball"},
377
+ {"action": "while", "condition": "distance > 0.2", "do": "step_forward"},
378
+ {"action": "skill", "name": "tilt_forward"},
379
+ {"action": "skill", "name": "ready_to_grab"},
380
+ {"action": "wait_ms", "duration": 300},
381
+ {"action": "skill", "name": "gripper_close"},
382
+ {"action": "wait_ms", "duration": 200},
383
+ {"action": "skill", "name": "arm_carry"},
384
+ {"action": "skill", "name": "stand"},
385
+ {"action": "verify", "check": "gripper_closed"},
386
+ ],
387
+ hardware=[
388
+ HardwareRequirement.ARM,
389
+ HardwareRequirement.GRIPPER,
390
+ HardwareRequirement.LEGS,
391
+ HardwareRequirement.CAMERA,
392
+ ],
393
+ )
394
+
395
+ # Scan environment
396
+ behaviors["scan"] = Behavior(
397
+ name="scan",
398
+ description="Scan environment for objects",
399
+ requires_perception=True,
400
+ perception_hook="detect_all_objects",
401
+ steps=[
402
+ {"action": "skill", "name": "stand"},
403
+ {"action": "detect", "target": "all"},
404
+ {"action": "return", "data": "detected_objects"},
405
+ ],
406
+ hardware=[HardwareRequirement.CAMERA],
407
+ )
408
+
409
+ return behaviors
410
+
411
+
412
+ class PrimitiveLibrary:
413
+ """
414
+ Manages the complete skill library for a robot.
415
+
416
+ Provides:
417
+ - Primitive execution
418
+ - Compound skill sequencing
419
+ - Behavior orchestration with perception
420
+ """
421
+
422
+ def __init__(self, robot_interface=None):
423
+ self.robot = robot_interface
424
+ self.primitives = create_mechdog_primitives()
425
+ self.compounds = create_mechdog_compounds(self.primitives)
426
+ self.behaviors = create_mechdog_behaviors()
427
+
428
+ def list_all(self) -> Dict[str, List[str]]:
429
+ """List all available skills by type."""
430
+ return {
431
+ "primitives": list(self.primitives.keys()),
432
+ "compounds": list(self.compounds.keys()),
433
+ "behaviors": list(self.behaviors.keys()),
434
+ }
435
+
436
+ def get_skill(self, name: str) -> Optional[Union[Primitive, CompoundSkill, Behavior]]:
437
+ """Get a skill by name."""
438
+ if name in self.primitives:
439
+ return self.primitives[name]
440
+ if name in self.compounds:
441
+ return self.compounds[name]
442
+ if name in self.behaviors:
443
+ return self.behaviors[name]
444
+ return None
445
+
446
+ def execute_primitive(self, name: str, speed_factor: float = 1.0) -> bool:
447
+ """Execute a primitive skill."""
448
+ if not self.robot:
449
+ print(f"No robot connected - would execute: {name}")
450
+ return False
451
+
452
+ prim = self.primitives.get(name)
453
+ if not prim:
454
+ print(f"Unknown primitive: {name}")
455
+ return False
456
+
457
+ duration = int(prim.duration_ms / speed_factor)
458
+ for servo_id, position in prim.servo_targets.items():
459
+ self.robot.set_servo(servo_id, position, duration)
460
+
461
+ time.sleep(duration / 1000 + 0.05)
462
+ return True
463
+
464
+ def execute_compound(self, name: str, speed_factor: float = 1.0) -> bool:
465
+ """Execute a compound skill."""
466
+ compound = self.compounds.get(name)
467
+ if not compound:
468
+ print(f"Unknown compound: {name}")
469
+ return False
470
+
471
+ print(f"Executing compound: {name}")
472
+ for step_name in compound.steps:
473
+ print(f" Step: {step_name}")
474
+ if not self.execute_primitive(step_name, speed_factor):
475
+ return False
476
+ time.sleep(compound.wait_between_ms / 1000)
477
+
478
+ return True
479
+
480
+ def execute(self, name: str, speed_factor: float = 1.0) -> bool:
481
+ """Execute any skill by name."""
482
+ if name in self.primitives:
483
+ return self.execute_primitive(name, speed_factor)
484
+ if name in self.compounds:
485
+ return self.execute_compound(name, speed_factor)
486
+ if name in self.behaviors:
487
+ print(f"Behavior execution requires perception - use execute_behavior()")
488
+ return False
489
+
490
+ print(f"Unknown skill: {name}")
491
+ return False
492
+
493
+ def export_all(self) -> Dict:
494
+ """Export all skills as serializable dict."""
495
+ return {
496
+ "primitives": {name: p.to_dict() for name, p in self.primitives.items()},
497
+ "compounds": {name: c.to_dict() for name, c in self.compounds.items()},
498
+ "behaviors": {name: b.to_dict() for name, b in self.behaviors.items()},
499
+ }
500
+
501
+ def save(self, path: Path):
502
+ """Save library to JSON file."""
503
+ with open(path, 'w') as f:
504
+ json.dump(self.export_all(), f, indent=2)
505
+
506
+ def generate_python_code(self, include_behaviors: bool = False) -> str:
507
+ """Generate Python code for all skills."""
508
+ lines = [
509
+ '"""',
510
+ 'Auto-generated MechDog skill primitives.',
511
+ '',
512
+ 'Usage:',
513
+ ' from mechdog_skills import *',
514
+ ' ',
515
+ ' # Execute primitives',
516
+ ' gripper_open(robot)',
517
+ ' arm_down(robot)',
518
+ ' ',
519
+ ' # Execute compounds',
520
+ ' pickup(robot)',
521
+ ' tilt_pickup(robot)',
522
+ '"""',
523
+ '',
524
+ 'import time',
525
+ 'from typing import Optional',
526
+ '',
527
+ '',
528
+ '# =============================================================================',
529
+ '# Primitives',
530
+ '# =============================================================================',
531
+ '',
532
+ ]
533
+
534
+ # Generate primitive functions
535
+ for name, prim in self.primitives.items():
536
+ lines.append(f'def {name}(robot, speed_factor: float = 1.0) -> bool:')
537
+ lines.append(f' """{prim.description}"""')
538
+ lines.append(f' duration = int({prim.duration_ms} / speed_factor)')
539
+ for servo_id, pos in prim.servo_targets.items():
540
+ servo_info = MECHDOG_SERVOS.get(servo_id, {})
541
+ servo_name = servo_info.get("name", f"servo_{servo_id}")
542
+ lines.append(f' robot.set_servo({servo_id}, {pos}, duration) # {servo_name}')
543
+ lines.append(f' time.sleep(duration / 1000 + 0.05)')
544
+ lines.append(f' return True')
545
+ lines.append('')
546
+ lines.append('')
547
+
548
+ # Generate compound functions
549
+ lines.append('# =============================================================================')
550
+ lines.append('# Compound Skills')
551
+ lines.append('# =============================================================================')
552
+ lines.append('')
553
+
554
+ for name, compound in self.compounds.items():
555
+ lines.append(f'def {name}(robot, speed_factor: float = 1.0) -> bool:')
556
+ lines.append(f' """{compound.description}"""')
557
+ for step in compound.steps:
558
+ lines.append(f' {step}(robot, speed_factor)')
559
+ lines.append(f' time.sleep({compound.wait_between_ms} / 1000)')
560
+ lines.append(f' return True')
561
+ lines.append('')
562
+ lines.append('')
563
+
564
+ if include_behaviors:
565
+ lines.append('# =============================================================================')
566
+ lines.append('# Behaviors (require perception)')
567
+ lines.append('# =============================================================================')
568
+ lines.append('')
569
+
570
+ for name, behavior in self.behaviors.items():
571
+ lines.append(f'def {name}(robot, detector, speed_factor: float = 1.0) -> bool:')
572
+ lines.append(f' """')
573
+ lines.append(f' {behavior.description}')
574
+ lines.append(f' ')
575
+ lines.append(f' Requires: {", ".join(h.value for h in behavior.hardware)}')
576
+ lines.append(f' """')
577
+ lines.append(f' # TODO: Implement perception-based behavior')
578
+ lines.append(f' # Steps: {behavior.steps}')
579
+ lines.append(f' raise NotImplementedError("Behavior requires perception integration")')
580
+ lines.append('')
581
+ lines.append('')
582
+
583
+ return '\n'.join(lines)
584
+
585
+
586
+ # =============================================================================
587
+ # CLI integration
588
+ # =============================================================================
589
+
590
+ def list_primitives():
591
+ """List all available primitives."""
592
+ lib = PrimitiveLibrary()
593
+ all_skills = lib.list_all()
594
+
595
+ print("\n=== PRIMITIVES ===")
596
+ for name in all_skills["primitives"]:
597
+ prim = lib.primitives[name]
598
+ print(f" {name}: {prim.description}")
599
+
600
+ print("\n=== COMPOUNDS ===")
601
+ for name in all_skills["compounds"]:
602
+ comp = lib.compounds[name]
603
+ print(f" {name}: {comp.description}")
604
+ print(f" Steps: {' → '.join(comp.steps)}")
605
+
606
+ print("\n=== BEHAVIORS ===")
607
+ for name in all_skills["behaviors"]:
608
+ beh = lib.behaviors[name]
609
+ print(f" {name}: {beh.description}")
610
+ print(f" Requires: {', '.join(h.value for h in beh.hardware)}")
611
+
612
+
613
+ if __name__ == "__main__":
614
+ list_primitives()