Skip to content

Commit 385ec2a

Browse files
committed
terrain_nav: add alt waypoint generator and settings dialog
- Add WP generator that uses MAV_CMD_NAV_LOITER_TO_ALT - Update window size - Use wxsettings module for dialog. - Ensure dialog retrieves settings from correct thread. Signed-off-by: Rhys Mainwaring <rhys.mainwaring@me.com>
1 parent d5556fa commit 385ec2a

File tree

4 files changed

+262
-55
lines changed

4 files changed

+262
-55
lines changed

MAVProxy/modules/mavproxy_terrainnav/terrainnav.py

Lines changed: 182 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from MAVProxy.modules.lib import multiproc
1313
from MAVProxy.modules.lib import mp_module
1414
from MAVProxy.modules.lib import mp_settings
15+
from MAVProxy.modules.lib.mp_settings import MPSetting
1516
from MAVProxy.modules.lib import mp_util
1617

1718
from MAVProxy.modules.mavproxy_map import mp_slipmap
@@ -21,39 +22,54 @@
2122

2223
if mp_util.has_wxpython:
2324
from MAVProxy.modules.lib.mp_menu import MPMenuSubMenu
25+
from MAVProxy.modules.lib.wxsettings import WXSettings
2426

2527
from pymavlink import mavutil
28+
from pymavlink.rotmat import Vector3
2629

2730
# terrain navigation
2831
from terrain_nav_py.dubins_airplane import DubinsAirplaneStateSpace
2932
from terrain_nav_py.grid_map import GridMapSRTM
3033
from terrain_nav_py.path import Path
34+
from terrain_nav_py.path_segment import PathSegment
3135
from terrain_nav_py.terrain_map import TerrainMap
3236
from terrain_nav_py.terrain_ompl_rrt import TerrainOmplRrt
3337

3438

3539
class TerrainNavModule(mp_module.MPModule):
36-
def __init__(self, mpstate: MPState) -> None:
37-
super().__init__(mpstate, "terrainnav", "terrain navigation module")
3840

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(
4245
[
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),
5464
]
5565
)
5666

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+
5773
# *** commands ***
5874
cmdname = "terrainnav"
5975
# TODO: do not support multi-instance or multi-vehicle yet
@@ -119,7 +135,7 @@ def __init__(self, mpstate: MPState) -> None:
119135
# *** fence state ***
120136
self._fence_change_time = 0
121137

122-
# *** multiprocessing ***
138+
# *** planner multiprocessing ***
123139
self._planner_process = None
124140
self._parent_pipe_recv, self._planner_pipe_send = multiproc.Pipe(duplex=False)
125141
self._planner_pipe_recv, self._parent_pipe_send = multiproc.Pipe(duplex=False)
@@ -251,7 +267,7 @@ def process_ui_msgs(self):
251267
elif isinstance(msg, terrainnav_msgs.RunPlanner):
252268
self._parent_pipe_send.send(PlannerCmdRunPlanner())
253269
elif isinstance(msg, terrainnav_msgs.GenWaypoints):
254-
self.gen_waypoints()
270+
self.generate_waypoints()
255271
elif isinstance(msg, terrainnav_msgs.ClearPath):
256272
self.clear_path()
257273
elif isinstance(msg, terrainnav_msgs.ClearWaypoints):
@@ -287,6 +303,8 @@ def process_ui_msgs(self):
287303
self.hide_planner_boundary()
288304
elif isinstance(msg, terrainnav_msgs.MoveBoundary):
289305
self.move_planner_boundary()
306+
elif isinstance(msg, terrainnav_msgs.Settings):
307+
self.edit_settings()
290308
else:
291309
# TODO: raise an exception
292310
if self.is_debug:
@@ -408,6 +426,12 @@ def move_planner_boundary(self):
408426

409427
self._parent_pipe_send.send(PlannerGridLatLon((lat, lon)))
410428

429+
def edit_settings(self):
430+
"""
431+
Open the settings dialog
432+
"""
433+
WXSettings(self.terrainnav_settings)
434+
411435
def draw_circle(self, id, lat, lon, radius, colour):
412436
map_module = self.module("map")
413437
if map_module is None:
@@ -680,7 +704,16 @@ def draw_states(self, states):
680704
)
681705
map_module.map.add_object(slip_polygon)
682706

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):
684717
path = self._candidate_path
685718
map_lat = self._grid_map_lat
686719
map_lon = self._grid_map_lon
@@ -692,7 +725,7 @@ def gen_waypoints(self):
692725
if wp_module is None:
693726
return
694727

695-
# TODO: provide accessors on Path - fix upstream
728+
# TODO: provide accessors on Path - fix upstream
696729
# TODO: dt is not set - fix upstream
697730
wp_spacing = self.terrainnav_settings.wp_spacing
698731
wp_num_total = 0
@@ -770,6 +803,135 @@ def gen_waypoints(self):
770803
# tell the wp module to expect some waypoints
771804
wp_module.loading_waypoints = True
772805

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+
773935
@property
774936
def is_debug(self):
775937
return self.mpstate.settings.moddebug > 1
@@ -1196,8 +1358,6 @@ def init_terrain_map(self):
11961358
# if self.is_debug:
11971359
# print(f"[TerrainPlanner] calculating distance-surface...", end="")
11981360
# self._grid_map.addLayerDistanceTransform(surface_distance=self.terrainnav_settings.min_agl_alt)
1199-
# if self.is_debug:
1200-
# print(f"done.")
12011361

12021362
self._terrain_map = TerrainMap()
12031363
self._terrain_map.setGridMap(self._grid_map)
@@ -1276,12 +1436,6 @@ def init_planner(self):
12761436
resolution_requested = self._resolution / self._grid_length
12771437
problem.setStateValidityCheckingResolution(resolution_requested)
12781438

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-
12851439
self._lock.release()
12861440

12871441
def run_planner(self):

MAVProxy/modules/mavproxy_terrainnav/terrainnav_app.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ def ui_task(self):
4444
# create the wx application and pass self as the state
4545
app = wx.App()
4646
app.frame = terrainnav_ui.TerrainNavFrame(
47-
state=self, title=self.title, size=(360, 300)
47+
state=self, title=self.title, size=(460, 340)
4848
)
4949
app.frame.SetDoubleBuffered(True)
5050
app.frame.Show()
@@ -111,6 +111,8 @@ def ui_is_alive(self):
111111
"""
112112
multiproc.freeze_support()
113113
app = TerrainNavApp(title="Terrain Navigation")
114+
app.start_ui()
115+
114116
while app.ui_is_alive():
115117
print("terrain navigation app is alive")
116-
time.sleep(0.5)
118+
time.sleep(0.1)

MAVProxy/modules/mavproxy_terrainnav/terrainnav_msgs.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,3 +91,7 @@ def __init__(self):
9191
class ClearWaypoints:
9292
def __init__(self):
9393
pass
94+
95+
class Settings:
96+
def __init__(self):
97+
pass

0 commit comments

Comments
 (0)