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.
Files changed (131) hide show
  1. ate/__init__.py +6 -0
  2. ate/__main__.py +16 -0
  3. ate/auth/__init__.py +1 -0
  4. ate/auth/device_flow.py +141 -0
  5. ate/auth/token_store.py +96 -0
  6. ate/behaviors/__init__.py +100 -0
  7. ate/behaviors/approach.py +399 -0
  8. ate/behaviors/common.py +686 -0
  9. ate/behaviors/tree.py +454 -0
  10. ate/cli.py +855 -3995
  11. ate/client.py +90 -0
  12. ate/commands/__init__.py +168 -0
  13. ate/commands/auth.py +389 -0
  14. ate/commands/bridge.py +448 -0
  15. ate/commands/data.py +185 -0
  16. ate/commands/deps.py +111 -0
  17. ate/commands/generate.py +384 -0
  18. ate/commands/memory.py +907 -0
  19. ate/commands/parts.py +166 -0
  20. ate/commands/primitive.py +399 -0
  21. ate/commands/protocol.py +288 -0
  22. ate/commands/recording.py +524 -0
  23. ate/commands/repo.py +154 -0
  24. ate/commands/simulation.py +291 -0
  25. ate/commands/skill.py +303 -0
  26. ate/commands/skills.py +487 -0
  27. ate/commands/team.py +147 -0
  28. ate/commands/workflow.py +271 -0
  29. ate/detection/__init__.py +38 -0
  30. ate/detection/base.py +142 -0
  31. ate/detection/color_detector.py +399 -0
  32. ate/detection/trash_detector.py +322 -0
  33. ate/drivers/__init__.py +39 -0
  34. ate/drivers/ble_transport.py +405 -0
  35. ate/drivers/mechdog.py +942 -0
  36. ate/drivers/wifi_camera.py +477 -0
  37. ate/interfaces/__init__.py +187 -0
  38. ate/interfaces/base.py +273 -0
  39. ate/interfaces/body.py +267 -0
  40. ate/interfaces/detection.py +282 -0
  41. ate/interfaces/locomotion.py +422 -0
  42. ate/interfaces/manipulation.py +408 -0
  43. ate/interfaces/navigation.py +389 -0
  44. ate/interfaces/perception.py +362 -0
  45. ate/interfaces/sensors.py +247 -0
  46. ate/interfaces/types.py +371 -0
  47. ate/llm_proxy.py +239 -0
  48. ate/mcp_server.py +387 -0
  49. ate/memory/__init__.py +35 -0
  50. ate/memory/cloud.py +244 -0
  51. ate/memory/context.py +269 -0
  52. ate/memory/embeddings.py +184 -0
  53. ate/memory/export.py +26 -0
  54. ate/memory/merge.py +146 -0
  55. ate/memory/migrate/__init__.py +34 -0
  56. ate/memory/migrate/base.py +89 -0
  57. ate/memory/migrate/pipeline.py +189 -0
  58. ate/memory/migrate/sources/__init__.py +13 -0
  59. ate/memory/migrate/sources/chroma.py +170 -0
  60. ate/memory/migrate/sources/pinecone.py +120 -0
  61. ate/memory/migrate/sources/qdrant.py +110 -0
  62. ate/memory/migrate/sources/weaviate.py +160 -0
  63. ate/memory/reranker.py +353 -0
  64. ate/memory/search.py +26 -0
  65. ate/memory/store.py +548 -0
  66. ate/recording/__init__.py +83 -0
  67. ate/recording/demonstration.py +378 -0
  68. ate/recording/session.py +415 -0
  69. ate/recording/upload.py +304 -0
  70. ate/recording/visual.py +416 -0
  71. ate/recording/wrapper.py +95 -0
  72. ate/robot/__init__.py +221 -0
  73. ate/robot/agentic_servo.py +856 -0
  74. ate/robot/behaviors.py +493 -0
  75. ate/robot/ble_capture.py +1000 -0
  76. ate/robot/ble_enumerate.py +506 -0
  77. ate/robot/calibration.py +668 -0
  78. ate/robot/calibration_state.py +388 -0
  79. ate/robot/commands.py +3735 -0
  80. ate/robot/direction_calibration.py +554 -0
  81. ate/robot/discovery.py +441 -0
  82. ate/robot/introspection.py +330 -0
  83. ate/robot/llm_system_id.py +654 -0
  84. ate/robot/locomotion_calibration.py +508 -0
  85. ate/robot/manager.py +270 -0
  86. ate/robot/marker_generator.py +611 -0
  87. ate/robot/perception.py +502 -0
  88. ate/robot/primitives.py +614 -0
  89. ate/robot/profiles.py +281 -0
  90. ate/robot/registry.py +322 -0
  91. ate/robot/servo_mapper.py +1153 -0
  92. ate/robot/skill_upload.py +675 -0
  93. ate/robot/target_calibration.py +500 -0
  94. ate/robot/teach.py +515 -0
  95. ate/robot/types.py +242 -0
  96. ate/robot/visual_labeler.py +1048 -0
  97. ate/robot/visual_servo_loop.py +494 -0
  98. ate/robot/visual_servoing.py +570 -0
  99. ate/robot/visual_system_id.py +906 -0
  100. ate/transports/__init__.py +121 -0
  101. ate/transports/base.py +394 -0
  102. ate/transports/ble.py +405 -0
  103. ate/transports/hybrid.py +444 -0
  104. ate/transports/serial.py +345 -0
  105. ate/urdf/__init__.py +30 -0
  106. ate/urdf/capture.py +582 -0
  107. ate/urdf/cloud.py +491 -0
  108. ate/urdf/collision.py +271 -0
  109. ate/urdf/commands.py +708 -0
  110. ate/urdf/depth.py +360 -0
  111. ate/urdf/inertial.py +312 -0
  112. ate/urdf/kinematics.py +330 -0
  113. ate/urdf/lifting.py +415 -0
  114. ate/urdf/meshing.py +300 -0
  115. ate/urdf/models/__init__.py +110 -0
  116. ate/urdf/models/depth_anything.py +253 -0
  117. ate/urdf/models/sam2.py +324 -0
  118. ate/urdf/motion_analysis.py +396 -0
  119. ate/urdf/pipeline.py +468 -0
  120. ate/urdf/scale.py +256 -0
  121. ate/urdf/scan_session.py +411 -0
  122. ate/urdf/segmentation.py +299 -0
  123. ate/urdf/synthesis.py +319 -0
  124. ate/urdf/topology.py +336 -0
  125. ate/urdf/validation.py +371 -0
  126. {foodforthought_cli-0.2.7.dist-info → foodforthought_cli-0.3.0.dist-info}/METADATA +9 -1
  127. foodforthought_cli-0.3.0.dist-info/RECORD +166 -0
  128. {foodforthought_cli-0.2.7.dist-info → foodforthought_cli-0.3.0.dist-info}/WHEEL +1 -1
  129. foodforthought_cli-0.2.7.dist-info/RECORD +0 -44
  130. {foodforthought_cli-0.2.7.dist-info → foodforthought_cli-0.3.0.dist-info}/entry_points.txt +0 -0
  131. {foodforthought_cli-0.2.7.dist-info → foodforthought_cli-0.3.0.dist-info}/top_level.txt +0 -0
