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). |