11#!/usr/bin/env python3
22'''mode command handling'''
33
4+ import collections
5+ import math
6+
47from pymavlink import mavutil
58
69from MAVProxy .modules .lib import mp_module
1013AP_FLAKE8_CLEAN
1114'''
1215
16+ # names for MAV_CMD_DO_ORBIT param3, ORBIT_YAW_BEHAVIOUR values; NaN
17+ # leaves the choice to the vehicle
18+ orbit_yaw_behaviours = {
19+ "Default" : float ('NaN' ),
20+ "FaceCentre" : mavutil .mavlink .ORBIT_YAW_BEHAVIOUR_HOLD_FRONT_TO_CIRCLE_CENTER ,
21+ "InitialHeading" : mavutil .mavlink .ORBIT_YAW_BEHAVIOUR_HOLD_INITIAL_HEADING ,
22+ "Uncontrolled" : mavutil .mavlink .ORBIT_YAW_BEHAVIOUR_UNCONTROLLED ,
23+ "Tangent" : mavutil .mavlink .ORBIT_YAW_BEHAVIOUR_HOLD_FRONT_TANGENT_TO_CIRCLE ,
24+ "RCControlled" : mavutil .mavlink .ORBIT_YAW_BEHAVIOUR_RC_CONTROLLED ,
25+ "Unchanged" : mavutil .mavlink .ORBIT_YAW_BEHAVIOUR_UNCHANGED ,
26+ }
27+
28+ orbit_frames = ['AboveHome' , 'AGL' , 'AMSL' ]
29+
30+ # single source of truth for the "orbit" command arguments: drives
31+ # command-line parsing, command-line help, and the map "Orbit Here" popup
32+ # dialog. See MPModule.parse_key_value_spec / format_key_value_help and
33+ # MPMenuCallMultiTextDropdownDialog.from_arg_spec for the facets consumed.
34+ # Keys without a 'label' (loc/lat/lng) are command-line only: the popup
35+ # takes its centre from the map click.
36+ orbit_arg_spec = collections .OrderedDict ([
37+ ('radius' , dict (type = float , required = True ,
38+ help = 'metres, positive for clockwise, negative for counter-clockwise' ,
39+ label = 'Radius (m, -ve for CCW)' , default = 50 )),
40+ ('alt' , dict (type = float , synonyms = ['altitude' ],
41+ help = 'orbit altitude' ,
42+ label = 'Altitude' , default = lambda ctx : ctx ['guidedalt' ])),
43+ ('velocity' , dict (type = str ,
44+ help = "tangential velocity in m/s, 'default' for the vehicle default" ,
45+ label = 'Velocity (m/s)' , default = 'Default' )),
46+ ('orbits' , dict (type = float ,
47+ help = 'number of circuits to fly, 0 to orbit forever' ,
48+ label = 'Orbits (0 for forever)' , default = 0 )),
49+ ('yaw' , dict (type = str , options = list (orbit_yaw_behaviours .keys ()),
50+ help = 'one of %s or an ORBIT_YAW_BEHAVIOUR enumeration value' %
51+ '|' .join (orbit_yaw_behaviours .keys ()),
52+ label = 'Yaw Behaviour' , default = 'Default' )),
53+ ('frame' , dict (type = str , options = orbit_frames ,
54+ help = 'altitude frame' ,
55+ label = 'Frame' , default = lambda ctx : ctx ['flytoframe' ])),
56+ ('loc' , dict (type = str ,
57+ help = 'orbit centre as LAT,LNG or LAT,LNG,ALT; defaults to the map click position' )),
58+ ('lat' , dict (type = float , synonyms = ['latitude' ], help = 'orbit centre latitude' )),
59+ ('lng' , dict (type = float , synonyms = ['longitude' , 'lon' ], help = 'orbit centre longitude' )),
60+ ])
61+
1362
1463class ModeModule (mp_module .MPModule ):
1564 def __init__ (self , mpstate ):
@@ -18,6 +67,7 @@ def __init__(self, mpstate):
1867 '(MODE)'
1968 ])
2069 self .add_command ('guided' , self .cmd_guided , "fly to a clicked location on map" )
70+ self .add_command ('orbit' , self .cmd_orbit , "orbit around a clicked location on map" )
2171 self .add_command ('confirm' , self .cmd_confirm , "confirm a command" )
2272 self .add_completion_function ('(MODE)' , self .complete_available_modes )
2373
@@ -140,6 +190,93 @@ def cmd_guided(self, args):
140190 altitude
141191 )
142192
193+ def cmd_orbit_usage (self ):
194+ '''print usage for the orbit command'''
195+ print ("Usage: orbit radius=RADIUS [alt=ALTITUDE] [velocity=VELOCITY] "
196+ "[orbits=ORBITS] [yaw=YAW] [frame=%s]" % '|' .join (orbit_frames ))
197+ print (" [loc=LAT,LNG | lat=LAT lng=LNG]" )
198+ for line in self .format_key_value_help (orbit_arg_spec ):
199+ print (line )
200+
201+ def cmd_orbit (self , args ):
202+ '''send MAV_CMD_DO_ORBIT to orbit around the clicked location'''
203+ values = {}
204+ if not self .parse_key_value_spec (args , orbit_arg_spec , values ):
205+ self .cmd_orbit_usage ()
206+ return
207+
208+ # work out the orbit centre: an explicit location if given, else the map click
209+ if 'loc' in values :
210+ if 'lat' in values or 'lng' in values :
211+ print ("specify either loc= or lat=/lng=, not both" )
212+ return
213+ parts = values ['loc' ].split (',' )
214+ try :
215+ if len (parts ) not in (2 , 3 ):
216+ raise ValueError
217+ latlon = (float (parts [0 ]), float (parts [1 ]))
218+ if len (parts ) == 3 and 'alt' not in values :
219+ values ['alt' ] = float (parts [2 ])
220+ except ValueError :
221+ print ("loc must be LAT,LNG or LAT,LNG,ALT" )
222+ return
223+ elif 'lat' in values or 'lng' in values :
224+ if 'lat' not in values or 'lng' not in values :
225+ print ("both lat and lng are required" )
226+ return
227+ latlon = (values ['lat' ], values ['lng' ])
228+ else :
229+ latlon = self .mpstate .click_location
230+ if latlon is None :
231+ print ("No map click position available" )
232+ return
233+
234+ if 'frame' in values :
235+ if values ['frame' ] not in orbit_frames :
236+ print ("frame must be one of %s" % '|' .join (orbit_frames ))
237+ return
238+ self .settings .flytoframe = values ['frame' ]
239+
240+ radius = values ['radius' ]
241+ if 'alt' in values :
242+ altitude = values ['alt' ]
243+ else :
244+ altitude = self .mpstate .settings .guidedalt
245+ altitude = self .height_convert_from_units (altitude )
246+ velocity = float ('NaN' ) # vehicle default velocity
247+ if 'velocity' in values and values ['velocity' ].lower () != 'default' :
248+ velocity = float (values ['velocity' ])
249+ orbits = 0 # orbit forever
250+ if 'orbits' in values :
251+ orbits = values ['orbits' ]
252+ yaw_behaviour = float ('NaN' ) # vehicle default yaw behaviour
253+ if 'yaw' in values :
254+ behaviours_lower = {k .lower (): v for (k , v ) in orbit_yaw_behaviours .items ()}
255+ if values ['yaw' ].lower () in behaviours_lower :
256+ yaw_behaviour = behaviours_lower [values ['yaw' ].lower ()]
257+ else :
258+ yaw_behaviour = float (values ['yaw' ])
259+
260+ frame = self .flyto_frame ()
261+
262+ print ("Orbit %s radius %.1fm alt %.1f frame %u" % (str (latlon ), radius , altitude , frame ))
263+
264+ self .master .mav .command_int_send (
265+ self .settings .target_system ,
266+ self .settings .target_component ,
267+ frame ,
268+ mavutil .mavlink .MAV_CMD_DO_ORBIT ,
269+ 0 , # current
270+ 0 , # autocontinue
271+ radius , # p1 - radius, +ve clockwise, -ve counter-clockwise
272+ velocity , # p2 - tangential velocity, NaN is use-default
273+ yaw_behaviour , # p3 - yaw behaviour, NaN is vehicle default
274+ orbits * 2 * math .pi , # p4 - angle to orbit in radians, 0 for forever
275+ int (latlon [0 ]* 1.0e7 ),
276+ int (latlon [1 ]* 1.0e7 ),
277+ altitude
278+ )
279+
143280 def build_pt_ignoremask (self , bits , force_not_accel = False ):
144281 '''creates an ignore bitmask which ignores all bits except the ones passed in'''
145282 ignore_bits = {
0 commit comments