foodforthought-cli 0.2.7__py3-none-any.whl → 0.2.8__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/behaviors/__init__.py +88 -0
- ate/behaviors/common.py +686 -0
- ate/behaviors/tree.py +454 -0
- ate/cli.py +610 -54
- ate/drivers/__init__.py +27 -0
- ate/drivers/mechdog.py +606 -0
- ate/interfaces/__init__.py +171 -0
- ate/interfaces/base.py +271 -0
- ate/interfaces/body.py +267 -0
- ate/interfaces/detection.py +282 -0
- ate/interfaces/locomotion.py +422 -0
- ate/interfaces/manipulation.py +408 -0
- ate/interfaces/navigation.py +389 -0
- ate/interfaces/perception.py +362 -0
- ate/interfaces/types.py +371 -0
- ate/mcp_server.py +387 -0
- ate/recording/__init__.py +44 -0
- ate/recording/demonstration.py +378 -0
- ate/recording/session.py +405 -0
- ate/recording/upload.py +304 -0
- ate/recording/wrapper.py +95 -0
- ate/robot/__init__.py +79 -0
- ate/robot/calibration.py +583 -0
- ate/robot/commands.py +3603 -0
- ate/robot/discovery.py +339 -0
- ate/robot/introspection.py +330 -0
- ate/robot/manager.py +270 -0
- ate/robot/profiles.py +275 -0
- ate/robot/registry.py +319 -0
- ate/robot/skill_upload.py +393 -0
- ate/robot/visual_labeler.py +1039 -0
- {foodforthought_cli-0.2.7.dist-info → foodforthought_cli-0.2.8.dist-info}/METADATA +9 -1
- {foodforthought_cli-0.2.7.dist-info → foodforthought_cli-0.2.8.dist-info}/RECORD +36 -7
- {foodforthought_cli-0.2.7.dist-info → foodforthought_cli-0.2.8.dist-info}/WHEEL +0 -0
- {foodforthought_cli-0.2.7.dist-info → foodforthought_cli-0.2.8.dist-info}/entry_points.txt +0 -0
- {foodforthought_cli-0.2.7.dist-info → foodforthought_cli-0.2.8.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Manipulation interfaces for robot arms and grippers.
|
|
3
|
+
|
|
4
|
+
Supports:
|
|
5
|
+
- Single arms (Franka, UR5, xArm, low-cost arms)
|
|
6
|
+
- Dual arms (PR2, humanoids)
|
|
7
|
+
- Grippers (parallel jaw, vacuum, soft grippers)
|
|
8
|
+
|
|
9
|
+
Key design principle: The interface is about WHAT you want to do,
|
|
10
|
+
not HOW the hardware does it. "Move end effector to position X"
|
|
11
|
+
works whether you have a 6-DOF arm or a 7-DOF arm.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from abc import ABC, abstractmethod
|
|
15
|
+
from typing import Optional, List, Tuple
|
|
16
|
+
from enum import Enum, auto
|
|
17
|
+
|
|
18
|
+
from .types import (
|
|
19
|
+
Vector3,
|
|
20
|
+
Quaternion,
|
|
21
|
+
Pose,
|
|
22
|
+
JointState,
|
|
23
|
+
JointLimits,
|
|
24
|
+
GripperState,
|
|
25
|
+
GripperStatus,
|
|
26
|
+
ForceTorqueReading,
|
|
27
|
+
ActionResult,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class MotionType(Enum):
|
|
32
|
+
"""Type of motion for arm movements."""
|
|
33
|
+
JOINT = auto() # Move joints directly
|
|
34
|
+
LINEAR = auto() # Straight line in Cartesian space
|
|
35
|
+
ARC = auto() # Arc motion through via point
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class ArmInterface(ABC):
|
|
39
|
+
"""
|
|
40
|
+
Interface for a robot arm (manipulator).
|
|
41
|
+
|
|
42
|
+
All positions are in the robot's base frame unless otherwise specified.
|
|
43
|
+
All units are SI (meters, radians, Newtons).
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
# =========================================================================
|
|
47
|
+
# End-effector control (Cartesian space)
|
|
48
|
+
# =========================================================================
|
|
49
|
+
|
|
50
|
+
@abstractmethod
|
|
51
|
+
def get_end_effector_pose(self) -> Pose:
|
|
52
|
+
"""
|
|
53
|
+
Get current end-effector pose.
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
Pose of end-effector in base frame
|
|
57
|
+
"""
|
|
58
|
+
pass
|
|
59
|
+
|
|
60
|
+
@abstractmethod
|
|
61
|
+
def move_to_pose(
|
|
62
|
+
self,
|
|
63
|
+
pose: Pose,
|
|
64
|
+
speed: float = 0.1,
|
|
65
|
+
motion_type: MotionType = MotionType.LINEAR
|
|
66
|
+
) -> ActionResult:
|
|
67
|
+
"""
|
|
68
|
+
Move end-effector to target pose.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
pose: Target pose in base frame
|
|
72
|
+
speed: Speed in m/s
|
|
73
|
+
motion_type: Type of motion path
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
ActionResult (when motion complete)
|
|
77
|
+
"""
|
|
78
|
+
pass
|
|
79
|
+
|
|
80
|
+
@abstractmethod
|
|
81
|
+
def move_to_position(
|
|
82
|
+
self,
|
|
83
|
+
position: Vector3,
|
|
84
|
+
orientation: Optional[Quaternion] = None,
|
|
85
|
+
speed: float = 0.1
|
|
86
|
+
) -> ActionResult:
|
|
87
|
+
"""
|
|
88
|
+
Move end-effector to position, optionally with orientation.
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
position: Target position in base frame
|
|
92
|
+
orientation: Target orientation (None = keep current)
|
|
93
|
+
speed: Speed in m/s
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
ActionResult
|
|
97
|
+
"""
|
|
98
|
+
pass
|
|
99
|
+
|
|
100
|
+
def move_relative(
|
|
101
|
+
self,
|
|
102
|
+
delta: Vector3,
|
|
103
|
+
speed: float = 0.1
|
|
104
|
+
) -> ActionResult:
|
|
105
|
+
"""
|
|
106
|
+
Move end-effector relative to current position.
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
delta: Relative movement in base frame
|
|
110
|
+
speed: Speed in m/s
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
ActionResult
|
|
114
|
+
"""
|
|
115
|
+
current = self.get_end_effector_pose()
|
|
116
|
+
target = Vector3(
|
|
117
|
+
current.position.x + delta.x,
|
|
118
|
+
current.position.y + delta.y,
|
|
119
|
+
current.position.z + delta.z
|
|
120
|
+
)
|
|
121
|
+
return self.move_to_position(target, current.orientation, speed)
|
|
122
|
+
|
|
123
|
+
# =========================================================================
|
|
124
|
+
# Joint control
|
|
125
|
+
# =========================================================================
|
|
126
|
+
|
|
127
|
+
@abstractmethod
|
|
128
|
+
def get_joint_state(self) -> JointState:
|
|
129
|
+
"""
|
|
130
|
+
Get current joint state.
|
|
131
|
+
|
|
132
|
+
Returns:
|
|
133
|
+
JointState with positions, velocities, efforts
|
|
134
|
+
"""
|
|
135
|
+
pass
|
|
136
|
+
|
|
137
|
+
@abstractmethod
|
|
138
|
+
def move_to_joints(
|
|
139
|
+
self,
|
|
140
|
+
positions: List[float],
|
|
141
|
+
speed: float = 0.5
|
|
142
|
+
) -> ActionResult:
|
|
143
|
+
"""
|
|
144
|
+
Move to target joint positions.
|
|
145
|
+
|
|
146
|
+
Args:
|
|
147
|
+
positions: Target joint angles in radians
|
|
148
|
+
speed: Joint speed in rad/s
|
|
149
|
+
|
|
150
|
+
Returns:
|
|
151
|
+
ActionResult
|
|
152
|
+
"""
|
|
153
|
+
pass
|
|
154
|
+
|
|
155
|
+
@abstractmethod
|
|
156
|
+
def get_joint_limits(self) -> List[JointLimits]:
|
|
157
|
+
"""
|
|
158
|
+
Get limits for all joints.
|
|
159
|
+
|
|
160
|
+
Returns:
|
|
161
|
+
List of JointLimits for each joint
|
|
162
|
+
"""
|
|
163
|
+
pass
|
|
164
|
+
|
|
165
|
+
def get_num_joints(self) -> int:
|
|
166
|
+
"""Get number of joints in the arm."""
|
|
167
|
+
return len(self.get_joint_state().positions)
|
|
168
|
+
|
|
169
|
+
def get_joint_names(self) -> List[str]:
|
|
170
|
+
"""
|
|
171
|
+
Get joint names.
|
|
172
|
+
|
|
173
|
+
Default: joint_0, joint_1, ...
|
|
174
|
+
Override for meaningful names.
|
|
175
|
+
"""
|
|
176
|
+
return [f"joint_{i}" for i in range(self.get_num_joints())]
|
|
177
|
+
|
|
178
|
+
# =========================================================================
|
|
179
|
+
# Common poses
|
|
180
|
+
# =========================================================================
|
|
181
|
+
|
|
182
|
+
@abstractmethod
|
|
183
|
+
def go_home(self) -> ActionResult:
|
|
184
|
+
"""
|
|
185
|
+
Move to home/ready position.
|
|
186
|
+
|
|
187
|
+
Returns:
|
|
188
|
+
ActionResult
|
|
189
|
+
"""
|
|
190
|
+
pass
|
|
191
|
+
|
|
192
|
+
def go_to_named_pose(self, name: str) -> ActionResult:
|
|
193
|
+
"""
|
|
194
|
+
Move to a named pose (e.g., "home", "ready", "stow").
|
|
195
|
+
|
|
196
|
+
Override to support robot-specific poses.
|
|
197
|
+
"""
|
|
198
|
+
if name == "home":
|
|
199
|
+
return self.go_home()
|
|
200
|
+
return ActionResult.error(f"Unknown pose: {name}")
|
|
201
|
+
|
|
202
|
+
# =========================================================================
|
|
203
|
+
# Motion control
|
|
204
|
+
# =========================================================================
|
|
205
|
+
|
|
206
|
+
@abstractmethod
|
|
207
|
+
def stop(self) -> ActionResult:
|
|
208
|
+
"""Stop current motion immediately."""
|
|
209
|
+
pass
|
|
210
|
+
|
|
211
|
+
@abstractmethod
|
|
212
|
+
def is_moving(self) -> bool:
|
|
213
|
+
"""Check if arm is currently moving."""
|
|
214
|
+
pass
|
|
215
|
+
|
|
216
|
+
def set_speed_factor(self, factor: float) -> ActionResult:
|
|
217
|
+
"""
|
|
218
|
+
Set global speed factor (0.0 to 1.0).
|
|
219
|
+
|
|
220
|
+
Affects all subsequent motions.
|
|
221
|
+
"""
|
|
222
|
+
return ActionResult.error("Speed factor not supported")
|
|
223
|
+
|
|
224
|
+
# =========================================================================
|
|
225
|
+
# Force/compliance control (optional)
|
|
226
|
+
# =========================================================================
|
|
227
|
+
|
|
228
|
+
def enable_compliance(self, stiffness: Vector3) -> ActionResult:
|
|
229
|
+
"""
|
|
230
|
+
Enable compliant/impedance control.
|
|
231
|
+
|
|
232
|
+
Args:
|
|
233
|
+
stiffness: Stiffness in N/m for x, y, z
|
|
234
|
+
|
|
235
|
+
Returns:
|
|
236
|
+
ActionResult
|
|
237
|
+
"""
|
|
238
|
+
return ActionResult.error("Compliance control not supported")
|
|
239
|
+
|
|
240
|
+
def disable_compliance(self) -> ActionResult:
|
|
241
|
+
"""Disable compliant control, return to position control."""
|
|
242
|
+
return ActionResult.error("Compliance control not supported")
|
|
243
|
+
|
|
244
|
+
def get_force_torque(self) -> Optional[ForceTorqueReading]:
|
|
245
|
+
"""
|
|
246
|
+
Get force/torque at end effector.
|
|
247
|
+
|
|
248
|
+
Returns:
|
|
249
|
+
ForceTorqueReading or None if no sensor
|
|
250
|
+
"""
|
|
251
|
+
return None
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
class GripperInterface(ABC):
|
|
255
|
+
"""
|
|
256
|
+
Interface for grippers/end-effectors.
|
|
257
|
+
|
|
258
|
+
Supports various gripper types:
|
|
259
|
+
- Parallel jaw (most common)
|
|
260
|
+
- Vacuum/suction
|
|
261
|
+
- Soft grippers
|
|
262
|
+
- Multi-finger hands
|
|
263
|
+
"""
|
|
264
|
+
|
|
265
|
+
@abstractmethod
|
|
266
|
+
def grasp(self, force: float = 10.0) -> ActionResult:
|
|
267
|
+
"""
|
|
268
|
+
Close gripper to grasp an object.
|
|
269
|
+
|
|
270
|
+
Args:
|
|
271
|
+
force: Gripping force in Newtons
|
|
272
|
+
|
|
273
|
+
Returns:
|
|
274
|
+
ActionResult (success if object detected)
|
|
275
|
+
"""
|
|
276
|
+
pass
|
|
277
|
+
|
|
278
|
+
@abstractmethod
|
|
279
|
+
def release(self) -> ActionResult:
|
|
280
|
+
"""
|
|
281
|
+
Open gripper to release object.
|
|
282
|
+
|
|
283
|
+
Returns:
|
|
284
|
+
ActionResult
|
|
285
|
+
"""
|
|
286
|
+
pass
|
|
287
|
+
|
|
288
|
+
@abstractmethod
|
|
289
|
+
def get_state(self) -> GripperState:
|
|
290
|
+
"""
|
|
291
|
+
Get current gripper state.
|
|
292
|
+
|
|
293
|
+
Returns:
|
|
294
|
+
GripperState (OPEN, CLOSED, HOLDING, etc.)
|
|
295
|
+
"""
|
|
296
|
+
pass
|
|
297
|
+
|
|
298
|
+
@abstractmethod
|
|
299
|
+
def get_status(self) -> GripperStatus:
|
|
300
|
+
"""
|
|
301
|
+
Get detailed gripper status.
|
|
302
|
+
|
|
303
|
+
Returns:
|
|
304
|
+
GripperStatus with position, force, object detection
|
|
305
|
+
"""
|
|
306
|
+
pass
|
|
307
|
+
|
|
308
|
+
def set_position(self, position: float) -> ActionResult:
|
|
309
|
+
"""
|
|
310
|
+
Set gripper to specific position.
|
|
311
|
+
|
|
312
|
+
Args:
|
|
313
|
+
position: 0.0 = fully closed, 1.0 = fully open
|
|
314
|
+
|
|
315
|
+
Returns:
|
|
316
|
+
ActionResult
|
|
317
|
+
"""
|
|
318
|
+
if position < 0.5:
|
|
319
|
+
return self.grasp()
|
|
320
|
+
else:
|
|
321
|
+
return self.release()
|
|
322
|
+
|
|
323
|
+
def is_holding_object(self) -> bool:
|
|
324
|
+
"""Check if gripper is holding an object."""
|
|
325
|
+
return self.get_state() == GripperState.HOLDING
|
|
326
|
+
|
|
327
|
+
# =========================================================================
|
|
328
|
+
# Gripper-specific capabilities
|
|
329
|
+
# =========================================================================
|
|
330
|
+
|
|
331
|
+
def set_speed(self, speed: float) -> ActionResult:
|
|
332
|
+
"""
|
|
333
|
+
Set gripper open/close speed.
|
|
334
|
+
|
|
335
|
+
Args:
|
|
336
|
+
speed: Speed factor (0.0 to 1.0)
|
|
337
|
+
"""
|
|
338
|
+
return ActionResult.error("Speed control not supported")
|
|
339
|
+
|
|
340
|
+
def calibrate(self) -> ActionResult:
|
|
341
|
+
"""Calibrate gripper (find limits)."""
|
|
342
|
+
return ActionResult.error("Calibration not supported")
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
class DualArmInterface(ABC):
|
|
346
|
+
"""
|
|
347
|
+
Interface for dual-arm robots.
|
|
348
|
+
|
|
349
|
+
Adds coordination primitives beyond two separate arms.
|
|
350
|
+
"""
|
|
351
|
+
|
|
352
|
+
@abstractmethod
|
|
353
|
+
def get_left_arm(self) -> ArmInterface:
|
|
354
|
+
"""Get interface to left arm."""
|
|
355
|
+
pass
|
|
356
|
+
|
|
357
|
+
@abstractmethod
|
|
358
|
+
def get_right_arm(self) -> ArmInterface:
|
|
359
|
+
"""Get interface to right arm."""
|
|
360
|
+
pass
|
|
361
|
+
|
|
362
|
+
@abstractmethod
|
|
363
|
+
def move_both_to_poses(
|
|
364
|
+
self,
|
|
365
|
+
left_pose: Pose,
|
|
366
|
+
right_pose: Pose,
|
|
367
|
+
speed: float = 0.1
|
|
368
|
+
) -> ActionResult:
|
|
369
|
+
"""
|
|
370
|
+
Move both arms simultaneously to target poses.
|
|
371
|
+
|
|
372
|
+
Args:
|
|
373
|
+
left_pose: Target for left arm
|
|
374
|
+
right_pose: Target for right arm
|
|
375
|
+
speed: Speed in m/s
|
|
376
|
+
|
|
377
|
+
Returns:
|
|
378
|
+
ActionResult
|
|
379
|
+
"""
|
|
380
|
+
pass
|
|
381
|
+
|
|
382
|
+
def bimanual_grasp(
|
|
383
|
+
self,
|
|
384
|
+
left_pose: Pose,
|
|
385
|
+
right_pose: Pose,
|
|
386
|
+
force: float = 10.0
|
|
387
|
+
) -> ActionResult:
|
|
388
|
+
"""
|
|
389
|
+
Coordinated grasp with both arms.
|
|
390
|
+
|
|
391
|
+
Moves arms to approach poses, then closes both grippers
|
|
392
|
+
with coordinated force control.
|
|
393
|
+
"""
|
|
394
|
+
# Default implementation - override for better coordination
|
|
395
|
+
result = self.move_both_to_poses(left_pose, right_pose)
|
|
396
|
+
if not result.success:
|
|
397
|
+
return result
|
|
398
|
+
|
|
399
|
+
# Close grippers
|
|
400
|
+
left_gripper = getattr(self.get_left_arm(), 'gripper', None)
|
|
401
|
+
right_gripper = getattr(self.get_right_arm(), 'gripper', None)
|
|
402
|
+
|
|
403
|
+
if left_gripper:
|
|
404
|
+
left_gripper.grasp(force)
|
|
405
|
+
if right_gripper:
|
|
406
|
+
right_gripper.grasp(force)
|
|
407
|
+
|
|
408
|
+
return ActionResult.ok("Bimanual grasp complete")
|