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.
Files changed (46) hide show
  1. ate/__init__.py +1 -1
  2. ate/bridge_server.py +622 -0
  3. ate/cli.py +2625 -242
  4. ate/compatibility.py +580 -0
  5. ate/generators/__init__.py +19 -0
  6. ate/generators/docker_generator.py +461 -0
  7. ate/generators/hardware_config.py +469 -0
  8. ate/generators/ros2_generator.py +617 -0
  9. ate/generators/skill_generator.py +783 -0
  10. ate/marketplace.py +524 -0
  11. ate/mcp_server.py +2424 -148
  12. ate/primitives.py +1016 -0
  13. ate/robot_setup.py +2222 -0
  14. ate/skill_schema.py +537 -0
  15. ate/telemetry/__init__.py +33 -0
  16. ate/telemetry/cli.py +455 -0
  17. ate/telemetry/collector.py +444 -0
  18. ate/telemetry/context.py +318 -0
  19. ate/telemetry/fleet_agent.py +419 -0
  20. ate/telemetry/formats/__init__.py +18 -0
  21. ate/telemetry/formats/hdf5_serializer.py +503 -0
  22. ate/telemetry/formats/mcap_serializer.py +457 -0
  23. ate/telemetry/types.py +334 -0
  24. foodforthought_cli-0.2.3.dist-info/METADATA +300 -0
  25. foodforthought_cli-0.2.3.dist-info/RECORD +44 -0
  26. foodforthought_cli-0.2.3.dist-info/top_level.txt +6 -0
  27. mechdog_labeled/__init__.py +3 -0
  28. mechdog_labeled/primitives.py +113 -0
  29. mechdog_labeled/servo_map.py +209 -0
  30. mechdog_output/__init__.py +3 -0
  31. mechdog_output/primitives.py +59 -0
  32. mechdog_output/servo_map.py +203 -0
  33. test_autodetect/__init__.py +3 -0
  34. test_autodetect/primitives.py +113 -0
  35. test_autodetect/servo_map.py +209 -0
  36. test_full_auto/__init__.py +3 -0
  37. test_full_auto/primitives.py +113 -0
  38. test_full_auto/servo_map.py +209 -0
  39. test_smart_detect/__init__.py +3 -0
  40. test_smart_detect/primitives.py +113 -0
  41. test_smart_detect/servo_map.py +209 -0
  42. foodforthought_cli-0.2.0.dist-info/METADATA +0 -151
  43. foodforthought_cli-0.2.0.dist-info/RECORD +0 -9
  44. foodforthought_cli-0.2.0.dist-info/top_level.txt +0 -1
  45. {foodforthought_cli-0.2.0.dist-info → foodforthought_cli-0.2.3.dist-info}/WHEEL +0 -0
  46. {foodforthought_cli-0.2.0.dist-info → foodforthought_cli-0.2.3.dist-info}/entry_points.txt +0 -0
@@ -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