Project

General

Profile

Task planning » History » Version 2

Florian Seguin, 2020-09-10 11:41

1 1 Florian Seguin
h1. Task planification
2 2 Florian Seguin
3 1 Florian Seguin
The Planner module can be used to create a Plan using a modified version of "Dana Nau's pyhop":http://www.cs.umd.edu/projects/shop/index.html. The plan created is then translated into a sequence of mission types objects from the base module. This sequence can be sent to the autopilot and executed from there.
4
Right now, the module is under heavy development and thus may contain bugs, missing features, wrong models... 
5
6
h2. HTN, pyhop and plans
7
8
Hierarchical task network (HTN) is a planification method where every action is structured. There are three main parts that compose the network :
9
- Primitive tasks : they represent roughly an action of the UAV
10
- Compound tasks : they represent a sequence of actions ordered. The compound task is only feasible when all actions can be done in the order given by the compound task 
11
- Decision tasks : they represent a choice between different ways of doing the task. A decision task is feasible whenever a way of doing the task is feasible.
12
These actions are then structured into a tree, where each leaf is a primitive task and every other node is a decision or a compound task.
13
14
For modeling our tree of decisions and solving we use a modified version of pyhop, a planner written by Dana Nau.
15
We need to first create a description of our "world", and define each primitive, compound and decision tasks.
16
17
<pre><code class="python"># Creating primitive tasks
18
def simple_task1(state, goal):
19
    # Doing stuff with state
20
    return state
21
22
def simple_task2(state, goal):
23
    # Doing other stuff with state
24
    return state
25
26
# Creating compound tasks
27
def compound_task1(state, goal):
28
    return [('simple_task1', goal), ('simple_task2', goal)]
29
30
def compound_task2(state, goal):
31
    return [('simple_task2', goal), ('simple_task1', goal)]
32
33
# Pyhop needs to know the primitive tasks
34
pyhop.declare_operators(simple_task1, simple_task2)
35
36
# You can declare your decisions methods this way
37
pyhop.declare_methods('objective', compound_task1, compound_task2)
38
</code></pre>
39
40
Once the "world" is described, we can try to solve the problem.
41
<pre><code class="python"># We solve the problem by calling solve method of pyhop
42
pyhop.solve(state, ['objective', goal])
43
</code></pre>
44
45
Using our precedent examples, we can draw our tree and explore it. Instead of returning a plan after one is found, it explores always all the tree and returns the plan with a the minimum cost based on a criteria. Since we are working with UAVs, these criterias can be the battery ('bat') or the time ('time').
46
47
h2. Translation and transmitting plans to the UAV
48
49
To translate and transmit a plan to the UAV, we use the PlanManager class. The PlanManager class is a centralized class where every UAV using a specific plugin (the PlanUpdater plugin) is registered. We will get into it later.
50
Since it is centralized, the class itself is a Singleton, meaning there can only be one instance of the object at time. It contains the current state of the UAVs, and other objects related to UAVs.
51
To create a plan, you can use the method create_plan.
52
<pre><code class="python">def create_plan(self, uavId, objective, goalParams, homeParams, minimizeBy='bat', verbose=0):
53
"""
54
Create a plan using the different informations passed in parameters. The
55
plan is created via a PlanObjective object. Translates the plan and
56
updates the uavsPlan attribute
57
Parameters
58
----------
59
uavId : int                                                                                                                                      
60
    The id of the UAV
61
objective : str
62
    The string of the objective we want to fulfill
63
goalParams : dict
64
    The goal parameters (i.e. the position of the cloud we target)                                                                                                                             
65
homeParams : dict
66
    The home parameters (i.e. the position where UAVs are launched)
67
minimizeBy : str
68
    The parameter to minimize (time, bat...)
69
verbose : int
70
    Used for printing
71
"""
72
    initState = {}
73
    for uId, uStatus in self.uavsStatus.items():
74
        initState[uId] = copy.deepcopy(self.uavsStatus[uId]).to_dict()                                                                               
75
        initState[uId].update(self.uavsParameters[uId])                                                                                              
76
    planObject = PlanObjective(uavId, objective,
77
                               state=initState, goal=goalParams, home=homeParams)
78
    self.uavsPlan[uavId] = self.translate_plan(uavId, planObject.solve(minimizeBy, verbose=verbose))  
79
</code></pre>
80
Note : we use an object called PlanObjective to translate our plan into pyhop goal and state objects. We also translate immediately the plan into a sequence of nephelae.mission.types.MissionTypes.
81
Once translated, the plan is stored into the associated variable. When a UAV is accepting or deleting a plan computed, this variable becomes empty. 
82
**Note that if another plan is computed for the same UAV, the previous plan that was contained into the associated variable is erased and replaced by the new one**.
83
84
h2. Executing a UAV plan 
85
86
After a plan has been accepted by the UAV (note that you need to plug the PlanUpdater plugin to the UAV to accept/reject plans), it is stored inside a dedicated variable called currentPlan. This plan can be accepted and sent to the autopilot.
87
To accept a plan you can use the __accept_plan()__ method of the PlanUpdater plugin.
88
To reject a plan you can use the __reject_plan()__ method of the PlanUpdater plugin.
89
To send the missions to the autopilot and update the executingPlan variable, you can use the __execute_plan()__ method of the PlanUpdater plugin.
90
The indices of the missions are duplicated and stored into another variable called executingPlan. This variable is then updated periodically with the missionStatus messages emitting from the autopilot.
91
The callback used to update the executingPlan :
92
<pre><code class="python">def mission_status_callback(self, missionStatus):
93
"""
94
Callback called whenever a message missionStatus is received. Updates
95
the executingPlan attribute by popping missions from it
96
(Note : the callback seems buggy to me, TBD)
97
Parameters
98
----------
99
missionStatus : dict
100
    The messages containing the current indexes of the missions being
101
    executed and the remaining time of the current mission.
102
"""
103
    index_list = missionStatus['index_list']
104
    remaining_time = missionStatus['remaining_time']
105
    popped_missions = []
106
        for index in self.executingPlan: # Popping loop : removing finished tasks
107
            if index not in index_list:
108
                popped_missions.append(self.pop_plan().missionId)
109
        self.remainingTime = remaining_time
110
        print(popped_missions)
111
</code></pre>
112
This callback is contained inside the PlanUpdater plugin. Note that it might contain some bugs (for instance, a discrepancy between an old missionStatus message and a new executingPlan could empty the executingPlan variable).