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
ate/behaviors/common.py
ADDED
|
@@ -0,0 +1,686 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Common behavior tree nodes for robotics.
|
|
3
|
+
|
|
4
|
+
These are reusable building blocks that wrap interface calls
|
|
5
|
+
into behavior tree-compatible actions and conditions.
|
|
6
|
+
|
|
7
|
+
The key insight: Each of these represents a SKILL that can be:
|
|
8
|
+
1. Recorded as a demonstration
|
|
9
|
+
2. Labeled by the community
|
|
10
|
+
3. Trained into a model
|
|
11
|
+
4. Deployed across robots
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from typing import Optional, List, Any
|
|
15
|
+
from .tree import (
|
|
16
|
+
BehaviorNode,
|
|
17
|
+
BehaviorStatus,
|
|
18
|
+
Sequence,
|
|
19
|
+
Selector,
|
|
20
|
+
Repeater,
|
|
21
|
+
RepeatUntilFail,
|
|
22
|
+
Action,
|
|
23
|
+
Condition,
|
|
24
|
+
)
|
|
25
|
+
from ..interfaces import (
|
|
26
|
+
NavigationInterface,
|
|
27
|
+
ObjectDetectionInterface,
|
|
28
|
+
GripperInterface,
|
|
29
|
+
Vector3,
|
|
30
|
+
NavigationGoal,
|
|
31
|
+
NavigationState,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
# =============================================================================
|
|
36
|
+
# Navigation Actions
|
|
37
|
+
# =============================================================================
|
|
38
|
+
|
|
39
|
+
class NavigateToPoint(BehaviorNode):
|
|
40
|
+
"""
|
|
41
|
+
Navigate to a specific point.
|
|
42
|
+
|
|
43
|
+
Blackboard:
|
|
44
|
+
- Writes: "navigation_status"
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
def __init__(
|
|
48
|
+
self,
|
|
49
|
+
nav: NavigationInterface,
|
|
50
|
+
x: float = 0,
|
|
51
|
+
y: float = 0,
|
|
52
|
+
z: float = 0,
|
|
53
|
+
name: str = ""
|
|
54
|
+
):
|
|
55
|
+
super().__init__(name or f"NavigateTo({x:.1f}, {y:.1f})")
|
|
56
|
+
self.nav = nav
|
|
57
|
+
self.target = Vector3(x, y, z)
|
|
58
|
+
self.started = False
|
|
59
|
+
|
|
60
|
+
def tick(self) -> BehaviorStatus:
|
|
61
|
+
if not self.started:
|
|
62
|
+
result = self.nav.navigate_to_point(
|
|
63
|
+
self.target.x, self.target.y, self.target.z
|
|
64
|
+
)
|
|
65
|
+
if not result.success:
|
|
66
|
+
return BehaviorStatus.FAILURE
|
|
67
|
+
self.started = True
|
|
68
|
+
|
|
69
|
+
status = self.nav.get_status()
|
|
70
|
+
if self.blackboard:
|
|
71
|
+
self.blackboard.set("navigation_status", status)
|
|
72
|
+
|
|
73
|
+
if status.state == NavigationState.COMPLETED:
|
|
74
|
+
return BehaviorStatus.SUCCESS
|
|
75
|
+
elif status.state == NavigationState.FAILED:
|
|
76
|
+
return BehaviorStatus.FAILURE
|
|
77
|
+
else:
|
|
78
|
+
return BehaviorStatus.RUNNING
|
|
79
|
+
|
|
80
|
+
def reset(self) -> None:
|
|
81
|
+
super().reset()
|
|
82
|
+
self.started = False
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class NavigateToPose(BehaviorNode):
|
|
86
|
+
"""
|
|
87
|
+
Navigate to a specific pose (position + heading).
|
|
88
|
+
|
|
89
|
+
Blackboard:
|
|
90
|
+
- Writes: "navigation_status"
|
|
91
|
+
"""
|
|
92
|
+
|
|
93
|
+
def __init__(
|
|
94
|
+
self,
|
|
95
|
+
nav: NavigationInterface,
|
|
96
|
+
x: float = 0,
|
|
97
|
+
y: float = 0,
|
|
98
|
+
yaw: float = 0,
|
|
99
|
+
name: str = ""
|
|
100
|
+
):
|
|
101
|
+
super().__init__(name or f"NavigateToPose({x:.1f}, {y:.1f}, {yaw:.1f})")
|
|
102
|
+
self.nav = nav
|
|
103
|
+
self.x = x
|
|
104
|
+
self.y = y
|
|
105
|
+
self.yaw = yaw
|
|
106
|
+
self.started = False
|
|
107
|
+
|
|
108
|
+
def tick(self) -> BehaviorStatus:
|
|
109
|
+
if not self.started:
|
|
110
|
+
result = self.nav.navigate_to_pose(self.x, self.y, self.yaw)
|
|
111
|
+
if not result.success:
|
|
112
|
+
return BehaviorStatus.FAILURE
|
|
113
|
+
self.started = True
|
|
114
|
+
|
|
115
|
+
status = self.nav.get_status()
|
|
116
|
+
if self.blackboard:
|
|
117
|
+
self.blackboard.set("navigation_status", status)
|
|
118
|
+
|
|
119
|
+
if status.state == NavigationState.COMPLETED:
|
|
120
|
+
return BehaviorStatus.SUCCESS
|
|
121
|
+
elif status.state == NavigationState.FAILED:
|
|
122
|
+
return BehaviorStatus.FAILURE
|
|
123
|
+
else:
|
|
124
|
+
return BehaviorStatus.RUNNING
|
|
125
|
+
|
|
126
|
+
def reset(self) -> None:
|
|
127
|
+
super().reset()
|
|
128
|
+
self.started = False
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
class Patrol(BehaviorNode):
|
|
132
|
+
"""
|
|
133
|
+
Patrol a set of waypoints.
|
|
134
|
+
|
|
135
|
+
Blackboard:
|
|
136
|
+
- Writes: "patrol_waypoint_index", "patrol_loop_count"
|
|
137
|
+
"""
|
|
138
|
+
|
|
139
|
+
def __init__(
|
|
140
|
+
self,
|
|
141
|
+
nav: NavigationInterface,
|
|
142
|
+
waypoints: List[Vector3],
|
|
143
|
+
loops: int = 1,
|
|
144
|
+
name: str = ""
|
|
145
|
+
):
|
|
146
|
+
super().__init__(name or f"Patrol({len(waypoints)} waypoints)")
|
|
147
|
+
self.nav = nav
|
|
148
|
+
self.waypoints = waypoints
|
|
149
|
+
self.loops = loops
|
|
150
|
+
self.current_waypoint = 0
|
|
151
|
+
self.current_loop = 0
|
|
152
|
+
self.navigating = False
|
|
153
|
+
|
|
154
|
+
def tick(self) -> BehaviorStatus:
|
|
155
|
+
# Check if we're done
|
|
156
|
+
if self.loops > 0 and self.current_loop >= self.loops:
|
|
157
|
+
return BehaviorStatus.SUCCESS
|
|
158
|
+
|
|
159
|
+
# Start navigating to current waypoint
|
|
160
|
+
if not self.navigating:
|
|
161
|
+
wp = self.waypoints[self.current_waypoint]
|
|
162
|
+
result = self.nav.navigate_to_point(wp.x, wp.y, wp.z)
|
|
163
|
+
if not result.success:
|
|
164
|
+
return BehaviorStatus.FAILURE
|
|
165
|
+
self.navigating = True
|
|
166
|
+
|
|
167
|
+
# Check navigation status
|
|
168
|
+
status = self.nav.get_status()
|
|
169
|
+
if self.blackboard:
|
|
170
|
+
self.blackboard.set("patrol_waypoint_index", self.current_waypoint)
|
|
171
|
+
self.blackboard.set("patrol_loop_count", self.current_loop)
|
|
172
|
+
|
|
173
|
+
if status.state == NavigationState.COMPLETED:
|
|
174
|
+
# Move to next waypoint
|
|
175
|
+
self.current_waypoint += 1
|
|
176
|
+
self.navigating = False
|
|
177
|
+
|
|
178
|
+
if self.current_waypoint >= len(self.waypoints):
|
|
179
|
+
self.current_waypoint = 0
|
|
180
|
+
self.current_loop += 1
|
|
181
|
+
|
|
182
|
+
if self.loops > 0 and self.current_loop >= self.loops:
|
|
183
|
+
return BehaviorStatus.SUCCESS
|
|
184
|
+
|
|
185
|
+
return BehaviorStatus.RUNNING
|
|
186
|
+
|
|
187
|
+
elif status.state == NavigationState.FAILED:
|
|
188
|
+
return BehaviorStatus.FAILURE
|
|
189
|
+
|
|
190
|
+
return BehaviorStatus.RUNNING
|
|
191
|
+
|
|
192
|
+
def reset(self) -> None:
|
|
193
|
+
super().reset()
|
|
194
|
+
self.current_waypoint = 0
|
|
195
|
+
self.current_loop = 0
|
|
196
|
+
self.navigating = False
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
class ReturnHome(BehaviorNode):
|
|
200
|
+
"""
|
|
201
|
+
Navigate to home position.
|
|
202
|
+
|
|
203
|
+
Blackboard:
|
|
204
|
+
- Reads: "home_position" (optional, falls back to nav.go_home())
|
|
205
|
+
"""
|
|
206
|
+
|
|
207
|
+
def __init__(self, nav: NavigationInterface, name: str = ""):
|
|
208
|
+
super().__init__(name or "ReturnHome")
|
|
209
|
+
self.nav = nav
|
|
210
|
+
self.started = False
|
|
211
|
+
|
|
212
|
+
def tick(self) -> BehaviorStatus:
|
|
213
|
+
if not self.started:
|
|
214
|
+
# Try blackboard first
|
|
215
|
+
if self.blackboard and self.blackboard.has("home_position"):
|
|
216
|
+
home = self.blackboard.get("home_position")
|
|
217
|
+
result = self.nav.navigate_to_point(home.x, home.y, home.z)
|
|
218
|
+
else:
|
|
219
|
+
result = self.nav.go_home()
|
|
220
|
+
|
|
221
|
+
if not result.success:
|
|
222
|
+
return BehaviorStatus.FAILURE
|
|
223
|
+
self.started = True
|
|
224
|
+
|
|
225
|
+
status = self.nav.get_status()
|
|
226
|
+
if status.state == NavigationState.COMPLETED:
|
|
227
|
+
return BehaviorStatus.SUCCESS
|
|
228
|
+
elif status.state == NavigationState.FAILED:
|
|
229
|
+
return BehaviorStatus.FAILURE
|
|
230
|
+
return BehaviorStatus.RUNNING
|
|
231
|
+
|
|
232
|
+
def reset(self) -> None:
|
|
233
|
+
super().reset()
|
|
234
|
+
self.started = False
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
# =============================================================================
|
|
238
|
+
# Detection Actions
|
|
239
|
+
# =============================================================================
|
|
240
|
+
|
|
241
|
+
class DetectObject(BehaviorNode):
|
|
242
|
+
"""
|
|
243
|
+
Run object detection and store results.
|
|
244
|
+
|
|
245
|
+
Blackboard:
|
|
246
|
+
- Writes: "detections", "detection_count"
|
|
247
|
+
"""
|
|
248
|
+
|
|
249
|
+
def __init__(
|
|
250
|
+
self,
|
|
251
|
+
detector: ObjectDetectionInterface,
|
|
252
|
+
class_name: Optional[str] = None,
|
|
253
|
+
min_confidence: float = 0.5,
|
|
254
|
+
name: str = ""
|
|
255
|
+
):
|
|
256
|
+
super().__init__(name or f"Detect({class_name or 'any'})")
|
|
257
|
+
self.detector = detector
|
|
258
|
+
self.class_name = class_name
|
|
259
|
+
self.min_confidence = min_confidence
|
|
260
|
+
|
|
261
|
+
def tick(self) -> BehaviorStatus:
|
|
262
|
+
if self.class_name:
|
|
263
|
+
detections = self.detector.detect_class(
|
|
264
|
+
self.class_name, self.min_confidence
|
|
265
|
+
)
|
|
266
|
+
else:
|
|
267
|
+
result = self.detector.detect()
|
|
268
|
+
detections = result.filter_by_confidence(self.min_confidence)
|
|
269
|
+
|
|
270
|
+
if self.blackboard:
|
|
271
|
+
self.blackboard.set("detections", detections)
|
|
272
|
+
self.blackboard.set("detection_count", len(detections))
|
|
273
|
+
|
|
274
|
+
if detections:
|
|
275
|
+
return BehaviorStatus.SUCCESS
|
|
276
|
+
return BehaviorStatus.FAILURE
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
class IsObjectVisible(BehaviorNode):
|
|
280
|
+
"""
|
|
281
|
+
Check if an object class is visible.
|
|
282
|
+
|
|
283
|
+
This is a CONDITION - returns immediately, never RUNNING.
|
|
284
|
+
"""
|
|
285
|
+
|
|
286
|
+
def __init__(
|
|
287
|
+
self,
|
|
288
|
+
detector: ObjectDetectionInterface,
|
|
289
|
+
class_name: str,
|
|
290
|
+
min_confidence: float = 0.5,
|
|
291
|
+
name: str = ""
|
|
292
|
+
):
|
|
293
|
+
super().__init__(name or f"IsVisible({class_name})")
|
|
294
|
+
self.detector = detector
|
|
295
|
+
self.class_name = class_name
|
|
296
|
+
self.min_confidence = min_confidence
|
|
297
|
+
|
|
298
|
+
def tick(self) -> BehaviorStatus:
|
|
299
|
+
detections = self.detector.detect_class(
|
|
300
|
+
self.class_name, self.min_confidence
|
|
301
|
+
)
|
|
302
|
+
if detections:
|
|
303
|
+
return BehaviorStatus.SUCCESS
|
|
304
|
+
return BehaviorStatus.FAILURE
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
class FindNearest(BehaviorNode):
|
|
308
|
+
"""
|
|
309
|
+
Find the nearest object of a class.
|
|
310
|
+
|
|
311
|
+
Blackboard:
|
|
312
|
+
- Writes: "target_detection", "target_position"
|
|
313
|
+
"""
|
|
314
|
+
|
|
315
|
+
def __init__(
|
|
316
|
+
self,
|
|
317
|
+
detector: ObjectDetectionInterface,
|
|
318
|
+
class_name: str,
|
|
319
|
+
name: str = ""
|
|
320
|
+
):
|
|
321
|
+
super().__init__(name or f"FindNearest({class_name})")
|
|
322
|
+
self.detector = detector
|
|
323
|
+
self.class_name = class_name
|
|
324
|
+
|
|
325
|
+
def tick(self) -> BehaviorStatus:
|
|
326
|
+
detection = self.detector.find_nearest(self.class_name)
|
|
327
|
+
|
|
328
|
+
if detection:
|
|
329
|
+
if self.blackboard:
|
|
330
|
+
self.blackboard.set("target_detection", detection)
|
|
331
|
+
if detection.position_3d:
|
|
332
|
+
self.blackboard.set("target_position", detection.position_3d)
|
|
333
|
+
return BehaviorStatus.SUCCESS
|
|
334
|
+
return BehaviorStatus.FAILURE
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
class ApproachObject(BehaviorNode):
|
|
338
|
+
"""
|
|
339
|
+
Approach an object (navigate to it).
|
|
340
|
+
|
|
341
|
+
Blackboard:
|
|
342
|
+
- Reads: "target_position" or "target_detection"
|
|
343
|
+
"""
|
|
344
|
+
|
|
345
|
+
def __init__(
|
|
346
|
+
self,
|
|
347
|
+
nav: NavigationInterface,
|
|
348
|
+
approach_distance: float = 0.3,
|
|
349
|
+
name: str = ""
|
|
350
|
+
):
|
|
351
|
+
super().__init__(name or "ApproachObject")
|
|
352
|
+
self.nav = nav
|
|
353
|
+
self.approach_distance = approach_distance
|
|
354
|
+
self.started = False
|
|
355
|
+
|
|
356
|
+
def tick(self) -> BehaviorStatus:
|
|
357
|
+
if not self.started:
|
|
358
|
+
# Get target from blackboard
|
|
359
|
+
if not self.blackboard:
|
|
360
|
+
return BehaviorStatus.FAILURE
|
|
361
|
+
|
|
362
|
+
target = self.blackboard.get("target_position")
|
|
363
|
+
if not target:
|
|
364
|
+
detection = self.blackboard.get("target_detection")
|
|
365
|
+
if detection and detection.position_3d:
|
|
366
|
+
target = detection.position_3d
|
|
367
|
+
else:
|
|
368
|
+
return BehaviorStatus.FAILURE
|
|
369
|
+
|
|
370
|
+
# Navigate to target (with offset)
|
|
371
|
+
result = self.nav.navigate_to_point(
|
|
372
|
+
target.x - self.approach_distance,
|
|
373
|
+
target.y,
|
|
374
|
+
target.z
|
|
375
|
+
)
|
|
376
|
+
if not result.success:
|
|
377
|
+
return BehaviorStatus.FAILURE
|
|
378
|
+
self.started = True
|
|
379
|
+
|
|
380
|
+
status = self.nav.get_status()
|
|
381
|
+
if status.state == NavigationState.COMPLETED:
|
|
382
|
+
return BehaviorStatus.SUCCESS
|
|
383
|
+
elif status.state == NavigationState.FAILED:
|
|
384
|
+
return BehaviorStatus.FAILURE
|
|
385
|
+
return BehaviorStatus.RUNNING
|
|
386
|
+
|
|
387
|
+
def reset(self) -> None:
|
|
388
|
+
super().reset()
|
|
389
|
+
self.started = False
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
# =============================================================================
|
|
393
|
+
# Manipulation Actions
|
|
394
|
+
# =============================================================================
|
|
395
|
+
|
|
396
|
+
class PickUp(BehaviorNode):
|
|
397
|
+
"""
|
|
398
|
+
Pick up an object at the target position.
|
|
399
|
+
|
|
400
|
+
Blackboard:
|
|
401
|
+
- Reads: "target_position"
|
|
402
|
+
- Writes: "has_object"
|
|
403
|
+
"""
|
|
404
|
+
|
|
405
|
+
def __init__(self, gripper: GripperInterface, name: str = ""):
|
|
406
|
+
super().__init__(name or "PickUp")
|
|
407
|
+
self.gripper = gripper
|
|
408
|
+
self.state = "init"
|
|
409
|
+
|
|
410
|
+
def tick(self) -> BehaviorStatus:
|
|
411
|
+
if self.state == "init":
|
|
412
|
+
# Open gripper
|
|
413
|
+
result = self.gripper.open()
|
|
414
|
+
if not result.success:
|
|
415
|
+
return BehaviorStatus.FAILURE
|
|
416
|
+
self.state = "lowering"
|
|
417
|
+
return BehaviorStatus.RUNNING
|
|
418
|
+
|
|
419
|
+
elif self.state == "lowering":
|
|
420
|
+
# Lower body (would need body interface)
|
|
421
|
+
# For now, skip this step
|
|
422
|
+
self.state = "grasping"
|
|
423
|
+
return BehaviorStatus.RUNNING
|
|
424
|
+
|
|
425
|
+
elif self.state == "grasping":
|
|
426
|
+
result = self.gripper.grasp()
|
|
427
|
+
if not result.success:
|
|
428
|
+
return BehaviorStatus.FAILURE
|
|
429
|
+
self.state = "checking"
|
|
430
|
+
return BehaviorStatus.RUNNING
|
|
431
|
+
|
|
432
|
+
elif self.state == "checking":
|
|
433
|
+
# Check if we got something
|
|
434
|
+
status = self.gripper.get_status()
|
|
435
|
+
if self.blackboard:
|
|
436
|
+
self.blackboard.set("has_object", status.has_object)
|
|
437
|
+
if status.has_object:
|
|
438
|
+
return BehaviorStatus.SUCCESS
|
|
439
|
+
return BehaviorStatus.FAILURE
|
|
440
|
+
|
|
441
|
+
return BehaviorStatus.FAILURE
|
|
442
|
+
|
|
443
|
+
def reset(self) -> None:
|
|
444
|
+
super().reset()
|
|
445
|
+
self.state = "init"
|
|
446
|
+
|
|
447
|
+
|
|
448
|
+
class PlaceAt(BehaviorNode):
|
|
449
|
+
"""
|
|
450
|
+
Place held object at a position.
|
|
451
|
+
|
|
452
|
+
Blackboard:
|
|
453
|
+
- Writes: "has_object" = False on success
|
|
454
|
+
"""
|
|
455
|
+
|
|
456
|
+
def __init__(
|
|
457
|
+
self,
|
|
458
|
+
gripper: GripperInterface,
|
|
459
|
+
x: float,
|
|
460
|
+
y: float,
|
|
461
|
+
z: float,
|
|
462
|
+
name: str = ""
|
|
463
|
+
):
|
|
464
|
+
super().__init__(name or f"PlaceAt({x:.1f}, {y:.1f}, {z:.1f})")
|
|
465
|
+
self.gripper = gripper
|
|
466
|
+
self.target = Vector3(x, y, z)
|
|
467
|
+
self.state = "init"
|
|
468
|
+
|
|
469
|
+
def tick(self) -> BehaviorStatus:
|
|
470
|
+
# Simplified - just release
|
|
471
|
+
if self.state == "init":
|
|
472
|
+
result = self.gripper.release()
|
|
473
|
+
if result.success:
|
|
474
|
+
if self.blackboard:
|
|
475
|
+
self.blackboard.set("has_object", False)
|
|
476
|
+
return BehaviorStatus.SUCCESS
|
|
477
|
+
return BehaviorStatus.FAILURE
|
|
478
|
+
return BehaviorStatus.FAILURE
|
|
479
|
+
|
|
480
|
+
def reset(self) -> None:
|
|
481
|
+
super().reset()
|
|
482
|
+
self.state = "init"
|
|
483
|
+
|
|
484
|
+
|
|
485
|
+
class DropInBin(BehaviorNode):
|
|
486
|
+
"""
|
|
487
|
+
Drop held object into a bin.
|
|
488
|
+
|
|
489
|
+
Blackboard:
|
|
490
|
+
- Reads: "bin_position" (optional)
|
|
491
|
+
- Writes: "has_object" = False on success
|
|
492
|
+
"""
|
|
493
|
+
|
|
494
|
+
def __init__(
|
|
495
|
+
self,
|
|
496
|
+
nav: NavigationInterface,
|
|
497
|
+
gripper: GripperInterface,
|
|
498
|
+
bin_position: Optional[Vector3] = None,
|
|
499
|
+
name: str = ""
|
|
500
|
+
):
|
|
501
|
+
super().__init__(name or "DropInBin")
|
|
502
|
+
self.nav = nav
|
|
503
|
+
self.gripper = gripper
|
|
504
|
+
self.bin_position = bin_position
|
|
505
|
+
self.state = "navigate"
|
|
506
|
+
|
|
507
|
+
def tick(self) -> BehaviorStatus:
|
|
508
|
+
if self.state == "navigate":
|
|
509
|
+
# Get bin position
|
|
510
|
+
pos = self.bin_position
|
|
511
|
+
if not pos and self.blackboard:
|
|
512
|
+
pos = self.blackboard.get("bin_position")
|
|
513
|
+
if not pos:
|
|
514
|
+
return BehaviorStatus.FAILURE
|
|
515
|
+
|
|
516
|
+
result = self.nav.navigate_to_point(pos.x, pos.y, pos.z)
|
|
517
|
+
if not result.success:
|
|
518
|
+
return BehaviorStatus.FAILURE
|
|
519
|
+
self.state = "navigating"
|
|
520
|
+
return BehaviorStatus.RUNNING
|
|
521
|
+
|
|
522
|
+
elif self.state == "navigating":
|
|
523
|
+
status = self.nav.get_status()
|
|
524
|
+
if status.state == NavigationState.COMPLETED:
|
|
525
|
+
self.state = "dropping"
|
|
526
|
+
return BehaviorStatus.RUNNING
|
|
527
|
+
elif status.state == NavigationState.FAILED:
|
|
528
|
+
return BehaviorStatus.FAILURE
|
|
529
|
+
return BehaviorStatus.RUNNING
|
|
530
|
+
|
|
531
|
+
elif self.state == "dropping":
|
|
532
|
+
result = self.gripper.release()
|
|
533
|
+
if result.success:
|
|
534
|
+
if self.blackboard:
|
|
535
|
+
self.blackboard.set("has_object", False)
|
|
536
|
+
return BehaviorStatus.SUCCESS
|
|
537
|
+
return BehaviorStatus.FAILURE
|
|
538
|
+
|
|
539
|
+
return BehaviorStatus.FAILURE
|
|
540
|
+
|
|
541
|
+
def reset(self) -> None:
|
|
542
|
+
super().reset()
|
|
543
|
+
self.state = "navigate"
|
|
544
|
+
|
|
545
|
+
|
|
546
|
+
# =============================================================================
|
|
547
|
+
# Conditions
|
|
548
|
+
# =============================================================================
|
|
549
|
+
|
|
550
|
+
class IsBatteryLow(BehaviorNode):
|
|
551
|
+
"""Check if battery is below threshold."""
|
|
552
|
+
|
|
553
|
+
def __init__(self, robot: Any, threshold: float = 20.0, name: str = ""):
|
|
554
|
+
super().__init__(name or "IsBatteryLow")
|
|
555
|
+
self.robot = robot
|
|
556
|
+
self.threshold = threshold
|
|
557
|
+
|
|
558
|
+
def tick(self) -> BehaviorStatus:
|
|
559
|
+
try:
|
|
560
|
+
status = self.robot.get_status()
|
|
561
|
+
if status.battery_level < self.threshold:
|
|
562
|
+
return BehaviorStatus.SUCCESS
|
|
563
|
+
except Exception:
|
|
564
|
+
pass
|
|
565
|
+
return BehaviorStatus.FAILURE
|
|
566
|
+
|
|
567
|
+
|
|
568
|
+
class IsPathClear(BehaviorNode):
|
|
569
|
+
"""Check if navigation path is clear."""
|
|
570
|
+
|
|
571
|
+
def __init__(self, nav: NavigationInterface, name: str = ""):
|
|
572
|
+
super().__init__(name or "IsPathClear")
|
|
573
|
+
self.nav = nav
|
|
574
|
+
|
|
575
|
+
def tick(self) -> BehaviorStatus:
|
|
576
|
+
if self.nav.is_path_clear():
|
|
577
|
+
return BehaviorStatus.SUCCESS
|
|
578
|
+
return BehaviorStatus.FAILURE
|
|
579
|
+
|
|
580
|
+
|
|
581
|
+
class HasObject(BehaviorNode):
|
|
582
|
+
"""Check if robot is holding an object."""
|
|
583
|
+
|
|
584
|
+
def __init__(self, gripper: GripperInterface = None, name: str = ""):
|
|
585
|
+
super().__init__(name or "HasObject")
|
|
586
|
+
self.gripper = gripper
|
|
587
|
+
|
|
588
|
+
def tick(self) -> BehaviorStatus:
|
|
589
|
+
# Try gripper first
|
|
590
|
+
if self.gripper:
|
|
591
|
+
status = self.gripper.get_status()
|
|
592
|
+
if status.has_object:
|
|
593
|
+
return BehaviorStatus.SUCCESS
|
|
594
|
+
return BehaviorStatus.FAILURE
|
|
595
|
+
|
|
596
|
+
# Fall back to blackboard
|
|
597
|
+
if self.blackboard and self.blackboard.get("has_object"):
|
|
598
|
+
return BehaviorStatus.SUCCESS
|
|
599
|
+
return BehaviorStatus.FAILURE
|
|
600
|
+
|
|
601
|
+
|
|
602
|
+
# =============================================================================
|
|
603
|
+
# Composite Behaviors (Higher-Level)
|
|
604
|
+
# =============================================================================
|
|
605
|
+
|
|
606
|
+
def PatrolAndCleanup(
|
|
607
|
+
nav: NavigationInterface,
|
|
608
|
+
detector: ObjectDetectionInterface,
|
|
609
|
+
gripper: GripperInterface,
|
|
610
|
+
waypoints: List[Vector3],
|
|
611
|
+
bin_position: Vector3,
|
|
612
|
+
target_class: str = "trash"
|
|
613
|
+
) -> BehaviorNode:
|
|
614
|
+
"""
|
|
615
|
+
High-level behavior: Patrol an area, pick up trash, dispose in bin.
|
|
616
|
+
|
|
617
|
+
This is the MechDog trash-picking behavior!
|
|
618
|
+
|
|
619
|
+
Tree structure:
|
|
620
|
+
RepeatUntilFail
|
|
621
|
+
└── Sequence
|
|
622
|
+
├── Patrol waypoints
|
|
623
|
+
└── RepeatUntilFail (cleanup loop)
|
|
624
|
+
└── Sequence
|
|
625
|
+
├── FindNearest(trash)
|
|
626
|
+
├── ApproachObject
|
|
627
|
+
├── PickUp
|
|
628
|
+
└── DropInBin
|
|
629
|
+
"""
|
|
630
|
+
cleanup_loop = RepeatUntilFail(
|
|
631
|
+
Sequence(children=[
|
|
632
|
+
FindNearest(detector, target_class),
|
|
633
|
+
ApproachObject(nav),
|
|
634
|
+
PickUp(gripper),
|
|
635
|
+
DropInBin(nav, gripper, bin_position),
|
|
636
|
+
]),
|
|
637
|
+
name="CleanupLoop"
|
|
638
|
+
)
|
|
639
|
+
|
|
640
|
+
return RepeatUntilFail(
|
|
641
|
+
Sequence(children=[
|
|
642
|
+
Patrol(nav, waypoints, loops=1),
|
|
643
|
+
cleanup_loop,
|
|
644
|
+
]),
|
|
645
|
+
name="PatrolAndCleanup"
|
|
646
|
+
)
|
|
647
|
+
|
|
648
|
+
|
|
649
|
+
def SearchAndRetrieve(
|
|
650
|
+
nav: NavigationInterface,
|
|
651
|
+
detector: ObjectDetectionInterface,
|
|
652
|
+
gripper: GripperInterface,
|
|
653
|
+
target_class: str,
|
|
654
|
+
return_position: Vector3
|
|
655
|
+
) -> BehaviorNode:
|
|
656
|
+
"""
|
|
657
|
+
Search for an object, pick it up, and bring it back.
|
|
658
|
+
|
|
659
|
+
Tree structure:
|
|
660
|
+
Sequence
|
|
661
|
+
├── Selector (find the object)
|
|
662
|
+
│ ├── IsObjectVisible
|
|
663
|
+
│ └── Sequence
|
|
664
|
+
│ ├── Move forward
|
|
665
|
+
│ └── IsObjectVisible
|
|
666
|
+
├── FindNearest
|
|
667
|
+
├── ApproachObject
|
|
668
|
+
├── PickUp
|
|
669
|
+
└── NavigateToPoint (return)
|
|
670
|
+
"""
|
|
671
|
+
return Sequence(
|
|
672
|
+
name=f"SearchAndRetrieve({target_class})",
|
|
673
|
+
children=[
|
|
674
|
+
Selector(children=[
|
|
675
|
+
IsObjectVisible(detector, target_class),
|
|
676
|
+
Sequence(children=[
|
|
677
|
+
NavigateToPoint(nav, 1, 0, 0, "MoveForward"),
|
|
678
|
+
IsObjectVisible(detector, target_class),
|
|
679
|
+
]),
|
|
680
|
+
]),
|
|
681
|
+
FindNearest(detector, target_class),
|
|
682
|
+
ApproachObject(nav),
|
|
683
|
+
PickUp(gripper),
|
|
684
|
+
NavigateToPoint(nav, return_position.x, return_position.y, return_position.z, "Return"),
|
|
685
|
+
]
|
|
686
|
+
)
|