ate/interfaces/base.py ADDED
@@ -0,0 +1,273 @@
1
+ """
2
+ Base interfaces that all robots must implement.
3
+
4
+ Every robot, regardless of type, has:
5
+ 1. Identity - what it is, what it can do
6
+ 2. Safety - emergency stop, status monitoring
7
+ 3. Lifecycle - connect, disconnect, ready state
8
+ """
9
+
10
+ from abc import ABC, abstractmethod
11
+ from dataclasses import dataclass, field
12
+ from typing import List, Optional, Set, Type, TYPE_CHECKING
13
+ from enum import Enum, auto
14
+
15
+ from .types import RobotStatus, BatteryState, ActionResult
16
+
17
+ if TYPE_CHECKING:
18
+ from .locomotion import LocomotionInterface
19
+ from .manipulation import ArmInterface, GripperInterface
20
+ from .perception import CameraInterface, LidarInterface, IMUInterface
21
+
22
+
23
+ class Capability(Enum):
24
+ """
25
+ Capabilities a robot can have.
26
+ Used for runtime capability checking and skill compatibility.
27
+ """
28
+ # Locomotion
29
+ QUADRUPED = auto()
30
+ BIPED = auto()
31
+ WHEELED = auto()
32
+ AERIAL = auto()
33
+ AQUATIC = auto()
34
+
35
+ # Manipulation
36
+ ARM = auto()
37
+ DUAL_ARM = auto()
38
+ GRIPPER = auto()
39
+ SUCTION = auto()
40
+
41
+ # Perception
42
+ CAMERA = auto()
43
+ DEPTH_CAMERA = auto()
44
+ LIDAR = auto()
45
+ IMU = auto()
46
+ FORCE_TORQUE = auto()
47
+ TACTILE = auto()
48
+ DISTANCE_SENSOR = auto() # Ultrasonic, IR, single-point lidar
49
+ PROXIMITY_SENSOR = auto() # Bump/touch sensors
50
+
51
+ # Body control
52
+ BODY_POSE = auto()
53
+
54
+ # Communication
55
+ AUDIO_INPUT = auto()
56
+ AUDIO_OUTPUT = auto()
57
+
58
+
59
+ @dataclass
60
+ class RobotInfo:
61
+ """
62
+ Static information about a robot.
63
+ Used for compatibility checking and UI display.
64
+ """
65
+ # Identity
66
+ name: str # Human-readable name
67
+ model: str # Model identifier (e.g., "hiwonder_mechdog")
68
+ manufacturer: str # Manufacturer name
69
+ archetype: str # Primary type: "quadruped", "humanoid", "arm", etc.
70
+
71
+ # Capabilities
72
+ capabilities: Set[Capability] = field(default_factory=set)
73
+
74
+ # Physical properties
75
+ mass: Optional[float] = None # kg
76
+ payload_capacity: Optional[float] = None # kg
77
+ reach: Optional[float] = None # meters (for arms)
78
+ dimensions: Optional[tuple] = None # (length, width, height) in meters
79
+
80
+ # Workspace limits (in base frame)
81
+ workspace_min: Optional[tuple] = None # (x, y, z) min bounds
82
+ workspace_max: Optional[tuple] = None # (x, y, z) max bounds
83
+
84
+ # Performance
85
+ max_speed: Optional[float] = None # m/s
86
+ battery_capacity: Optional[float] = None # Wh
87
+
88
+ # Metadata
89
+ firmware_version: Optional[str] = None
90
+ serial_number: Optional[str] = None
91
+ description: str = ""
92
+
93
+ def has_capability(self, cap: Capability) -> bool:
94
+ return cap in self.capabilities
95
+
96
+ def has_all_capabilities(self, caps: Set[Capability]) -> bool:
97
+ return caps.issubset(self.capabilities)
98
+
99
+
100
+ class RobotInterface(ABC):
101
+ """
102
+ Base interface that ALL robots must implement.
103
+
104
+ This provides:
105
+ - Identity and capability information
106
+ - Connection lifecycle
107
+ - Status monitoring
108
+
109
+ Specific capabilities (locomotion, manipulation, etc.) are added via mixins.
110
+ """
111
+
112
+ @abstractmethod
113
+ def get_info(self) -> RobotInfo:
114
+ """
115
+ Get static information about this robot.
116
+
117
+ Returns:
118
+ RobotInfo with name, model, capabilities, etc.
119
+ """
120
+ pass
121
+
122
+ @abstractmethod
123
+ def connect(self) -> ActionResult:
124
+ """
125
+ Establish connection to the robot hardware.
126
+
127
+ Returns:
128
+ ActionResult indicating success/failure
129
+ """
130
+ pass
131
+
132
+ @abstractmethod
133
+ def disconnect(self) -> ActionResult:
134
+ """
135
+ Safely disconnect from the robot hardware.
136
+
137
+ Returns:
138
+ ActionResult indicating success/failure
139
+ """
140
+ pass
141
+
142
+ @abstractmethod
143
+ def is_connected(self) -> bool:
144
+ """
145
+ Check if currently connected to the robot.
146
+
147
+ Returns:
148
+ True if connected and communication is working
149
+ """
150
+ pass
151
+
152
+ @abstractmethod
153
+ def get_status(self) -> RobotStatus:
154
+ """
155
+ Get current robot status.
156
+
157
+ Returns:
158
+ RobotStatus with mode, errors, battery, etc.
159
+ """
160
+ pass
161
+
162
+ def has_capability(self, cap: Capability) -> bool:
163
+ """Check if robot has a specific capability."""
164
+ return self.get_info().has_capability(cap)
165
+
166
+ def get_capabilities(self) -> Set[Capability]:
167
+ """Get all capabilities of this robot."""
168
+ return self.get_info().capabilities
169
+
170
+
171
+ class SafetyInterface(ABC):
172
+ """
173
+ Safety interface that all robots should implement.
174
+
175
+ Provides emergency stop and safety monitoring.
176
+ """
177
+
178
+ @abstractmethod
179
+ def emergency_stop(self) -> ActionResult:
180
+ """
181
+ Immediately stop all motion and enter safe state.
182
+
183
+ This should:
184
+ - Stop all actuators immediately
185
+ - Disable motor power if possible
186
+ - Set robot to ESTOPPED mode
187
+
188
+ Returns:
189
+ ActionResult (should rarely fail)
190
+ """
191
+ pass
192
+
193
+ @abstractmethod
194
+ def reset_emergency_stop(self) -> ActionResult:
195
+ """
196
+ Clear emergency stop state and return to ready.
197
+
198
+ Returns:
199
+ ActionResult indicating if reset was successful
200
+ """
201
+ pass
202
+
203
+ @abstractmethod
204
+ def is_estopped(self) -> bool:
205
+ """
206
+ Check if emergency stop is active.
207
+
208
+ Returns:
209
+ True if robot is in emergency stop state
210
+ """
211
+ pass
212
+
213
+ @abstractmethod
214
+ def get_battery_state(self) -> Optional[BatteryState]:
215
+ """
216
+ Get current battery status.
217
+
218
+ Returns:
219
+ BatteryState or None if no battery
220
+ """
221
+ pass
222
+
223
+ def check_safety(self) -> List[str]:
224
+ """
225
+ Run safety checks and return list of issues.
226
+
227
+ Returns:
228
+ List of safety warning/error messages (empty if all OK)
229
+ """
230
+ issues = []
231
+
232
+ battery = self.get_battery_state()
233
+ if battery:
234
+ if battery.percentage < 0.1:
235
+ issues.append(f"Critical battery: {battery.percentage*100:.0f}%")
236
+ elif battery.percentage < 0.2:
237
+ issues.append(f"Low battery: {battery.percentage*100:.0f}%")
238
+
239
+ if self.is_estopped():
240
+ issues.append("Emergency stop is active")
241
+
242
+ return issues
243
+
244
+
245
+ # =============================================================================
246
+ # Utility functions for capability checking
247
+ # =============================================================================
248
+
249
+ def requires_capabilities(*caps: Capability):
250
+ """
251
+ Decorator to mark that a skill requires certain capabilities.
252
+
253
+ Usage:
254
+ @requires_capabilities(Capability.QUADRUPED, Capability.GRIPPER)
255
+ def pick_up_object(robot, target):
256
+ ...
257
+ """
258
+ def decorator(func):
259
+ func._required_capabilities = set(caps)
260
+ return func
261
+ return decorator
262
+
263
+
264
+ def check_robot_compatibility(robot: RobotInterface, required: Set[Capability]) -> List[str]:
265
+ """
266
+ Check if a robot is compatible with required capabilities.
267
+
268
+ Returns:
269
+ List of missing capabilities (empty if compatible)
270
+ """
271
+ robot_caps = robot.get_capabilities()
272
+ missing = required - robot_caps
273
+ return [cap.name for cap in missing]
ate/interfaces/body.py ADDED
@@ -0,0 +1,267 @@
1
+ """
2
+ Body pose interfaces for robots with adjustable body position/orientation.
3
+
4
+ Primarily for legged robots that can:
5
+ - Adjust body height
6
+ - Tilt body (roll, pitch, yaw)
7
+ - Shift body position relative to feet
8
+
9
+ These are independent of locomotion - you can adjust pose while standing still.
10
+ """
11
+
12
+ from abc import ABC, abstractmethod
13
+ from typing import Optional, Tuple
14
+
15
+ from .types import (
16
+ Vector3,
17
+ Quaternion,
18
+ Pose,
19
+ ActionResult,
20
+ )
21
+
22
+
23
+ class BodyPoseInterface(ABC):
24
+ """
25
+ Interface for controlling robot body pose.
26
+
27
+ Implemented by quadrupeds, bipeds, and other robots that can adjust
28
+ their body position/orientation independent of locomotion.
29
+
30
+ Coordinate frame:
31
+ - Body origin is typically at the center of the robot
32
+ - Z+ is up, X+ is forward, Y+ is left
33
+ """
34
+
35
+ # =========================================================================
36
+ # Body height control
37
+ # =========================================================================
38
+
39
+ @abstractmethod
40
+ def set_body_height(self, height: float) -> ActionResult:
41
+ """
42
+ Set body height above ground.
43
+
44
+ Args:
45
+ height: Height in meters (from nominal ground level)
46
+
47
+ Returns:
48
+ ActionResult
49
+ """
50
+ pass
51
+
52
+ @abstractmethod
53
+ def get_body_height(self) -> float:
54
+ """
55
+ Get current body height.
56
+
57
+ Returns:
58
+ Height in meters
59
+ """
60
+ pass
61
+
62
+ def get_height_limits(self) -> Tuple[float, float]:
63
+ """
64
+ Get allowed body height range.
65
+
66
+ Returns:
67
+ (min_height, max_height) in meters
68
+ """
69
+ return (0.05, 0.30) # Default for small quadrupeds
70
+
71
+ # =========================================================================
72
+ # Body orientation control
73
+ # =========================================================================
74
+
75
+ @abstractmethod
76
+ def set_body_orientation(
77
+ self,
78
+ roll: float = 0.0,
79
+ pitch: float = 0.0,
80
+ yaw: float = 0.0
81
+ ) -> ActionResult:
82
+ """
83
+ Set body orientation (tilt).
84
+
85
+ Args:
86
+ roll: Roll angle in radians (positive = right side down)
87
+ pitch: Pitch angle in radians (positive = nose down)
88
+ yaw: Yaw angle in radians (positive = turn left)
89
+
90
+ Returns:
91
+ ActionResult
92
+ """
93
+ pass
94
+
95
+ @abstractmethod
96
+ def get_body_orientation(self) -> Tuple[float, float, float]:
97
+ """
98
+ Get current body orientation.
99
+
100
+ Returns:
101
+ (roll, pitch, yaw) in radians
102
+ """
103
+ pass
104
+
105
+ def get_orientation_limits(self) -> Tuple[Tuple[float, float], Tuple[float, float], Tuple[float, float]]:
106
+ """
107
+ Get allowed orientation ranges.
108
+
109
+ Returns:
110
+ ((roll_min, roll_max), (pitch_min, pitch_max), (yaw_min, yaw_max))
111
+ All in radians
112
+ """
113
+ import math
114
+ return (
115
+ (-math.pi/6, math.pi/6), # ±30° roll
116
+ (-math.pi/6, math.pi/6), # ±30° pitch
117
+ (-math.pi/4, math.pi/4), # ±45° yaw
118
+ )
119
+
120
+ # =========================================================================
121
+ # Body position control
122
+ # =========================================================================
123
+
124
+ def set_body_position(self, offset: Vector3) -> ActionResult:
125
+ """
126
+ Shift body position relative to feet.
127
+
128
+ This moves the body while keeping feet planted.
129
+
130
+ Args:
131
+ offset: Position offset from center (x=forward, y=left, z=up)
132
+
133
+ Returns:
134
+ ActionResult
135
+ """
136
+ return ActionResult.error("Body position shift not supported")
137
+
138
+ def get_body_position(self) -> Vector3:
139
+ """
140
+ Get current body position offset.
141
+
142
+ Returns:
143
+ Vector3 offset from center
144
+ """
145
+ return Vector3.zero()
146
+
147
+ # =========================================================================
148
+ # Combined pose control
149
+ # =========================================================================
150
+
151
+ @abstractmethod
152
+ def set_body_pose(
153
+ self,
154
+ height: Optional[float] = None,
155
+ roll: Optional[float] = None,
156
+ pitch: Optional[float] = None,
157
+ yaw: Optional[float] = None,
158
+ x_offset: Optional[float] = None,
159
+ y_offset: Optional[float] = None
160
+ ) -> ActionResult:
161
+ """
162
+ Set multiple body pose parameters at once.
163
+
164
+ Args:
165
+ height: Body height (None = keep current)
166
+ roll, pitch, yaw: Orientation (None = keep current)
167
+ x_offset, y_offset: Body position offset (None = keep current)
168
+
169
+ Returns:
170
+ ActionResult
171
+ """
172
+ pass
173
+
174
+ def get_body_pose(self) -> dict:
175
+ """
176
+ Get complete body pose.
177
+
178
+ Returns:
179
+ Dict with height, roll, pitch, yaw, x_offset, y_offset
180
+ """
181
+ roll, pitch, yaw = self.get_body_orientation()
182
+ pos = self.get_body_position()
183
+ return {
184
+ "height": self.get_body_height(),
185
+ "roll": roll,
186
+ "pitch": pitch,
187
+ "yaw": yaw,
188
+ "x_offset": pos.x,
189
+ "y_offset": pos.y,
190
+ }
191
+
192
+ # =========================================================================
193
+ # Preset poses
194
+ # =========================================================================
195
+
196
+ def reset_pose(self) -> ActionResult:
197
+ """
198
+ Reset to default/neutral body pose.
199
+
200
+ Returns:
201
+ ActionResult
202
+ """
203
+ height_min, height_max = self.get_height_limits()
204
+ default_height = (height_min + height_max) / 2
205
+ return self.set_body_pose(
206
+ height=default_height,
207
+ roll=0.0,
208
+ pitch=0.0,
209
+ yaw=0.0,
210
+ x_offset=0.0,
211
+ y_offset=0.0
212
+ )
213
+
214
+ def lower_body(self, height: Optional[float] = None) -> ActionResult:
215
+ """
216
+ Lower body (for grasping objects on ground).
217
+
218
+ Args:
219
+ height: Target height (None = minimum safe height)
220
+
221
+ Returns:
222
+ ActionResult
223
+ """
224
+ if height is None:
225
+ height = self.get_height_limits()[0] + 0.02 # 2cm above min
226
+ return self.set_body_height(height)
227
+
228
+ def raise_body(self, height: Optional[float] = None) -> ActionResult:
229
+ """
230
+ Raise body to maximum comfortable height.
231
+
232
+ Args:
233
+ height: Target height (None = default standing height)
234
+
235
+ Returns:
236
+ ActionResult
237
+ """
238
+ if height is None:
239
+ height_min, height_max = self.get_height_limits()
240
+ height = (height_min + height_max) / 2
241
+ return self.set_body_height(height)
242
+
243
+ def look_at(self, direction: Vector3) -> ActionResult:
244
+ """
245
+ Tilt body to "look" in a direction.
246
+
247
+ Adjusts pitch and yaw to point forward axis toward direction.
248
+
249
+ Args:
250
+ direction: Direction to look (in robot base frame)
251
+
252
+ Returns:
253
+ ActionResult
254
+ """
255
+ import math
256
+
257
+ # Calculate required yaw and pitch
258
+ yaw = math.atan2(direction.y, direction.x)
259
+ horizontal_dist = math.sqrt(direction.x**2 + direction.y**2)
260
+ pitch = -math.atan2(direction.z, horizontal_dist)
261
+
262
+ # Clamp to limits
263
+ roll_lim, pitch_lim, yaw_lim = self.get_orientation_limits()
264
+ pitch = max(pitch_lim[0], min(pitch_lim[1], pitch))
265
+ yaw = max(yaw_lim[0], min(yaw_lim[1], yaw))
266
+
267
+ return self.set_body_orientation(roll=0.0, pitch=pitch, yaw=yaw)