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.
- 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 +100 -0
- ate/behaviors/approach.py +399 -0
- ate/behaviors/common.py +686 -0
- ate/behaviors/tree.py +454 -0
- ate/cli.py +855 -3995
- 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 +39 -0
- ate/drivers/ble_transport.py +405 -0
- ate/drivers/mechdog.py +942 -0
- ate/drivers/wifi_camera.py +477 -0
- ate/interfaces/__init__.py +187 -0
- ate/interfaces/base.py +273 -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/sensors.py +247 -0
- ate/interfaces/types.py +371 -0
- ate/llm_proxy.py +239 -0
- ate/mcp_server.py +387 -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 +83 -0
- ate/recording/demonstration.py +378 -0
- ate/recording/session.py +415 -0
- ate/recording/upload.py +304 -0
- ate/recording/visual.py +416 -0
- ate/recording/wrapper.py +95 -0
- ate/robot/__init__.py +221 -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 +668 -0
- ate/robot/calibration_state.py +388 -0
- ate/robot/commands.py +3735 -0
- ate/robot/direction_calibration.py +554 -0
- ate/robot/discovery.py +441 -0
- ate/robot/introspection.py +330 -0
- ate/robot/llm_system_id.py +654 -0
- ate/robot/locomotion_calibration.py +508 -0
- ate/robot/manager.py +270 -0
- ate/robot/marker_generator.py +611 -0
- ate/robot/perception.py +502 -0
- ate/robot/primitives.py +614 -0
- ate/robot/profiles.py +281 -0
- ate/robot/registry.py +322 -0
- ate/robot/servo_mapper.py +1153 -0
- ate/robot/skill_upload.py +675 -0
- ate/robot/target_calibration.py +500 -0
- ate/robot/teach.py +515 -0
- ate/robot/types.py +242 -0
- ate/robot/visual_labeler.py +1048 -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.7.dist-info → foodforthought_cli-0.3.0.dist-info}/METADATA +9 -1
- foodforthought_cli-0.3.0.dist-info/RECORD +166 -0
- {foodforthought_cli-0.2.7.dist-info → foodforthought_cli-0.3.0.dist-info}/WHEEL +1 -1
- foodforthought_cli-0.2.7.dist-info/RECORD +0 -44
- {foodforthought_cli-0.2.7.dist-info → foodforthought_cli-0.3.0.dist-info}/entry_points.txt +0 -0
- {foodforthought_cli-0.2.7.dist-info → foodforthought_cli-0.3.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Robot capability introspection.
|
|
3
|
+
|
|
4
|
+
Inspect what a robot can do by examining its interfaces and methods.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import inspect
|
|
8
|
+
from typing import Dict, List, Set, Any, Optional, Type
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
|
|
11
|
+
from ..interfaces import (
|
|
12
|
+
RobotInterface,
|
|
13
|
+
SafetyInterface,
|
|
14
|
+
QuadrupedLocomotion,
|
|
15
|
+
BipedLocomotion,
|
|
16
|
+
WheeledLocomotion,
|
|
17
|
+
AerialLocomotion,
|
|
18
|
+
ArmInterface,
|
|
19
|
+
GripperInterface,
|
|
20
|
+
CameraInterface,
|
|
21
|
+
DepthCameraInterface,
|
|
22
|
+
LidarInterface,
|
|
23
|
+
IMUInterface,
|
|
24
|
+
BodyPoseInterface,
|
|
25
|
+
NavigationInterface,
|
|
26
|
+
ObjectDetectionInterface,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass
|
|
31
|
+
class MethodInfo:
|
|
32
|
+
"""Information about a robot method."""
|
|
33
|
+
name: str
|
|
34
|
+
interface: str
|
|
35
|
+
description: str
|
|
36
|
+
parameters: List[str]
|
|
37
|
+
return_type: str
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@dataclass
|
|
41
|
+
class CapabilityInfo:
|
|
42
|
+
"""Information about a robot capability."""
|
|
43
|
+
name: str
|
|
44
|
+
interface_class: str
|
|
45
|
+
description: str
|
|
46
|
+
methods: List[MethodInfo]
|
|
47
|
+
available: bool = True
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
# Map interfaces to capability names
|
|
51
|
+
INTERFACE_CAPABILITIES = {
|
|
52
|
+
QuadrupedLocomotion: "quadruped_locomotion",
|
|
53
|
+
BipedLocomotion: "bipedal_locomotion",
|
|
54
|
+
WheeledLocomotion: "wheeled_locomotion",
|
|
55
|
+
AerialLocomotion: "aerial_locomotion",
|
|
56
|
+
ArmInterface: "arm",
|
|
57
|
+
GripperInterface: "gripper",
|
|
58
|
+
CameraInterface: "camera",
|
|
59
|
+
DepthCameraInterface: "depth_camera",
|
|
60
|
+
LidarInterface: "lidar",
|
|
61
|
+
IMUInterface: "imu",
|
|
62
|
+
BodyPoseInterface: "body_pose",
|
|
63
|
+
SafetyInterface: "safety",
|
|
64
|
+
NavigationInterface: "navigation",
|
|
65
|
+
ObjectDetectionInterface: "object_detection",
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def get_capabilities(robot: Any) -> Dict[str, CapabilityInfo]:
|
|
70
|
+
"""
|
|
71
|
+
Get all capabilities of a robot instance.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
robot: Robot instance
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
Dict mapping capability name to CapabilityInfo
|
|
78
|
+
"""
|
|
79
|
+
capabilities = {}
|
|
80
|
+
|
|
81
|
+
for interface, cap_name in INTERFACE_CAPABILITIES.items():
|
|
82
|
+
if isinstance(robot, interface):
|
|
83
|
+
methods = get_interface_methods(interface)
|
|
84
|
+
capabilities[cap_name] = CapabilityInfo(
|
|
85
|
+
name=cap_name,
|
|
86
|
+
interface_class=interface.__name__,
|
|
87
|
+
description=interface.__doc__ or "",
|
|
88
|
+
methods=methods,
|
|
89
|
+
available=True,
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
return capabilities
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def get_methods(robot: Any) -> Dict[str, List[MethodInfo]]:
|
|
96
|
+
"""
|
|
97
|
+
Get all available methods grouped by capability.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
robot: Robot instance
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
Dict mapping capability name to list of methods
|
|
104
|
+
"""
|
|
105
|
+
result = {}
|
|
106
|
+
|
|
107
|
+
for interface, cap_name in INTERFACE_CAPABILITIES.items():
|
|
108
|
+
if isinstance(robot, interface):
|
|
109
|
+
methods = get_interface_methods(interface)
|
|
110
|
+
if methods:
|
|
111
|
+
result[cap_name] = methods
|
|
112
|
+
|
|
113
|
+
return result
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def get_interface_methods(interface: Type) -> List[MethodInfo]:
|
|
117
|
+
"""
|
|
118
|
+
Get methods defined by an interface.
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
interface: Interface class
|
|
122
|
+
|
|
123
|
+
Returns:
|
|
124
|
+
List of MethodInfo
|
|
125
|
+
"""
|
|
126
|
+
methods = []
|
|
127
|
+
|
|
128
|
+
for name, method in inspect.getmembers(interface, predicate=inspect.isfunction):
|
|
129
|
+
if name.startswith("_"):
|
|
130
|
+
continue
|
|
131
|
+
|
|
132
|
+
# Get signature
|
|
133
|
+
try:
|
|
134
|
+
sig = inspect.signature(method)
|
|
135
|
+
params = [
|
|
136
|
+
p.name for p in sig.parameters.values()
|
|
137
|
+
if p.name != "self"
|
|
138
|
+
]
|
|
139
|
+
return_type = str(sig.return_annotation) if sig.return_annotation != inspect.Signature.empty else "None"
|
|
140
|
+
except (ValueError, TypeError):
|
|
141
|
+
params = []
|
|
142
|
+
return_type = "Unknown"
|
|
143
|
+
|
|
144
|
+
# Clean up return type
|
|
145
|
+
return_type = return_type.replace("typing.", "").replace("<class '", "").replace("'>", "")
|
|
146
|
+
|
|
147
|
+
methods.append(MethodInfo(
|
|
148
|
+
name=name,
|
|
149
|
+
interface=interface.__name__,
|
|
150
|
+
description=method.__doc__ or "",
|
|
151
|
+
parameters=params,
|
|
152
|
+
return_type=return_type,
|
|
153
|
+
))
|
|
154
|
+
|
|
155
|
+
return methods
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def test_capability(robot: Any, capability: str) -> Dict[str, Any]:
|
|
159
|
+
"""
|
|
160
|
+
Test if a capability is working.
|
|
161
|
+
|
|
162
|
+
Args:
|
|
163
|
+
robot: Robot instance
|
|
164
|
+
capability: Capability name to test
|
|
165
|
+
|
|
166
|
+
Returns:
|
|
167
|
+
Test result with status and details
|
|
168
|
+
"""
|
|
169
|
+
result = {
|
|
170
|
+
"capability": capability,
|
|
171
|
+
"status": "unknown",
|
|
172
|
+
"tests": [],
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
# Find the interface for this capability
|
|
176
|
+
interface = None
|
|
177
|
+
for iface, cap_name in INTERFACE_CAPABILITIES.items():
|
|
178
|
+
if cap_name == capability:
|
|
179
|
+
interface = iface
|
|
180
|
+
break
|
|
181
|
+
|
|
182
|
+
if interface is None:
|
|
183
|
+
result["status"] = "error"
|
|
184
|
+
result["error"] = f"Unknown capability: {capability}"
|
|
185
|
+
return result
|
|
186
|
+
|
|
187
|
+
if not isinstance(robot, interface):
|
|
188
|
+
result["status"] = "not_available"
|
|
189
|
+
result["error"] = f"Robot does not have capability: {capability}"
|
|
190
|
+
return result
|
|
191
|
+
|
|
192
|
+
# Run capability-specific tests
|
|
193
|
+
try:
|
|
194
|
+
if capability == "camera":
|
|
195
|
+
result["tests"].append(_test_camera(robot))
|
|
196
|
+
elif capability == "quadruped_locomotion":
|
|
197
|
+
result["tests"].append(_test_locomotion(robot))
|
|
198
|
+
elif capability == "body_pose":
|
|
199
|
+
result["tests"].append(_test_body_pose(robot))
|
|
200
|
+
elif capability == "safety":
|
|
201
|
+
result["tests"].append(_test_safety(robot))
|
|
202
|
+
else:
|
|
203
|
+
result["tests"].append({
|
|
204
|
+
"name": "basic_check",
|
|
205
|
+
"status": "passed",
|
|
206
|
+
"message": f"Robot implements {interface.__name__}",
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
# Determine overall status
|
|
210
|
+
if all(t.get("status") == "passed" for t in result["tests"]):
|
|
211
|
+
result["status"] = "passed"
|
|
212
|
+
elif any(t.get("status") == "failed" for t in result["tests"]):
|
|
213
|
+
result["status"] = "failed"
|
|
214
|
+
else:
|
|
215
|
+
result["status"] = "partial"
|
|
216
|
+
|
|
217
|
+
except Exception as e:
|
|
218
|
+
result["status"] = "error"
|
|
219
|
+
result["error"] = str(e)
|
|
220
|
+
|
|
221
|
+
return result
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def _test_camera(robot: Any) -> Dict[str, Any]:
|
|
225
|
+
"""Test camera capability."""
|
|
226
|
+
test = {"name": "camera_capture", "status": "unknown"}
|
|
227
|
+
|
|
228
|
+
try:
|
|
229
|
+
image = robot.get_image()
|
|
230
|
+
if image.width > 0 and image.height > 0:
|
|
231
|
+
test["status"] = "passed"
|
|
232
|
+
test["message"] = f"Captured {image.width}x{image.height} image"
|
|
233
|
+
else:
|
|
234
|
+
test["status"] = "failed"
|
|
235
|
+
test["message"] = "Image capture returned empty"
|
|
236
|
+
except Exception as e:
|
|
237
|
+
test["status"] = "failed"
|
|
238
|
+
test["error"] = str(e)
|
|
239
|
+
|
|
240
|
+
return test
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def _test_locomotion(robot: Any) -> Dict[str, Any]:
|
|
244
|
+
"""Test locomotion capability (non-destructive)."""
|
|
245
|
+
test = {"name": "locomotion_status", "status": "unknown"}
|
|
246
|
+
|
|
247
|
+
try:
|
|
248
|
+
if hasattr(robot, "is_moving"):
|
|
249
|
+
is_moving = robot.is_moving()
|
|
250
|
+
test["status"] = "passed"
|
|
251
|
+
test["message"] = f"Robot is {'moving' if is_moving else 'stationary'}"
|
|
252
|
+
else:
|
|
253
|
+
test["status"] = "passed"
|
|
254
|
+
test["message"] = "Locomotion interface available"
|
|
255
|
+
except Exception as e:
|
|
256
|
+
test["status"] = "failed"
|
|
257
|
+
test["error"] = str(e)
|
|
258
|
+
|
|
259
|
+
return test
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def _test_body_pose(robot: Any) -> Dict[str, Any]:
|
|
263
|
+
"""Test body pose capability."""
|
|
264
|
+
test = {"name": "body_pose_read", "status": "unknown"}
|
|
265
|
+
|
|
266
|
+
try:
|
|
267
|
+
height = robot.get_body_height()
|
|
268
|
+
test["status"] = "passed"
|
|
269
|
+
test["message"] = f"Body height: {height:.3f}m"
|
|
270
|
+
except Exception as e:
|
|
271
|
+
test["status"] = "failed"
|
|
272
|
+
test["error"] = str(e)
|
|
273
|
+
|
|
274
|
+
return test
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def _test_safety(robot: Any) -> Dict[str, Any]:
|
|
278
|
+
"""Test safety capability."""
|
|
279
|
+
test = {"name": "safety_status", "status": "unknown"}
|
|
280
|
+
|
|
281
|
+
try:
|
|
282
|
+
estopped = robot.is_estopped()
|
|
283
|
+
test["status"] = "passed"
|
|
284
|
+
test["message"] = f"E-stop: {'active' if estopped else 'inactive'}"
|
|
285
|
+
except Exception as e:
|
|
286
|
+
test["status"] = "failed"
|
|
287
|
+
test["error"] = str(e)
|
|
288
|
+
|
|
289
|
+
return test
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def format_capabilities(capabilities: Dict[str, CapabilityInfo]) -> str:
|
|
293
|
+
"""Format capabilities for CLI display."""
|
|
294
|
+
lines = []
|
|
295
|
+
|
|
296
|
+
for name, info in capabilities.items():
|
|
297
|
+
lines.append(f"\n{name.upper()}")
|
|
298
|
+
lines.append("-" * len(name))
|
|
299
|
+
|
|
300
|
+
if info.description:
|
|
301
|
+
first_line = info.description.strip().split("\n")[0]
|
|
302
|
+
lines.append(f" {first_line}")
|
|
303
|
+
|
|
304
|
+
lines.append(" Methods:")
|
|
305
|
+
for method in info.methods[:5]: # Show first 5 methods
|
|
306
|
+
params = ", ".join(method.parameters[:3]) # Show first 3 params
|
|
307
|
+
if len(method.parameters) > 3:
|
|
308
|
+
params += ", ..."
|
|
309
|
+
lines.append(f" - {method.name}({params})")
|
|
310
|
+
|
|
311
|
+
if len(info.methods) > 5:
|
|
312
|
+
lines.append(f" ... and {len(info.methods) - 5} more")
|
|
313
|
+
|
|
314
|
+
return "\n".join(lines)
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def format_methods(methods: Dict[str, List[MethodInfo]]) -> str:
|
|
318
|
+
"""Format methods for CLI display."""
|
|
319
|
+
lines = []
|
|
320
|
+
|
|
321
|
+
for capability, method_list in methods.items():
|
|
322
|
+
lines.append(f"\n[{capability}]")
|
|
323
|
+
for method in method_list:
|
|
324
|
+
params = ", ".join(method.parameters)
|
|
325
|
+
lines.append(f" {method.name}({params}) -> {method.return_type}")
|
|
326
|
+
if method.description:
|
|
327
|
+
first_line = method.description.strip().split("\n")[0][:60]
|
|
328
|
+
lines.append(f" {first_line}")
|
|
329
|
+
|
|
330
|
+
return "\n".join(lines)
|