12
12
from MAVProxy .modules .lib import multiproc
13
13
from MAVProxy .modules .lib import mp_module
14
14
from MAVProxy .modules .lib import mp_settings
15
+ from MAVProxy .modules .lib .mp_settings import MPSetting
15
16
from MAVProxy .modules .lib import mp_util
16
17
17
18
from MAVProxy .modules .mavproxy_map import mp_slipmap
21
22
22
23
if mp_util .has_wxpython :
23
24
from MAVProxy .modules .lib .mp_menu import MPMenuSubMenu
25
+ from MAVProxy .modules .lib .wxsettings import WXSettings
24
26
25
27
from pymavlink import mavutil
28
+ from pymavlink .rotmat import Vector3
26
29
27
30
# terrain navigation
28
31
from terrain_nav_py .dubins_airplane import DubinsAirplaneStateSpace
29
32
from terrain_nav_py .grid_map import GridMapSRTM
30
33
from terrain_nav_py .path import Path
34
+ from terrain_nav_py .path_segment import PathSegment
31
35
from terrain_nav_py .terrain_map import TerrainMap
32
36
from terrain_nav_py .terrain_ompl_rrt import TerrainOmplRrt
33
37
34
38
35
39
class TerrainNavModule (mp_module .MPModule ):
36
- def __init__ (self , mpstate : MPState ) -> None :
37
- super ().__init__ (mpstate , "terrainnav" , "terrain navigation module" )
38
40
39
- # TODO: some of these settings should be extracted from params
40
- # *** planner settings ***
41
- self .terrainnav_settings = mp_settings .MPSettings (
41
+ # TODO: some of these settings should be extracted from params
42
+ @staticmethod
43
+ def default_settings ():
44
+ return mp_settings .MPSettings (
42
45
[
43
- ("loiter_agl_alt" , float , 60.0 ),
44
- ("loiter_radius" , float , 60.0 ),
45
- ("turning_radius" , float , 60.0 ),
46
- ("climb_angle_deg" , float , 8.0 ),
47
- ("max_agl_alt" , float , 100.0 ),
48
- ("min_agl_alt" , float , 50.0 ),
49
- ("grid_spacing" , float , 30.0 ),
50
- ("grid_length" , float , 10000.0 ),
51
- ("time_budget" , float , 20.0 ),
52
- ("resolution" , float , 100.0 ),
53
- ("wp_spacing" , float , 60.0 ),
46
+ MPSetting ("loiter_agl_alt" , float , 60.0 ),
47
+ MPSetting ("loiter_radius" , float , 60.0 ), # WP_LOITER_RADIUS
48
+ MPSetting ("turning_radius" , float , 60.0 ), # WP_LOITER_RADIUS
49
+ MPSetting ("climb_angle_deg" , float , 8.0 ), # TECS_CLMB_MAX
50
+ MPSetting ("max_agl_alt" , float , 100.0 ),
51
+ MPSetting ("min_agl_alt" , float , 50.0 ),
52
+ MPSetting ("grid_spacing" , float , 30.0 ),
53
+ MPSetting ("grid_length" , float , 10000.0 ),
54
+ MPSetting ("time_budget" , float , 20.0 ),
55
+ MPSetting ("resolution" , float , 100.0 ),
56
+ MPSetting (
57
+ "wp_generator" ,
58
+ str ,
59
+ "SimpleWaypoints" ,
60
+ choice = ["SimpleWaypoints" , "UseLoiterToAlt" ],
61
+ ),
62
+ MPSetting ("wp_spacing" , float , 60.0 ),
63
+ MPSetting ("wp_min_loiter_angle_deg" , float , 45.0 ),
54
64
]
55
65
)
56
66
67
+ def __init__ (self , mpstate : MPState ) -> None :
68
+ super ().__init__ (mpstate , "terrainnav" , "terrain navigation module" )
69
+
70
+ # *** planner settings ***
71
+ self .terrainnav_settings = TerrainNavModule .default_settings ()
72
+
57
73
# *** commands ***
58
74
cmdname = "terrainnav"
59
75
# TODO: do not support multi-instance or multi-vehicle yet
@@ -119,7 +135,7 @@ def __init__(self, mpstate: MPState) -> None:
119
135
# *** fence state ***
120
136
self ._fence_change_time = 0
121
137
122
- # *** multiprocessing ***
138
+ # *** planner multiprocessing ***
123
139
self ._planner_process = None
124
140
self ._parent_pipe_recv , self ._planner_pipe_send = multiproc .Pipe (duplex = False )
125
141
self ._planner_pipe_recv , self ._parent_pipe_send = multiproc .Pipe (duplex = False )
@@ -251,7 +267,7 @@ def process_ui_msgs(self):
251
267
elif isinstance (msg , terrainnav_msgs .RunPlanner ):
252
268
self ._parent_pipe_send .send (PlannerCmdRunPlanner ())
253
269
elif isinstance (msg , terrainnav_msgs .GenWaypoints ):
254
- self .gen_waypoints ()
270
+ self .generate_waypoints ()
255
271
elif isinstance (msg , terrainnav_msgs .ClearPath ):
256
272
self .clear_path ()
257
273
elif isinstance (msg , terrainnav_msgs .ClearWaypoints ):
@@ -287,6 +303,8 @@ def process_ui_msgs(self):
287
303
self .hide_planner_boundary ()
288
304
elif isinstance (msg , terrainnav_msgs .MoveBoundary ):
289
305
self .move_planner_boundary ()
306
+ elif isinstance (msg , terrainnav_msgs .Settings ):
307
+ self .edit_settings ()
290
308
else :
291
309
# TODO: raise an exception
292
310
if self .is_debug :
@@ -408,6 +426,12 @@ def move_planner_boundary(self):
408
426
409
427
self ._parent_pipe_send .send (PlannerGridLatLon ((lat , lon )))
410
428
429
+ def edit_settings (self ):
430
+ """
431
+ Open the settings dialog
432
+ """
433
+ WXSettings (self .terrainnav_settings )
434
+
411
435
def draw_circle (self , id , lat , lon , radius , colour ):
412
436
map_module = self .module ("map" )
413
437
if map_module is None :
@@ -680,7 +704,16 @@ def draw_states(self, states):
680
704
)
681
705
map_module .map .add_object (slip_polygon )
682
706
683
- def gen_waypoints (self ):
707
+ def generate_waypoints (self ):
708
+ wp_gen = self .terrainnav_settings .wp_generator
709
+ if wp_gen == "SimpleWaypoints" :
710
+ self .wp_gen_simple_waypoints ()
711
+ elif wp_gen == "UseLoiterToAlt" :
712
+ self .wp_gen_use_loiter_to_alt ()
713
+ else :
714
+ print (f"[terrainnav] invalid WP generator: { wp_gen } " )
715
+
716
+ def wp_gen_simple_waypoints (self ):
684
717
path = self ._candidate_path
685
718
map_lat = self ._grid_map_lat
686
719
map_lon = self ._grid_map_lon
@@ -692,7 +725,7 @@ def gen_waypoints(self):
692
725
if wp_module is None :
693
726
return
694
727
695
- # TODO: provide accessors on Path - fix upstream
728
+ # TODO: provide accessors on Path - fix upstream
696
729
# TODO: dt is not set - fix upstream
697
730
wp_spacing = self .terrainnav_settings .wp_spacing
698
731
wp_num_total = 0
@@ -770,6 +803,135 @@ def gen_waypoints(self):
770
803
# tell the wp module to expect some waypoints
771
804
wp_module .loading_waypoints = True
772
805
806
+ def wp_gen_use_loiter_to_alt (self ):
807
+ path = self ._candidate_path
808
+ map_lat = self ._grid_map_lat
809
+ map_lon = self ._grid_map_lon
810
+
811
+ if path is None or map_lat is None or map_lon is None :
812
+ return
813
+
814
+ wp_module = self .module ("wp" )
815
+ if wp_module is None :
816
+ return
817
+
818
+ # TODO: provide accessors on Path
819
+ mission_items = []
820
+ wp_num = 0
821
+ for i , segment in enumerate (path ._segments ):
822
+ position3 = segment .first_state ().position
823
+ tangent3 = (segment .first_state ().velocity ).normalized ()
824
+ curvature = segment .curvature
825
+ position2 = Vector3 (position3 .x , position3 .y , 0.0 )
826
+ tangent2 = Vector3 (tangent3 .x , tangent3 .y , 0.0 )
827
+
828
+ start_alt = segment .first_state ().position .z
829
+
830
+ (end_lat , end_lon ) = mp_util .gps_offset (
831
+ map_lat ,
832
+ map_lon ,
833
+ segment .last_state ().position .x ,
834
+ segment .last_state ().position .y ,
835
+ )
836
+ end_alt = segment .last_state ().position .z
837
+ end_yaw_deg = math .degrees (segment .last_state ().attitude .euler [2 ])
838
+
839
+ segment_delta_alt = end_alt - start_alt
840
+ segment_length = segment .get_length ()
841
+ segment_gamma = math .asin (segment_delta_alt / segment_length )
842
+ segment_gamma_deg = math .degrees (segment_gamma )
843
+ # TODO: remove debug print
844
+ # print(
845
+ # f"[{wp_num}] delta_alt: {segment_delta_alt}, "
846
+ # f"length: {segment_length}, gamma_deg: {segment_gamma_deg}"
847
+ # )
848
+
849
+ # waypoint number - should also include home position
850
+ sys_id = self .mpstate .settings .target_system
851
+ cmp_id = self .mpstate .settings .target_component
852
+ pass_radius = 0.0
853
+ if curvature != 0.0 :
854
+ # curvature in ENU, CCW is negative, CW is positive
855
+ pass_radius = 1.0 if curvature < 0 else - 1.0
856
+
857
+ # do not add a loiter for short turns
858
+ radius = 1.0 / math .fabs (curvature )
859
+ phi = segment_length / radius
860
+ min_phi = math .radians (self .terrainnav_settings .wp_min_loiter_angle_deg )
861
+
862
+ if phi >= min_phi :
863
+ arc_centre = PathSegment .get_arc_centre (
864
+ position2 , tangent2 , curvature
865
+ )
866
+ (cen_lat , cen_lon ) = mp_util .gps_offset (
867
+ map_lat ,
868
+ map_lon ,
869
+ arc_centre .x ,
870
+ arc_centre .y ,
871
+ )
872
+
873
+ # MAV_CMD_NAV_LOITER_TO_ALT (31)
874
+ p1 = 1.0 # heading required: 0: no, 1: yes
875
+ p2 = - 1.0 / curvature # radius: > 0 loiter CW, < 0 loiter CCW
876
+ p3 = 0.0
877
+ p4 = 1.0 # loiter exit location: 1: line between exit and next wp.
878
+ mission_item = mavutil .mavlink .MAVLink_mission_item_message (
879
+ target_system = sys_id ,
880
+ target_component = cmp_id ,
881
+ seq = wp_num ,
882
+ frame = mavutil .mavlink .MAV_FRAME_GLOBAL ,
883
+ command = mavutil .mavlink .MAV_CMD_NAV_LOITER_TO_ALT ,
884
+ current = 0 ,
885
+ autocontinue = 1 ,
886
+ param1 = p1 ,
887
+ param2 = p2 ,
888
+ param3 = p3 ,
889
+ param4 = p4 ,
890
+ x = cen_lat ,
891
+ y = cen_lon ,
892
+ z = end_alt ,
893
+ )
894
+ mission_items .append (mission_item )
895
+ wp_num += 1
896
+
897
+ # mission item MAV_CMD_NAV_WAYPOINT (16)
898
+ p1 = 0.0 # hold
899
+ p2 = 0.0 # accept radius
900
+ p3 = 0.0 # pass_radius # pass radius - not working?
901
+ p4 = 0.0 # end_yaw_deg # yaw at waypoint - not working?
902
+ mission_item = mavutil .mavlink .MAVLink_mission_item_message (
903
+ target_system = sys_id ,
904
+ target_component = cmp_id ,
905
+ seq = wp_num ,
906
+ frame = mavutil .mavlink .MAV_FRAME_GLOBAL ,
907
+ command = mavutil .mavlink .MAV_CMD_NAV_WAYPOINT ,
908
+ current = 0 ,
909
+ autocontinue = 1 ,
910
+ param1 = p1 ,
911
+ param2 = p2 ,
912
+ param3 = p3 ,
913
+ param4 = p4 ,
914
+ x = end_lat ,
915
+ y = end_lon ,
916
+ z = end_alt ,
917
+ )
918
+ mission_items .append (mission_item )
919
+ wp_num += 1
920
+
921
+ # prepare waypoints for load
922
+ wp_module .wploader .clear ()
923
+ wp_module .wploader .expected_count = len (mission_items )
924
+ self .mpstate .master ().waypoint_count_send (len (mission_items ))
925
+ for w in mission_items :
926
+ wp_module .wploader .add (w )
927
+ wsend = wp_module .wploader .wp (w .seq )
928
+ if self .mpstate .settings .wp_use_mission_int :
929
+ wsend = wp_module .wp_to_mission_item_int (w )
930
+ self .mpstate .master ().mav .send (wsend )
931
+
932
+ # tell the wp module to expect some waypoints
933
+ wp_module .loading_waypoints = True
934
+
773
935
@property
774
936
def is_debug (self ):
775
937
return self .mpstate .settings .moddebug > 1
@@ -1196,8 +1358,6 @@ def init_terrain_map(self):
1196
1358
# if self.is_debug:
1197
1359
# print(f"[TerrainPlanner] calculating distance-surface...", end="")
1198
1360
# self._grid_map.addLayerDistanceTransform(surface_distance=self.terrainnav_settings.min_agl_alt)
1199
- # if self.is_debug:
1200
- # print(f"done.")
1201
1361
1202
1362
self ._terrain_map = TerrainMap ()
1203
1363
self ._terrain_map .setGridMap (self ._grid_map )
@@ -1276,12 +1436,6 @@ def init_planner(self):
1276
1436
resolution_requested = self ._resolution / self ._grid_length
1277
1437
problem .setStateValidityCheckingResolution (resolution_requested )
1278
1438
1279
- # TODO: enable debug - will need message from mddule
1280
- # if self.is_debug:
1281
- # si = problem.getSpaceInformation()
1282
- # resolution_used = si.getStateValidityCheckingResolution()
1283
- # print(f"[TerrainPlanner] resolution used: {resolution_used}")
1284
-
1285
1439
self ._lock .release ()
1286
1440
1287
1441
def run_planner (self ):
0 commit comments