Project

General

Profile

Task planning » History » Version 1

Florian Seguin, 2020-09-10 11:41

1 1 Florian Seguin
h1. Task planification
2
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.
3
Right now, the module is under heavy development and thus may contain bugs, missing features, wrong models... 
4
5
h2. HTN, pyhop and plans
6
7
Hierarchical task network (HTN) is a planification method where every action is structured. There are three main parts that compose the network :
8
- Primitive tasks : they represent roughly an action of the UAV
9
- 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 
10
- 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.
11
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.
12
13
For modeling our tree of decisions and solving we use a modified version of pyhop, a planner written by Dana Nau.
14
We need to first create a description of our "world", and define each primitive, compound and decision tasks.
15
16
<pre><code class="python"># Creating primitive tasks
17
def simple_task1(state, goal):
18
    # Doing stuff with state
19
    return state
20
21
def simple_task2(state, goal):
22
    # Doing other stuff with state
23
    return state
24
25
# Creating compound tasks
26
def compound_task1(state, goal):
27
    return [('simple_task1', goal), ('simple_task2', goal)]
28
29
def compound_task2(state, goal):
30
    return [('simple_task2', goal), ('simple_task1', goal)]
31
32
# Pyhop needs to know the primitive tasks
33
pyhop.declare_operators(simple_task1, simple_task2)
34
35
# You can declare your decisions methods this way
36
pyhop.declare_methods('objective', compound_task1, compound_task2)
37
</code></pre>
38
39
Once the "world" is described, we can try to solve the problem.
40
<pre><code class="python"># We solve the problem by calling solve method of pyhop
41
pyhop.solve(state, ['objective', goal])
42
</code></pre>
43
44
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').
45
46
h2. Translation and transmitting plans to the UAV
47
48
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.
49
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.
50
To create a plan, you can use the method create_plan.
51
<pre><code class="python">def create_plan(self, uavId, objective, goalParams, homeParams, minimizeBy='bat', verbose=0):
52
"""
53
Create a plan using the different informations passed in parameters. The
54
plan is created via a PlanObjective object. Translates the plan and
55
updates the uavsPlan attribute
56
Parameters
57
----------
58
uavId : int                                                                                                                                      
59
    The id of the UAV
60
objective : str
61
    The string of the objective we want to fulfill
62
goalParams : dict
63
    The goal parameters (i.e. the position of the cloud we target)                                                                                                                             
64
homeParams : dict
65
    The home parameters (i.e. the position where UAVs are launched)
66
minimizeBy : str
67
    The parameter to minimize (time, bat...)
68
verbose : int
69
    Used for printing
70
"""
71
    initState = {}
72
    for uId, uStatus in self.uavsStatus.items():
73
        initState[uId] = copy.deepcopy(self.uavsStatus[uId]).to_dict()                                                                               
74
        initState[uId].update(self.uavsParameters[uId])                                                                                              
75
    planObject = PlanObjective(uavId, objective,
76
                               state=initState, goal=goalParams, home=homeParams)
77
    self.uavsPlan[uavId] = self.translate_plan(uavId, planObject.solve(minimizeBy, verbose=verbose))  
78
</code></pre>
79
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.
80
Once translated, the plan is stored into the associated variable. When a UAV is accepting or deleting a plan computed, this variable becomes empty. 
81
**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**.
82
83
h2. Executing a UAV plan 
84
85
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.
86
To accept a plan you can use the __accept_plan()__ method of the PlanUpdater plugin.
87
To reject a plan you can use the __reject_plan()__ method of the PlanUpdater plugin.
88
To send the missions to the autopilot and update the executingPlan variable, you can use the __execute_plan()__ method of the PlanUpdater plugin.
89
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.
90
The callback used to update the executingPlan :
91
<pre><code class="python">def mission_status_callback(self, missionStatus):
92
"""
93
Callback called whenever a message missionStatus is received. Updates
94
the executingPlan attribute by popping missions from it
95
(Note : the callback seems buggy to me, TBD)
96
Parameters
97
----------
98
missionStatus : dict
99
    The messages containing the current indexes of the missions being
100
    executed and the remaining time of the current mission.
101
"""
102
    index_list = missionStatus['index_list']
103
    remaining_time = missionStatus['remaining_time']
104
    popped_missions = []
105
        for index in self.executingPlan: # Popping loop : removing finished tasks
106
            if index not in index_list:
107
                popped_missions.append(self.pop_plan().missionId)
108
        self.remainingTime = remaining_time
109
        print(popped_missions)
110
</code></pre>
111
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).