foodforthought-cli 0.2.0__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.
- ate/__init__.py +1 -1
- ate/bridge_server.py +622 -0
- ate/cli.py +2625 -242
- ate/compatibility.py +580 -0
- ate/generators/__init__.py +19 -0
- ate/generators/docker_generator.py +461 -0
- ate/generators/hardware_config.py +469 -0
- ate/generators/ros2_generator.py +617 -0
- ate/generators/skill_generator.py +783 -0
- ate/marketplace.py +524 -0
- ate/mcp_server.py +2424 -148
- ate/primitives.py +1016 -0
- ate/robot_setup.py +2222 -0
- ate/skill_schema.py +537 -0
- ate/telemetry/__init__.py +33 -0
- ate/telemetry/cli.py +455 -0
- ate/telemetry/collector.py +444 -0
- ate/telemetry/context.py +318 -0
- ate/telemetry/fleet_agent.py +419 -0
- ate/telemetry/formats/__init__.py +18 -0
- ate/telemetry/formats/hdf5_serializer.py +503 -0
- ate/telemetry/formats/mcap_serializer.py +457 -0
- ate/telemetry/types.py +334 -0
- foodforthought_cli-0.2.3.dist-info/METADATA +300 -0
- foodforthought_cli-0.2.3.dist-info/RECORD +44 -0
- foodforthought_cli-0.2.3.dist-info/top_level.txt +6 -0
- mechdog_labeled/__init__.py +3 -0
- mechdog_labeled/primitives.py +113 -0
- mechdog_labeled/servo_map.py +209 -0
- mechdog_output/__init__.py +3 -0
- mechdog_output/primitives.py +59 -0
- mechdog_output/servo_map.py +203 -0
- test_autodetect/__init__.py +3 -0
- test_autodetect/primitives.py +113 -0
- test_autodetect/servo_map.py +209 -0
- test_full_auto/__init__.py +3 -0
- test_full_auto/primitives.py +113 -0
- test_full_auto/servo_map.py +209 -0
- test_smart_detect/__init__.py +3 -0
- test_smart_detect/primitives.py +113 -0
- test_smart_detect/servo_map.py +209 -0
- foodforthought_cli-0.2.0.dist-info/METADATA +0 -151
- foodforthought_cli-0.2.0.dist-info/RECORD +0 -9
- foodforthought_cli-0.2.0.dist-info/top_level.txt +0 -1
- {foodforthought_cli-0.2.0.dist-info → foodforthought_cli-0.2.3.dist-info}/WHEEL +0 -0
- {foodforthought_cli-0.2.0.dist-info → foodforthought_cli-0.2.3.dist-info}/entry_points.txt +0 -0
ate/telemetry/context.py
ADDED
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Context Manager for Telemetry Recording
|
|
3
|
+
|
|
4
|
+
Provides a convenient context manager API for trajectory recording.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from contextlib import contextmanager
|
|
8
|
+
from typing import Dict, List, Optional, Any, Generator
|
|
9
|
+
|
|
10
|
+
from .collector import TelemetryCollector
|
|
11
|
+
from .types import (
|
|
12
|
+
TrajectoryRecording,
|
|
13
|
+
Pose,
|
|
14
|
+
Contact,
|
|
15
|
+
EventType,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class RecordingContext:
|
|
20
|
+
"""
|
|
21
|
+
Context object yielded by record_trajectory context manager.
|
|
22
|
+
|
|
23
|
+
Provides methods for recording frames and events during execution.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def __init__(self, collector: TelemetryCollector):
|
|
27
|
+
self._collector = collector
|
|
28
|
+
self.success = True
|
|
29
|
+
self._error: Optional[Exception] = None
|
|
30
|
+
|
|
31
|
+
def record_frame(
|
|
32
|
+
self,
|
|
33
|
+
joint_positions: Dict[str, float],
|
|
34
|
+
joint_velocities: Optional[Dict[str, float]] = None,
|
|
35
|
+
joint_torques: Optional[Dict[str, float]] = None,
|
|
36
|
+
joint_accelerations: Optional[Dict[str, float]] = None,
|
|
37
|
+
end_effector_pose: Optional[Pose] = None,
|
|
38
|
+
contacts: Optional[List[Contact]] = None,
|
|
39
|
+
sensor_readings: Optional[Dict[str, float]] = None,
|
|
40
|
+
control_inputs: Optional[Dict[str, float]] = None,
|
|
41
|
+
timestamp: Optional[float] = None,
|
|
42
|
+
) -> None:
|
|
43
|
+
"""
|
|
44
|
+
Record a single frame of trajectory data.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
joint_positions: Joint name -> position (radians or meters)
|
|
48
|
+
joint_velocities: Joint name -> velocity
|
|
49
|
+
joint_torques: Joint name -> torque
|
|
50
|
+
joint_accelerations: Joint name -> acceleration
|
|
51
|
+
end_effector_pose: Pose of the end effector
|
|
52
|
+
contacts: List of contacts detected this frame
|
|
53
|
+
sensor_readings: Sensor name -> value
|
|
54
|
+
control_inputs: Control signal name -> value
|
|
55
|
+
timestamp: Override timestamp (defaults to time since recording start)
|
|
56
|
+
"""
|
|
57
|
+
self._collector.record_frame(
|
|
58
|
+
joint_positions=joint_positions,
|
|
59
|
+
joint_velocities=joint_velocities,
|
|
60
|
+
joint_torques=joint_torques,
|
|
61
|
+
joint_accelerations=joint_accelerations,
|
|
62
|
+
end_effector_pose=end_effector_pose,
|
|
63
|
+
contacts=contacts,
|
|
64
|
+
sensor_readings=sensor_readings,
|
|
65
|
+
control_inputs=control_inputs,
|
|
66
|
+
timestamp=timestamp,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
def log_event(
|
|
70
|
+
self,
|
|
71
|
+
event_type: EventType,
|
|
72
|
+
data: Optional[Dict[str, Any]] = None,
|
|
73
|
+
) -> None:
|
|
74
|
+
"""
|
|
75
|
+
Log an execution event.
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
event_type: Type of event (EventType enum or string)
|
|
79
|
+
data: Additional event data
|
|
80
|
+
"""
|
|
81
|
+
self._collector.log_event(event_type, data)
|
|
82
|
+
|
|
83
|
+
def log_contact(self, force: float, body1: str = "gripper", body2: str = "object") -> None:
|
|
84
|
+
"""Convenience method to log a contact event."""
|
|
85
|
+
self.log_event(EventType.CONTACT, {
|
|
86
|
+
"force": force,
|
|
87
|
+
"body1": body1,
|
|
88
|
+
"body2": body2,
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
def log_grasp(self, force: float, object_id: Optional[str] = None) -> None:
|
|
92
|
+
"""Convenience method to log a grasp event."""
|
|
93
|
+
self.log_event(EventType.GRASP, {
|
|
94
|
+
"force": force,
|
|
95
|
+
"objectId": object_id,
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
def log_release(self, object_id: Optional[str] = None) -> None:
|
|
99
|
+
"""Convenience method to log a release event."""
|
|
100
|
+
self.log_event(EventType.RELEASE, {
|
|
101
|
+
"objectId": object_id,
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
def log_waypoint(self, waypoint_id: str, pose: Optional[Pose] = None) -> None:
|
|
105
|
+
"""Convenience method to log a waypoint reached event."""
|
|
106
|
+
data = {"waypointId": waypoint_id}
|
|
107
|
+
if pose:
|
|
108
|
+
data["pose"] = pose.to_dict()
|
|
109
|
+
self.log_event(EventType.WAYPOINT_REACHED, data)
|
|
110
|
+
|
|
111
|
+
def log_error(self, error: str, recoverable: bool = True) -> None:
|
|
112
|
+
"""Convenience method to log an error event."""
|
|
113
|
+
self.log_event(EventType.ERROR, {
|
|
114
|
+
"error": error,
|
|
115
|
+
"recoverable": recoverable,
|
|
116
|
+
})
|
|
117
|
+
if not recoverable:
|
|
118
|
+
self.success = False
|
|
119
|
+
|
|
120
|
+
def log_recovery(self, action: str) -> None:
|
|
121
|
+
"""Convenience method to log a recovery event."""
|
|
122
|
+
self.log_event(EventType.RECOVERY, {
|
|
123
|
+
"action": action,
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
@property
|
|
127
|
+
def recording_id(self) -> Optional[str]:
|
|
128
|
+
"""Get the current recording ID."""
|
|
129
|
+
return self._collector.current_recording_id
|
|
130
|
+
|
|
131
|
+
@property
|
|
132
|
+
def frame_count(self) -> int:
|
|
133
|
+
"""Get the current frame count."""
|
|
134
|
+
return self._collector.frame_count
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
@contextmanager
|
|
138
|
+
def record_trajectory(
|
|
139
|
+
robot_id: str,
|
|
140
|
+
skill_id: Optional[str] = None,
|
|
141
|
+
skill_params: Optional[Dict[str, Any]] = None,
|
|
142
|
+
source: str = "hardware",
|
|
143
|
+
api_url: Optional[str] = None,
|
|
144
|
+
api_key: Optional[str] = None,
|
|
145
|
+
auto_upload: bool = True,
|
|
146
|
+
project_id: Optional[str] = None,
|
|
147
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
148
|
+
) -> Generator[RecordingContext, None, None]:
|
|
149
|
+
"""
|
|
150
|
+
Context manager for trajectory recording.
|
|
151
|
+
|
|
152
|
+
Automatically handles recording lifecycle, including starting, stopping,
|
|
153
|
+
and uploading telemetry data.
|
|
154
|
+
|
|
155
|
+
Usage:
|
|
156
|
+
from ate.telemetry import record_trajectory
|
|
157
|
+
|
|
158
|
+
with record_trajectory("my_robot", skill_id="pick_and_place") as recorder:
|
|
159
|
+
while executing:
|
|
160
|
+
recorder.record_frame(
|
|
161
|
+
joint_positions=get_joint_positions(),
|
|
162
|
+
end_effector_pose=get_ee_pose(),
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
# Set success based on execution result
|
|
166
|
+
recorder.success = True
|
|
167
|
+
|
|
168
|
+
Args:
|
|
169
|
+
robot_id: Unique identifier for the robot
|
|
170
|
+
skill_id: ID of the skill being executed (optional)
|
|
171
|
+
skill_params: Parameters passed to the skill
|
|
172
|
+
source: Source of telemetry ('simulation', 'hardware', 'fleet')
|
|
173
|
+
api_url: FoodforThought API URL (defaults to env var)
|
|
174
|
+
api_key: API key for authentication (defaults to env var)
|
|
175
|
+
auto_upload: Whether to automatically upload completed recordings
|
|
176
|
+
project_id: Default project ID for artifact creation
|
|
177
|
+
metadata: Additional metadata for the recording
|
|
178
|
+
|
|
179
|
+
Yields:
|
|
180
|
+
RecordingContext: Context object for recording frames and events
|
|
181
|
+
"""
|
|
182
|
+
collector = TelemetryCollector(
|
|
183
|
+
robot_id=robot_id,
|
|
184
|
+
api_url=api_url,
|
|
185
|
+
api_key=api_key,
|
|
186
|
+
auto_upload=auto_upload,
|
|
187
|
+
project_id=project_id,
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
collector.start_recording(
|
|
191
|
+
skill_id=skill_id,
|
|
192
|
+
skill_params=skill_params,
|
|
193
|
+
source=source,
|
|
194
|
+
metadata=metadata,
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
ctx = RecordingContext(collector)
|
|
198
|
+
|
|
199
|
+
try:
|
|
200
|
+
yield ctx
|
|
201
|
+
except Exception as e:
|
|
202
|
+
ctx.success = False
|
|
203
|
+
ctx._error = e
|
|
204
|
+
collector.log_event(EventType.ERROR, {
|
|
205
|
+
"error": str(e),
|
|
206
|
+
"type": type(e).__name__,
|
|
207
|
+
"recoverable": False,
|
|
208
|
+
})
|
|
209
|
+
raise
|
|
210
|
+
finally:
|
|
211
|
+
collector.stop_recording(ctx.success)
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
# Convenience functions for common recording patterns
|
|
215
|
+
|
|
216
|
+
def record_skill_execution(
|
|
217
|
+
robot_id: str,
|
|
218
|
+
skill_id: str,
|
|
219
|
+
execute_fn,
|
|
220
|
+
skill_params: Optional[Dict[str, Any]] = None,
|
|
221
|
+
source: str = "hardware",
|
|
222
|
+
frame_rate: float = 50.0,
|
|
223
|
+
get_joint_positions=None,
|
|
224
|
+
get_joint_velocities=None,
|
|
225
|
+
get_joint_torques=None,
|
|
226
|
+
get_ee_pose=None,
|
|
227
|
+
**kwargs,
|
|
228
|
+
) -> TrajectoryRecording:
|
|
229
|
+
"""
|
|
230
|
+
Record a skill execution with automatic frame capture.
|
|
231
|
+
|
|
232
|
+
This is a higher-level function that wraps the context manager
|
|
233
|
+
and handles frame recording automatically at a specified rate.
|
|
234
|
+
|
|
235
|
+
Usage:
|
|
236
|
+
def my_skill(robot, params):
|
|
237
|
+
robot.move_to(params["target"])
|
|
238
|
+
robot.grasp()
|
|
239
|
+
return True
|
|
240
|
+
|
|
241
|
+
recording = record_skill_execution(
|
|
242
|
+
robot_id="robot-123",
|
|
243
|
+
skill_id="grasp_object",
|
|
244
|
+
execute_fn=lambda: my_skill(robot, {"target": [0.5, 0.3, 0.1]}),
|
|
245
|
+
skill_params={"target": [0.5, 0.3, 0.1]},
|
|
246
|
+
get_joint_positions=robot.get_joint_positions,
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
Args:
|
|
250
|
+
robot_id: Unique identifier for the robot
|
|
251
|
+
skill_id: ID of the skill being executed
|
|
252
|
+
execute_fn: Function to execute (returns bool for success)
|
|
253
|
+
skill_params: Parameters passed to the skill
|
|
254
|
+
source: Source of telemetry
|
|
255
|
+
frame_rate: Rate at which to record frames (Hz)
|
|
256
|
+
get_joint_positions: Function to get joint positions
|
|
257
|
+
get_joint_velocities: Function to get joint velocities
|
|
258
|
+
get_joint_torques: Function to get joint torques
|
|
259
|
+
get_ee_pose: Function to get end effector pose
|
|
260
|
+
**kwargs: Additional arguments passed to record_trajectory
|
|
261
|
+
|
|
262
|
+
Returns:
|
|
263
|
+
The completed TrajectoryRecording
|
|
264
|
+
"""
|
|
265
|
+
import threading
|
|
266
|
+
import time
|
|
267
|
+
|
|
268
|
+
recording_thread_stop = threading.Event()
|
|
269
|
+
recording_exception = None
|
|
270
|
+
|
|
271
|
+
collector = TelemetryCollector(robot_id=robot_id, **kwargs)
|
|
272
|
+
collector.start_recording(skill_id=skill_id, skill_params=skill_params, source=source)
|
|
273
|
+
|
|
274
|
+
def recording_loop():
|
|
275
|
+
nonlocal recording_exception
|
|
276
|
+
interval = 1.0 / frame_rate
|
|
277
|
+
try:
|
|
278
|
+
while not recording_thread_stop.is_set():
|
|
279
|
+
# Get current state
|
|
280
|
+
joint_positions = get_joint_positions() if get_joint_positions else {}
|
|
281
|
+
joint_velocities = get_joint_velocities() if get_joint_velocities else None
|
|
282
|
+
joint_torques = get_joint_torques() if get_joint_torques else None
|
|
283
|
+
ee_pose = get_ee_pose() if get_ee_pose else None
|
|
284
|
+
|
|
285
|
+
collector.record_frame(
|
|
286
|
+
joint_positions=joint_positions,
|
|
287
|
+
joint_velocities=joint_velocities,
|
|
288
|
+
joint_torques=joint_torques,
|
|
289
|
+
end_effector_pose=ee_pose,
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
time.sleep(interval)
|
|
293
|
+
except Exception as e:
|
|
294
|
+
recording_exception = e
|
|
295
|
+
|
|
296
|
+
# Start recording thread
|
|
297
|
+
record_thread = threading.Thread(target=recording_loop, daemon=True)
|
|
298
|
+
record_thread.start()
|
|
299
|
+
|
|
300
|
+
try:
|
|
301
|
+
# Execute the skill
|
|
302
|
+
success = execute_fn()
|
|
303
|
+
except Exception as e:
|
|
304
|
+
success = False
|
|
305
|
+
collector.log_event(EventType.ERROR, {"error": str(e)})
|
|
306
|
+
raise
|
|
307
|
+
finally:
|
|
308
|
+
# Stop recording thread
|
|
309
|
+
recording_thread_stop.set()
|
|
310
|
+
record_thread.join(timeout=1.0)
|
|
311
|
+
|
|
312
|
+
# Stop recording
|
|
313
|
+
recording = collector.stop_recording(success=success)
|
|
314
|
+
|
|
315
|
+
if recording_exception:
|
|
316
|
+
raise recording_exception
|
|
317
|
+
|
|
318
|
+
return recording
|