foodforthought-cli 0.2.8__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.
- ate/__init__.py +6 -0
- ate/__main__.py +16 -0
- ate/auth/__init__.py +1 -0
- ate/auth/device_flow.py +141 -0
- ate/auth/token_store.py +96 -0
- ate/behaviors/__init__.py +12 -0
- ate/behaviors/approach.py +399 -0
- ate/cli.py +855 -4551
- ate/client.py +90 -0
- ate/commands/__init__.py +168 -0
- ate/commands/auth.py +389 -0
- ate/commands/bridge.py +448 -0
- ate/commands/data.py +185 -0
- ate/commands/deps.py +111 -0
- ate/commands/generate.py +384 -0
- ate/commands/memory.py +907 -0
- ate/commands/parts.py +166 -0
- ate/commands/primitive.py +399 -0
- ate/commands/protocol.py +288 -0
- ate/commands/recording.py +524 -0
- ate/commands/repo.py +154 -0
- ate/commands/simulation.py +291 -0
- ate/commands/skill.py +303 -0
- ate/commands/skills.py +487 -0
- ate/commands/team.py +147 -0
- ate/commands/workflow.py +271 -0
- ate/detection/__init__.py +38 -0
- ate/detection/base.py +142 -0
- ate/detection/color_detector.py +399 -0
- ate/detection/trash_detector.py +322 -0
- ate/drivers/__init__.py +18 -6
- ate/drivers/ble_transport.py +405 -0
- ate/drivers/mechdog.py +360 -24
- ate/drivers/wifi_camera.py +477 -0
- ate/interfaces/__init__.py +16 -0
- ate/interfaces/base.py +2 -0
- ate/interfaces/sensors.py +247 -0
- ate/llm_proxy.py +239 -0
- ate/memory/__init__.py +35 -0
- ate/memory/cloud.py +244 -0
- ate/memory/context.py +269 -0
- ate/memory/embeddings.py +184 -0
- ate/memory/export.py +26 -0
- ate/memory/merge.py +146 -0
- ate/memory/migrate/__init__.py +34 -0
- ate/memory/migrate/base.py +89 -0
- ate/memory/migrate/pipeline.py +189 -0
- ate/memory/migrate/sources/__init__.py +13 -0
- ate/memory/migrate/sources/chroma.py +170 -0
- ate/memory/migrate/sources/pinecone.py +120 -0
- ate/memory/migrate/sources/qdrant.py +110 -0
- ate/memory/migrate/sources/weaviate.py +160 -0
- ate/memory/reranker.py +353 -0
- ate/memory/search.py +26 -0
- ate/memory/store.py +548 -0
- ate/recording/__init__.py +42 -3
- ate/recording/session.py +12 -2
- ate/recording/visual.py +416 -0
- ate/robot/__init__.py +142 -0
- ate/robot/agentic_servo.py +856 -0
- ate/robot/behaviors.py +493 -0
- ate/robot/ble_capture.py +1000 -0
- ate/robot/ble_enumerate.py +506 -0
- ate/robot/calibration.py +88 -3
- ate/robot/calibration_state.py +388 -0
- ate/robot/commands.py +143 -11
- ate/robot/direction_calibration.py +554 -0
- ate/robot/discovery.py +104 -2
- ate/robot/llm_system_id.py +654 -0
- ate/robot/locomotion_calibration.py +508 -0
- ate/robot/marker_generator.py +611 -0
- ate/robot/perception.py +502 -0
- ate/robot/primitives.py +614 -0
- ate/robot/profiles.py +6 -0
- ate/robot/registry.py +5 -2
- ate/robot/servo_mapper.py +1153 -0
- ate/robot/skill_upload.py +285 -3
- ate/robot/target_calibration.py +500 -0
- ate/robot/teach.py +515 -0
- ate/robot/types.py +242 -0
- ate/robot/visual_labeler.py +9 -0
- ate/robot/visual_servo_loop.py +494 -0
- ate/robot/visual_servoing.py +570 -0
- ate/robot/visual_system_id.py +906 -0
- ate/transports/__init__.py +121 -0
- ate/transports/base.py +394 -0
- ate/transports/ble.py +405 -0
- ate/transports/hybrid.py +444 -0
- ate/transports/serial.py +345 -0
- ate/urdf/__init__.py +30 -0
- ate/urdf/capture.py +582 -0
- ate/urdf/cloud.py +491 -0
- ate/urdf/collision.py +271 -0
- ate/urdf/commands.py +708 -0
- ate/urdf/depth.py +360 -0
- ate/urdf/inertial.py +312 -0
- ate/urdf/kinematics.py +330 -0
- ate/urdf/lifting.py +415 -0
- ate/urdf/meshing.py +300 -0
- ate/urdf/models/__init__.py +110 -0
- ate/urdf/models/depth_anything.py +253 -0
- ate/urdf/models/sam2.py +324 -0
- ate/urdf/motion_analysis.py +396 -0
- ate/urdf/pipeline.py +468 -0
- ate/urdf/scale.py +256 -0
- ate/urdf/scan_session.py +411 -0
- ate/urdf/segmentation.py +299 -0
- ate/urdf/synthesis.py +319 -0
- ate/urdf/topology.py +336 -0
- ate/urdf/validation.py +371 -0
- {foodforthought_cli-0.2.8.dist-info → foodforthought_cli-0.3.0.dist-info}/METADATA +1 -1
- foodforthought_cli-0.3.0.dist-info/RECORD +166 -0
- {foodforthought_cli-0.2.8.dist-info → foodforthought_cli-0.3.0.dist-info}/WHEEL +1 -1
- foodforthought_cli-0.2.8.dist-info/RECORD +0 -73
- {foodforthought_cli-0.2.8.dist-info → foodforthought_cli-0.3.0.dist-info}/entry_points.txt +0 -0
- {foodforthought_cli-0.2.8.dist-info → foodforthought_cli-0.3.0.dist-info}/top_level.txt +0 -0
ate/robot/primitives.py
ADDED
|
@@ -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()
|
ate/robot/profiles.py
CHANGED
|
@@ -42,6 +42,12 @@ class RobotProfile:
|
|
|
42
42
|
camera_port: int = 80
|
|
43
43
|
camera_stream_port: int = 81
|
|
44
44
|
|
|
45
|
+
# BLE connection
|
|
46
|
+
use_ble: bool = False # Use BLE instead of serial
|
|
47
|
+
ble_address: Optional[str] = None # BLE device address
|
|
48
|
+
ble_service_uuid: str = "0000ffe0-0000-1000-8000-00805f9b34fb" # ESP32/HM-10 default
|
|
49
|
+
ble_char_uuid: str = "0000ffe1-0000-1000-8000-00805f9b34fb" # RX/TX characteristic
|
|
50
|
+
|
|
45
51
|
# Robot-specific config
|
|
46
52
|
has_arm: bool = False
|
|
47
53
|
has_camera: bool = False
|
ate/robot/registry.py
CHANGED
|
@@ -89,9 +89,9 @@ register_robot(RobotType(
|
|
|
89
89
|
name="MechDog",
|
|
90
90
|
manufacturer="HiWonder",
|
|
91
91
|
archetype="quadruped",
|
|
92
|
-
description="12 DOF quadruped robot with optional arm and camera. ESP32-based with MicroPython.",
|
|
92
|
+
description="12 DOF quadruped robot with optional arm and camera. ESP32-based with MicroPython. Supports USB, WiFi, and Bluetooth LE.",
|
|
93
93
|
|
|
94
|
-
connection_types={ConnectionType.SERIAL, ConnectionType.WIFI},
|
|
94
|
+
connection_types={ConnectionType.SERIAL, ConnectionType.WIFI, ConnectionType.BLUETOOTH},
|
|
95
95
|
default_connection=ConnectionType.SERIAL,
|
|
96
96
|
|
|
97
97
|
serial_patterns=[
|
|
@@ -104,6 +104,9 @@ register_robot(RobotType(
|
|
|
104
104
|
default_ports={
|
|
105
105
|
"camera_port": 80,
|
|
106
106
|
"camera_stream_port": 81,
|
|
107
|
+
# BLE configuration - ESP32 default BLE serial service
|
|
108
|
+
"ble_service_uuid": "0000ffe0-0000-1000-8000-00805f9b34fb", # HM-10 compatible
|
|
109
|
+
"ble_char_uuid": "0000ffe1-0000-1000-8000-00805f9b34fb", # RX/TX characteristic
|
|
107
110
|
},
|
|
108
111
|
|
|
109
112
|
capabilities={
|