pi_trees_lib.py
Go to the documentation of this file.
00001 #!/usr/bin/env python
00002 
00003 """
00004     pi_trees_lib.py - Version 0.1 2013-08-28
00005     
00006     Core classes for implementing Behavior Trees in Python
00007     
00008     Created for the Pi Robot Project: http://www.pirobot.org
00009     Copyright (c) 2013 Patrick Goebel.  All rights reserved.
00010 
00011     This program is free software; you can redistribute it and/or modify
00012     it under the terms of the GNU General Public License as published by
00013     the Free Software Foundation; either version 2 of the License, or
00014     (at your option) any later version.
00015     
00016     This program is distributed in the hope that it will be useful,
00017     but WITHOUT ANY WARRANTY; without even the implied warranty of
00018     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00019     GNU General Public License for more details at:
00020     
00021     http://www.gnu.org/licenses/gpl.html
00022 """
00023 
00024 import string
00025 
00026 # import pygraphviz as pgv
00027 # from pygraph.classes.graph import graph
00028 # from pygraph.classes.digraph import digraph
00029 # from pygraph.algorithms.searching import breadth_first_search
00030 # from pygraph.readwrite.dot import write
00031 # import gv
00032 
00033 class TaskStatus(object):
00034     """ A class for enumerating task statuses """
00035     FAILURE = 0
00036     SUCCESS = 1
00037     RUNNING = 2
00038     
00039 class Task(object):
00040     """ "The base Task class """
00041     def __init__(self, name, children=None, *args, **kwargs):
00042         self.name = name
00043         self.status = None
00044                 
00045         if children is None:
00046             children = []
00047             
00048         self.children = children
00049                          
00050     def run(self):
00051         pass
00052 
00053     def reset(self):
00054         for c in self.children:
00055             c.reset()
00056 
00057     def add_child(self, c):
00058         self.children.append(c)
00059 
00060     def remove_child(self, c):
00061         self.children.remove(c)
00062         
00063     def prepend_child(self, c):
00064         self.children.insert(0, c)
00065         
00066     def insert_child(self, c, i):
00067         self.children.insert(i, c)
00068         
00069     def get_status(self):
00070         return self.status
00071     
00072     def set_status(self, s):
00073         self.status = s
00074     
00075     def announce(self):
00076         print("Executing task " + str(self.name))
00077     
00078     # These next two functions allow us to use the 'with' syntax
00079     def __enter__(self):
00080         return self.name
00081     
00082     def __exit__(self, exc_type, exc_val, exc_tb):
00083         if  exc_type is not None:
00084             return False
00085         return True
00086 
00087 class Selector(Task):
00088     """ A selector runs each task in order until one succeeds,
00089         at which point it returns SUCCESS. If all tasks fail, a FAILURE
00090         status is returned.  If a subtask is still RUNNING, then a RUNNING
00091         status is returned and processing continues until either SUCCESS
00092         or FAILURE is returned from the subtask.
00093     """
00094     def __init__(self, name, *args, **kwargs):
00095         super(Selector, self).__init__(name, *args, **kwargs)
00096  
00097     def run(self):
00098         for c in self.children:
00099             
00100             c.status = c.run()
00101             
00102             if c.status != TaskStatus.FAILURE:
00103                 return c.status
00104 
00105         return TaskStatus.FAILURE
00106  
00107 class Sequence(Task):
00108     """
00109         A sequence runs each task in order until one fails,
00110         at which point it returns FAILURE. If all tasks succeed, a SUCCESS
00111         status is returned.  If a subtask is still RUNNING, then a RUNNING
00112         status is returned and processing continues until either SUCCESS
00113         or FAILURE is returned from the subtask.
00114     """
00115     def __init__(self, name, *args, **kwargs):
00116         super(Sequence, self).__init__(name, *args, **kwargs)
00117  
00118     def run(self):
00119         for c in self.children:
00120             
00121             c.status = c.run()
00122                          
00123             if c.status != TaskStatus.SUCCESS:
00124                 return c.status   
00125             
00126         return TaskStatus.SUCCESS
00127     
00128 class Iterator(Task):
00129     """
00130         Iterate through all child tasks ignoring failure.
00131     """
00132     def __init__(self, name, *args, **kwargs):
00133         super(Iterator, self).__init__(name, *args, **kwargs)
00134  
00135     def run(self):
00136         for c in self.children:
00137             
00138             c.status = c.run()
00139                          
00140             if c.status != TaskStatus.SUCCESS and c.status != TaskStatus.FAILURE:
00141                 return c.status
00142             
00143         return TaskStatus.SUCCESS
00144     
00145 class ParallelOne(Task):
00146     """
00147         A parallel task runs each child task at (roughly) the same time.
00148         The ParallelOne task returns success as soon as any child succeeds.
00149     """
00150     def __init__(self, name, *args, **kwargs):
00151         super(ParallelOne, self).__init__(name, *args, **kwargs)
00152                  
00153     def run(self):
00154         for c in self.children:
00155             c.status = c.run()
00156 
00157             if c.status == TaskStatus.SUCCESS:
00158                 return TaskStatus.SUCCESS
00159         
00160         return TaskStatus.FAILURE
00161         
00162 class ParallelAll(Task):
00163     """
00164         A parallel task runs each child task at (roughly) the same time.
00165         The ParallelAll task requires all subtasks to succeed for it to succeed.
00166     """
00167     def __init__(self, name, *args, **kwargs):
00168         super(ParallelAll, self).__init__(name, *args, **kwargs)
00169                  
00170     def run(self):
00171         n_success = 0
00172         n_children = len(self.children)
00173 
00174         for c in self.children:
00175             c.status = c.run()
00176             if c.status == TaskStatus.SUCCESS:
00177                 n_success += 1
00178 
00179             if c.status == TaskStatus.FAILURE:
00180                 return TaskStatus.FAILURE
00181 
00182         if n_success == n_children:
00183             return TaskStatus.SUCCESS
00184         else:
00185             return TaskStatus.RUNNING
00186         
00187 class Loop(Task):
00188     """
00189         Loop over one or more subtasks for the given number of iterations
00190         Use the value -1 to indicate a continual loop.
00191     """
00192     def __init__(self, name, announce=True, *args, **kwargs):
00193         super(Loop, self).__init__(name, *args, **kwargs)
00194         
00195         self.iterations = kwargs['iterations']
00196         self.announce = announce
00197         self.loop_count = 0
00198         self.name = name
00199         print("Loop iterations: " + str(self.iterations))
00200         
00201     def run(self):
00202         
00203         while True:
00204             if self.iterations != -1 and self.loop_count >= self.iterations:
00205                 return TaskStatus.SUCCESS
00206                         
00207             for c in self.children:
00208                 while True:
00209                     c.status = c.run()
00210                     
00211                     if c.status == TaskStatus.SUCCESS:
00212                         break
00213 
00214                     return c.status
00215                 
00216                 c.reset()
00217                 
00218             self.loop_count += 1
00219             
00220             if self.announce:
00221                 print(self.name + " COMPLETED " + str(self.loop_count) + " LOOP(S)")
00222 
00223 
00224 class IgnoreFailure(Task):
00225     """
00226         Always return either RUNNING or SUCCESS.
00227     """
00228     def __init__(self, name, *args, **kwargs):
00229         super(IgnoreFailure, self).__init__(name, *args, **kwargs)
00230  
00231     def run(self):
00232         
00233         for c in self.children:
00234             
00235             c.status = c.run()
00236             
00237             if c.status == TaskStatus.FAILURE:
00238                 return TaskStatus.SUCCESS
00239             else:
00240                 return c.status
00241 
00242         return TaskStatus.SUCCESS
00243     
00244 class TaskNot(Task):
00245     """
00246         Turn SUCCESS into FAILURE and vice-versa
00247     """
00248     def __init__(self, name, *args, **kwargs):
00249         super(TaskNot, self).__init__(name, *args, **kwargs)
00250  
00251     def run(self):
00252         
00253         for c in self.children:
00254             
00255             c.status = c.run()
00256             
00257             if c.status == TaskStatus.FAILURE:
00258                 return TaskStatus.SUCCESS
00259             
00260             elif c.status == TaskStatus.FAILURE:
00261                 return TaskStatus.SUCCESS
00262             
00263             else:
00264                 return c.status
00265     
00266 class AutoRemoveSequence(Task):
00267     """ 
00268         Remove each successful subtask from a sequence 
00269     """
00270     def __init__(self, name, *args, **kwargs):
00271         super(AutoRemoveSequence, self).__init__(name, *args, **kwargs)
00272  
00273     def run(self):
00274         for c in self.children:
00275             c.status = c.run()
00276             
00277             if c.status == TaskStatus.FAILURE:
00278                 return TaskStatus.FAILURE
00279             
00280             if c.statuss == TaskStatus.RUNNING:
00281                 return TaskStatus.RUNNING
00282         
00283             try:
00284                 self.children.remove(self.children[0])
00285             except:
00286                 return TaskStatus.FAILURE
00287                 
00288         return TaskStatus.SUCCESS
00289     
00290 class CallbackTask(Task):
00291     """ 
00292         Turn any callback function (cb) into a task
00293     """
00294     def __init__(self, name, cb=None, cb_args=[], cb_kwargs={}, **kwargs):
00295         super(CallbackTask, self).__init__(name, cb=None, cb_args=[], cb_kwargs={}, **kwargs)
00296         
00297         self.name = name
00298         self.cb = cb
00299         self.cb_args = cb_args
00300         self.cb_kwargs = cb_kwargs
00301   
00302     def run(self):
00303         status = self.cb(*self.cb_args, **self.cb_kwargs)
00304          
00305         if status == 0 or status == False:
00306             return TaskStatus.FAILURE
00307         
00308         elif status == 1 or status == True:
00309             return TaskStatus.SUCCESS
00310         
00311         else:
00312             return TaskStatus.RUNNING            
00313             
00314 
00315 class loop(Task):
00316     """
00317         Loop over one or more subtasks a given number of iterations
00318     """
00319     def __init__(self, task, iterations=-1):
00320         new_name = task.name + "_loop_" + str(iterations)
00321         super(loop, self).__init__(new_name)
00322 
00323         self.task = task
00324         self.iterations = iterations
00325         self.old_run = task.run
00326         self.old_reset = task.reset
00327         self.old_children = task.children
00328         self.loop_count = 0
00329         
00330         print("Loop iterations: " + str(self.iterations))
00331         
00332     def run(self):
00333         if self.iterations != -1 and self.loop_count >= self.iterations:
00334             return TaskStatus.SUCCESS
00335 
00336         print("Loop " + str(self.loop_count))
00337             
00338         while True:
00339             self.status = self.old_run()
00340             
00341             if self.status == TaskStatus.SUCCESS:
00342                 break
00343             else:
00344                 return self.status
00345                 
00346         self.old_reset()
00347         self.loop_count += 1
00348 
00349         self.task.run = self.run
00350 
00351         return self.task
00352     
00353 class ignore_failure(Task):
00354     """
00355         Always return either RUNNING or SUCCESS.
00356     """
00357     def __init__(self, task):
00358         new_name = task.name + "_ignore_failure"
00359         super(ignore_failure, self).__init__(new_name)
00360 
00361         self.task = task
00362         self.old_run = task.run
00363         
00364     def run(self):
00365         while True:    
00366             self.status = self.old_run()
00367             
00368             if self.status == TaskStatus.FAILURE:
00369                 return TaskStatus.SUCCESS
00370             else:
00371                 return self.status
00372         
00373         self.task.run = self.run
00374         
00375         return self.task
00376     
00377 class task_not(Task):
00378     """
00379         Turn SUCCESS into FAILURE and vice-versa
00380     """
00381     def __init__(self, task):
00382         new_name = task.name + "_not"
00383         super(task_not, self).__init__(new_name)
00384 
00385         self.task = task
00386         self.old_run = task.run
00387         
00388     def run(self):
00389         while True:    
00390             self.status = self.old_run()
00391             
00392             if self.status == TaskStatus.FAILURE:
00393                 return TaskStatus.SUCCESS
00394             
00395             elif self.status == TaskStatus.FAILURE:
00396                 return TaskStatus.SUCCESS
00397             
00398             else:
00399                 return self.status
00400         
00401         self.task.run = self.run
00402         
00403         return self.task
00404 
00405 def print_tree(tree, indent=0):
00406     """
00407         Print an ASCII representation of the tree
00408     """
00409     for c in tree.children:
00410         print "    " * indent, "-->", c.name
00411         
00412         if c.children != []:
00413             print_tree(c, indent+1)
00414             
00415 def print_phpsyntax_tree(tree):    
00416     """
00417         Print an output compatible with ironcreek.net/phpSyntaxTree
00418     """
00419     for c in tree.children:
00420         print "[" + string.replace(c.name, "_", "."),
00421         if c.children != []:
00422             print_phpsyntax_tree(c),
00423         print "]",
00424     
00425 # def print_dot_tree(root):
00426 #     """
00427 #         Print an output compatible with the DOT synatax and Graphiz
00428 #     """
00429 #     gr = pgv.AGraph(rotate='0', bgcolor='lightyellow')
00430 #     gr.node_attr['fontsize']='9'
00431 #                 
00432 #     def add_edges(root):
00433 #         for c in root.children:
00434 #             gr.add_edge((root.name, c.name))
00435 #             if c.children != []:
00436 #                 add_edges(c)
00437 #                 
00438 #     add_edges(root)
00439 #     
00440 #     st, order = breadth_first_search(gr, root=root.name)
00441 #     
00442 #     gst = digraph()
00443 #     gst.add_spanning_tree(st)
00444 #     
00445 #     dot = write(gst)
00446 #     gvv = gv.readstring(dot)
00447 #     
00448 #     gv.layout(gvv,'dot')
00449 #     gv.render(gvv,'png','tree.png')
00450     
00451     #gr.write("tree.dot")
00452     
00453     # Draw as PNG
00454     #gr.layout(prog='dot')
00455     #gr.draw('tree.png')
00456     
00457                 
00458 
00459  


pi_trees_lib
Author(s): Patrick Goebel
autogenerated on Fri Aug 28 2015 12:02:23