foodforthought-cli 0.2.4__py3-none-any.whl → 0.2.8__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/behaviors/__init__.py +88 -0
- ate/behaviors/common.py +686 -0
- ate/behaviors/tree.py +454 -0
- ate/cli.py +610 -54
- ate/drivers/__init__.py +27 -0
- ate/drivers/mechdog.py +606 -0
- ate/interfaces/__init__.py +171 -0
- ate/interfaces/base.py +271 -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/types.py +371 -0
- ate/mcp_server.py +387 -0
- ate/recording/__init__.py +44 -0
- ate/recording/demonstration.py +378 -0
- ate/recording/session.py +405 -0
- ate/recording/upload.py +304 -0
- ate/recording/wrapper.py +95 -0
- ate/robot/__init__.py +79 -0
- ate/robot/calibration.py +583 -0
- ate/robot/commands.py +3603 -0
- ate/robot/discovery.py +339 -0
- ate/robot/introspection.py +330 -0
- ate/robot/manager.py +270 -0
- ate/robot/profiles.py +275 -0
- ate/robot/registry.py +319 -0
- ate/robot/skill_upload.py +393 -0
- ate/robot/visual_labeler.py +1039 -0
- {foodforthought_cli-0.2.4.dist-info → foodforthought_cli-0.2.8.dist-info}/METADATA +9 -1
- {foodforthought_cli-0.2.4.dist-info → foodforthought_cli-0.2.8.dist-info}/RECORD +37 -8
- {foodforthought_cli-0.2.4.dist-info → foodforthought_cli-0.2.8.dist-info}/WHEEL +0 -0
- {foodforthought_cli-0.2.4.dist-info → foodforthought_cli-0.2.8.dist-info}/entry_points.txt +0 -0
- {foodforthought_cli-0.2.4.dist-info → foodforthought_cli-0.2.8.dist-info}/top_level.txt +0 -0
ate/behaviors/tree.py
ADDED
|
@@ -0,0 +1,454 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Core Behavior Tree implementation.
|
|
3
|
+
|
|
4
|
+
A behavior tree is a directed acyclic graph that controls the execution flow
|
|
5
|
+
of robot behaviors. Each node returns one of three statuses:
|
|
6
|
+
- SUCCESS: The behavior completed successfully
|
|
7
|
+
- FAILURE: The behavior failed
|
|
8
|
+
- RUNNING: The behavior is still executing
|
|
9
|
+
|
|
10
|
+
Composite nodes:
|
|
11
|
+
- Sequence: Executes children in order, fails if any child fails
|
|
12
|
+
- Selector: Tries children in order, succeeds if any child succeeds
|
|
13
|
+
- Parallel: Runs children concurrently
|
|
14
|
+
|
|
15
|
+
Decorator nodes:
|
|
16
|
+
- Inverter: Inverts child result
|
|
17
|
+
- Repeater: Repeats child N times
|
|
18
|
+
- Succeeder: Always succeeds regardless of child
|
|
19
|
+
|
|
20
|
+
Leaf nodes:
|
|
21
|
+
- Action: Performs an action
|
|
22
|
+
- Condition: Checks a condition
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from abc import ABC, abstractmethod
|
|
26
|
+
from dataclasses import dataclass, field
|
|
27
|
+
from typing import List, Optional, Callable, Any, Dict
|
|
28
|
+
from enum import Enum, auto
|
|
29
|
+
import time
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class BehaviorStatus(Enum):
|
|
33
|
+
"""Result of executing a behavior node."""
|
|
34
|
+
SUCCESS = auto() # Behavior completed successfully
|
|
35
|
+
FAILURE = auto() # Behavior failed
|
|
36
|
+
RUNNING = auto() # Behavior is still running
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclass
|
|
40
|
+
class Blackboard:
|
|
41
|
+
"""
|
|
42
|
+
Shared data store for behavior tree nodes.
|
|
43
|
+
|
|
44
|
+
Nodes can read/write data here to communicate.
|
|
45
|
+
Example: Detection node writes target position, Navigation reads it.
|
|
46
|
+
"""
|
|
47
|
+
data: Dict[str, Any] = field(default_factory=dict)
|
|
48
|
+
|
|
49
|
+
def get(self, key: str, default: Any = None) -> Any:
|
|
50
|
+
return self.data.get(key, default)
|
|
51
|
+
|
|
52
|
+
def set(self, key: str, value: Any) -> None:
|
|
53
|
+
self.data[key] = value
|
|
54
|
+
|
|
55
|
+
def has(self, key: str) -> bool:
|
|
56
|
+
return key in self.data
|
|
57
|
+
|
|
58
|
+
def clear(self) -> None:
|
|
59
|
+
self.data.clear()
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class BehaviorNode(ABC):
|
|
63
|
+
"""Base class for all behavior tree nodes."""
|
|
64
|
+
|
|
65
|
+
def __init__(self, name: str = ""):
|
|
66
|
+
self.name = name or self.__class__.__name__
|
|
67
|
+
self.status = BehaviorStatus.RUNNING
|
|
68
|
+
self.blackboard: Optional[Blackboard] = None
|
|
69
|
+
|
|
70
|
+
@abstractmethod
|
|
71
|
+
def tick(self) -> BehaviorStatus:
|
|
72
|
+
"""
|
|
73
|
+
Execute one tick of this node.
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
BehaviorStatus indicating result
|
|
77
|
+
"""
|
|
78
|
+
pass
|
|
79
|
+
|
|
80
|
+
def reset(self) -> None:
|
|
81
|
+
"""Reset node state for re-execution."""
|
|
82
|
+
self.status = BehaviorStatus.RUNNING
|
|
83
|
+
|
|
84
|
+
def set_blackboard(self, blackboard: Blackboard) -> None:
|
|
85
|
+
"""Set the shared blackboard."""
|
|
86
|
+
self.blackboard = blackboard
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class Composite(BehaviorNode):
|
|
90
|
+
"""Base class for composite nodes with children."""
|
|
91
|
+
|
|
92
|
+
def __init__(self, name: str = "", children: List[BehaviorNode] = None):
|
|
93
|
+
super().__init__(name)
|
|
94
|
+
self.children = children or []
|
|
95
|
+
|
|
96
|
+
def add_child(self, child: BehaviorNode) -> "Composite":
|
|
97
|
+
self.children.append(child)
|
|
98
|
+
return self
|
|
99
|
+
|
|
100
|
+
def set_blackboard(self, blackboard: Blackboard) -> None:
|
|
101
|
+
super().set_blackboard(blackboard)
|
|
102
|
+
for child in self.children:
|
|
103
|
+
child.set_blackboard(blackboard)
|
|
104
|
+
|
|
105
|
+
def reset(self) -> None:
|
|
106
|
+
super().reset()
|
|
107
|
+
for child in self.children:
|
|
108
|
+
child.reset()
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class Sequence(Composite):
|
|
112
|
+
"""
|
|
113
|
+
Executes children in order.
|
|
114
|
+
|
|
115
|
+
- Returns SUCCESS if all children succeed
|
|
116
|
+
- Returns FAILURE if any child fails
|
|
117
|
+
- Returns RUNNING if a child is running
|
|
118
|
+
|
|
119
|
+
Use case: "stand up, THEN walk forward, THEN stop"
|
|
120
|
+
"""
|
|
121
|
+
|
|
122
|
+
def __init__(self, name: str = "", children: List[BehaviorNode] = None):
|
|
123
|
+
super().__init__(name, children)
|
|
124
|
+
self.current_index = 0
|
|
125
|
+
|
|
126
|
+
def tick(self) -> BehaviorStatus:
|
|
127
|
+
while self.current_index < len(self.children):
|
|
128
|
+
child = self.children[self.current_index]
|
|
129
|
+
status = child.tick()
|
|
130
|
+
|
|
131
|
+
if status == BehaviorStatus.FAILURE:
|
|
132
|
+
self.current_index = 0
|
|
133
|
+
return BehaviorStatus.FAILURE
|
|
134
|
+
|
|
135
|
+
if status == BehaviorStatus.RUNNING:
|
|
136
|
+
return BehaviorStatus.RUNNING
|
|
137
|
+
|
|
138
|
+
# SUCCESS - move to next child
|
|
139
|
+
self.current_index += 1
|
|
140
|
+
|
|
141
|
+
self.current_index = 0
|
|
142
|
+
return BehaviorStatus.SUCCESS
|
|
143
|
+
|
|
144
|
+
def reset(self) -> None:
|
|
145
|
+
super().reset()
|
|
146
|
+
self.current_index = 0
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
class Selector(Composite):
|
|
150
|
+
"""
|
|
151
|
+
Tries children in order until one succeeds.
|
|
152
|
+
|
|
153
|
+
- Returns SUCCESS if any child succeeds
|
|
154
|
+
- Returns FAILURE if all children fail
|
|
155
|
+
- Returns RUNNING if a child is running
|
|
156
|
+
|
|
157
|
+
Use case: "try picking with right hand, OR try left hand"
|
|
158
|
+
"""
|
|
159
|
+
|
|
160
|
+
def __init__(self, name: str = "", children: List[BehaviorNode] = None):
|
|
161
|
+
super().__init__(name, children)
|
|
162
|
+
self.current_index = 0
|
|
163
|
+
|
|
164
|
+
def tick(self) -> BehaviorStatus:
|
|
165
|
+
while self.current_index < len(self.children):
|
|
166
|
+
child = self.children[self.current_index]
|
|
167
|
+
status = child.tick()
|
|
168
|
+
|
|
169
|
+
if status == BehaviorStatus.SUCCESS:
|
|
170
|
+
self.current_index = 0
|
|
171
|
+
return BehaviorStatus.SUCCESS
|
|
172
|
+
|
|
173
|
+
if status == BehaviorStatus.RUNNING:
|
|
174
|
+
return BehaviorStatus.RUNNING
|
|
175
|
+
|
|
176
|
+
# FAILURE - try next child
|
|
177
|
+
self.current_index += 1
|
|
178
|
+
|
|
179
|
+
self.current_index = 0
|
|
180
|
+
return BehaviorStatus.FAILURE
|
|
181
|
+
|
|
182
|
+
def reset(self) -> None:
|
|
183
|
+
super().reset()
|
|
184
|
+
self.current_index = 0
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
class Parallel(Composite):
|
|
188
|
+
"""
|
|
189
|
+
Runs all children concurrently.
|
|
190
|
+
|
|
191
|
+
Policy options:
|
|
192
|
+
- "require_all": SUCCESS if all succeed, FAILURE if any fails
|
|
193
|
+
- "require_one": SUCCESS if any succeeds, FAILURE if all fail
|
|
194
|
+
|
|
195
|
+
Use case: "navigate AND detect obstacles simultaneously"
|
|
196
|
+
"""
|
|
197
|
+
|
|
198
|
+
def __init__(
|
|
199
|
+
self,
|
|
200
|
+
name: str = "",
|
|
201
|
+
children: List[BehaviorNode] = None,
|
|
202
|
+
policy: str = "require_all"
|
|
203
|
+
):
|
|
204
|
+
super().__init__(name, children)
|
|
205
|
+
self.policy = policy
|
|
206
|
+
|
|
207
|
+
def tick(self) -> BehaviorStatus:
|
|
208
|
+
successes = 0
|
|
209
|
+
failures = 0
|
|
210
|
+
running = 0
|
|
211
|
+
|
|
212
|
+
for child in self.children:
|
|
213
|
+
status = child.tick()
|
|
214
|
+
if status == BehaviorStatus.SUCCESS:
|
|
215
|
+
successes += 1
|
|
216
|
+
elif status == BehaviorStatus.FAILURE:
|
|
217
|
+
failures += 1
|
|
218
|
+
else:
|
|
219
|
+
running += 1
|
|
220
|
+
|
|
221
|
+
if self.policy == "require_all":
|
|
222
|
+
if failures > 0:
|
|
223
|
+
return BehaviorStatus.FAILURE
|
|
224
|
+
if running > 0:
|
|
225
|
+
return BehaviorStatus.RUNNING
|
|
226
|
+
return BehaviorStatus.SUCCESS
|
|
227
|
+
else: # require_one
|
|
228
|
+
if successes > 0:
|
|
229
|
+
return BehaviorStatus.SUCCESS
|
|
230
|
+
if running > 0:
|
|
231
|
+
return BehaviorStatus.RUNNING
|
|
232
|
+
return BehaviorStatus.FAILURE
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
class Decorator(BehaviorNode):
|
|
236
|
+
"""Base class for decorator nodes with a single child."""
|
|
237
|
+
|
|
238
|
+
def __init__(self, child: BehaviorNode, name: str = ""):
|
|
239
|
+
super().__init__(name)
|
|
240
|
+
self.child = child
|
|
241
|
+
|
|
242
|
+
def set_blackboard(self, blackboard: Blackboard) -> None:
|
|
243
|
+
super().set_blackboard(blackboard)
|
|
244
|
+
self.child.set_blackboard(blackboard)
|
|
245
|
+
|
|
246
|
+
def reset(self) -> None:
|
|
247
|
+
super().reset()
|
|
248
|
+
self.child.reset()
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
class Inverter(Decorator):
|
|
252
|
+
"""
|
|
253
|
+
Inverts the result of child node.
|
|
254
|
+
|
|
255
|
+
- SUCCESS becomes FAILURE
|
|
256
|
+
- FAILURE becomes SUCCESS
|
|
257
|
+
- RUNNING stays RUNNING
|
|
258
|
+
|
|
259
|
+
Use case: "is NOT battery low"
|
|
260
|
+
"""
|
|
261
|
+
|
|
262
|
+
def tick(self) -> BehaviorStatus:
|
|
263
|
+
status = self.child.tick()
|
|
264
|
+
if status == BehaviorStatus.SUCCESS:
|
|
265
|
+
return BehaviorStatus.FAILURE
|
|
266
|
+
if status == BehaviorStatus.FAILURE:
|
|
267
|
+
return BehaviorStatus.SUCCESS
|
|
268
|
+
return BehaviorStatus.RUNNING
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
class Succeeder(Decorator):
|
|
272
|
+
"""
|
|
273
|
+
Always returns SUCCESS.
|
|
274
|
+
|
|
275
|
+
Use case: Optional actions that shouldn't fail the tree.
|
|
276
|
+
"""
|
|
277
|
+
|
|
278
|
+
def tick(self) -> BehaviorStatus:
|
|
279
|
+
self.child.tick()
|
|
280
|
+
return BehaviorStatus.SUCCESS
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
class Repeater(Decorator):
|
|
284
|
+
"""
|
|
285
|
+
Repeats child a specified number of times.
|
|
286
|
+
|
|
287
|
+
Use case: "walk forward 5 steps"
|
|
288
|
+
"""
|
|
289
|
+
|
|
290
|
+
def __init__(self, child: BehaviorNode, times: int = 1, name: str = ""):
|
|
291
|
+
super().__init__(child, name)
|
|
292
|
+
self.times = times
|
|
293
|
+
self.count = 0
|
|
294
|
+
|
|
295
|
+
def tick(self) -> BehaviorStatus:
|
|
296
|
+
if self.count >= self.times:
|
|
297
|
+
return BehaviorStatus.SUCCESS
|
|
298
|
+
|
|
299
|
+
status = self.child.tick()
|
|
300
|
+
if status == BehaviorStatus.RUNNING:
|
|
301
|
+
return BehaviorStatus.RUNNING
|
|
302
|
+
|
|
303
|
+
# Child completed (success or failure)
|
|
304
|
+
self.count += 1
|
|
305
|
+
self.child.reset()
|
|
306
|
+
|
|
307
|
+
if self.count >= self.times:
|
|
308
|
+
return BehaviorStatus.SUCCESS
|
|
309
|
+
|
|
310
|
+
return BehaviorStatus.RUNNING
|
|
311
|
+
|
|
312
|
+
def reset(self) -> None:
|
|
313
|
+
super().reset()
|
|
314
|
+
self.count = 0
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
class RepeatUntilFail(Decorator):
|
|
318
|
+
"""
|
|
319
|
+
Repeats child until it fails.
|
|
320
|
+
|
|
321
|
+
Use case: "keep picking up trash until none visible"
|
|
322
|
+
"""
|
|
323
|
+
|
|
324
|
+
def tick(self) -> BehaviorStatus:
|
|
325
|
+
status = self.child.tick()
|
|
326
|
+
if status == BehaviorStatus.FAILURE:
|
|
327
|
+
return BehaviorStatus.SUCCESS
|
|
328
|
+
if status == BehaviorStatus.SUCCESS:
|
|
329
|
+
self.child.reset()
|
|
330
|
+
return BehaviorStatus.RUNNING
|
|
331
|
+
return BehaviorStatus.RUNNING
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
class Action(BehaviorNode):
|
|
335
|
+
"""
|
|
336
|
+
Leaf node that performs an action.
|
|
337
|
+
|
|
338
|
+
Wraps a function that returns BehaviorStatus.
|
|
339
|
+
"""
|
|
340
|
+
|
|
341
|
+
def __init__(
|
|
342
|
+
self,
|
|
343
|
+
action: Callable[[], BehaviorStatus],
|
|
344
|
+
name: str = ""
|
|
345
|
+
):
|
|
346
|
+
super().__init__(name)
|
|
347
|
+
self.action = action
|
|
348
|
+
|
|
349
|
+
def tick(self) -> BehaviorStatus:
|
|
350
|
+
return self.action()
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
class Condition(BehaviorNode):
|
|
354
|
+
"""
|
|
355
|
+
Leaf node that checks a condition.
|
|
356
|
+
|
|
357
|
+
Returns SUCCESS if condition is true, FAILURE otherwise.
|
|
358
|
+
Never returns RUNNING.
|
|
359
|
+
"""
|
|
360
|
+
|
|
361
|
+
def __init__(
|
|
362
|
+
self,
|
|
363
|
+
condition: Callable[[], bool],
|
|
364
|
+
name: str = ""
|
|
365
|
+
):
|
|
366
|
+
super().__init__(name)
|
|
367
|
+
self.condition = condition
|
|
368
|
+
|
|
369
|
+
def tick(self) -> BehaviorStatus:
|
|
370
|
+
if self.condition():
|
|
371
|
+
return BehaviorStatus.SUCCESS
|
|
372
|
+
return BehaviorStatus.FAILURE
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
class BehaviorTree:
|
|
376
|
+
"""
|
|
377
|
+
A complete behavior tree with execution control.
|
|
378
|
+
|
|
379
|
+
Provides:
|
|
380
|
+
- Blackboard for shared data
|
|
381
|
+
- Tick loop control
|
|
382
|
+
- Visualization/debugging
|
|
383
|
+
"""
|
|
384
|
+
|
|
385
|
+
def __init__(self, root: BehaviorNode, name: str = "BehaviorTree"):
|
|
386
|
+
self.root = root
|
|
387
|
+
self.name = name
|
|
388
|
+
self.blackboard = Blackboard()
|
|
389
|
+
self.root.set_blackboard(self.blackboard)
|
|
390
|
+
self.tick_count = 0
|
|
391
|
+
self.running = False
|
|
392
|
+
|
|
393
|
+
def tick(self) -> BehaviorStatus:
|
|
394
|
+
"""Execute one tick of the tree."""
|
|
395
|
+
self.tick_count += 1
|
|
396
|
+
return self.root.tick()
|
|
397
|
+
|
|
398
|
+
def reset(self) -> None:
|
|
399
|
+
"""Reset the tree for re-execution."""
|
|
400
|
+
self.root.reset()
|
|
401
|
+
self.tick_count = 0
|
|
402
|
+
|
|
403
|
+
def run(
|
|
404
|
+
self,
|
|
405
|
+
tick_rate: float = 10.0,
|
|
406
|
+
max_ticks: int = 0,
|
|
407
|
+
on_tick: Optional[Callable[[int, BehaviorStatus], None]] = None
|
|
408
|
+
) -> BehaviorStatus:
|
|
409
|
+
"""
|
|
410
|
+
Run the tree until completion.
|
|
411
|
+
|
|
412
|
+
Args:
|
|
413
|
+
tick_rate: Ticks per second
|
|
414
|
+
max_ticks: Maximum ticks (0 = unlimited)
|
|
415
|
+
on_tick: Callback after each tick
|
|
416
|
+
|
|
417
|
+
Returns:
|
|
418
|
+
Final status
|
|
419
|
+
"""
|
|
420
|
+
self.running = True
|
|
421
|
+
interval = 1.0 / tick_rate
|
|
422
|
+
|
|
423
|
+
while self.running:
|
|
424
|
+
start = time.time()
|
|
425
|
+
status = self.tick()
|
|
426
|
+
|
|
427
|
+
if on_tick:
|
|
428
|
+
on_tick(self.tick_count, status)
|
|
429
|
+
|
|
430
|
+
if status != BehaviorStatus.RUNNING:
|
|
431
|
+
self.running = False
|
|
432
|
+
return status
|
|
433
|
+
|
|
434
|
+
if max_ticks > 0 and self.tick_count >= max_ticks:
|
|
435
|
+
self.running = False
|
|
436
|
+
return BehaviorStatus.FAILURE
|
|
437
|
+
|
|
438
|
+
# Sleep to maintain tick rate
|
|
439
|
+
elapsed = time.time() - start
|
|
440
|
+
if elapsed < interval:
|
|
441
|
+
time.sleep(interval - elapsed)
|
|
442
|
+
|
|
443
|
+
return BehaviorStatus.FAILURE
|
|
444
|
+
|
|
445
|
+
def stop(self) -> None:
|
|
446
|
+
"""Stop the tree execution."""
|
|
447
|
+
self.running = False
|
|
448
|
+
|
|
449
|
+
def get_blackboard(self) -> Blackboard:
|
|
450
|
+
"""Get the shared blackboard."""
|
|
451
|
+
return self.blackboard
|
|
452
|
+
|
|
453
|
+
def __repr__(self) -> str:
|
|
454
|
+
return f"BehaviorTree(name='{self.name}', ticks={self.tick_count})"
|