'''
Fly Copter in SITL
AP_FLAKE8_CLEAN
'''
from __future__ import annotations
import copy
import math
import os
import shutil
import tempfile
import time
import numpy
import pathlib
import re
from pymavlink import quaternion
from pymavlink import mavutil
from pymavlink import mavextra
from pymavlink import rotmat
from pysim import util
from pysim import vehicleinfo
import vehicle_test_suite
from vehicle_test_suite import NotAchievedException, AutoTestTimeoutException, PreconditionFailedException
from vehicle_test_suite import Test
from vehicle_test_suite import MAV_POS_TARGET_TYPE_MASK
from vehicle_test_suite import WaitAndMaintainArmed
from vehicle_test_suite import WaitAndMaintainAttitude
from vehicle_test_suite import WaitModeTimeout
from pymavlink.rotmat import Vector3, Matrix3
testdir = os.path.dirname(os.path.realpath(__file__))
SITL_START_LOCATION = mavutil.location(
-35.362938,
149.165085,
584.0805053710938,
270
)
class AutoTestCopter(vehicle_test_suite.TestSuite):
@staticmethod
def get_not_armable_mode_list():
return ["AUTO", "AUTOTUNE", "BRAKE", "CIRCLE", "FLIP", "LAND", "RTL", "SMART_RTL", "AVOID_ADSB", "FOLLOW"]
@staticmethod
def get_not_disarmed_settable_modes_list():
return ["FLIP", "AUTOTUNE"]
@staticmethod
def get_no_position_not_settable_modes_list():
return []
@staticmethod
def get_position_armable_modes_list():
return ["DRIFT", "GUIDED", "LOITER", "POSHOLD", "THROW"]
@staticmethod
def get_normal_armable_modes_list():
return ["ACRO", "ALT_HOLD", "STABILIZE", "GUIDED_NOGPS"]
def log_name(self):
return "ArduCopter"
def test_filepath(self):
return os.path.realpath(__file__)
def default_speedup(self):
return 100
def set_current_test_name(self, name):
self.current_test_name_directory = "ArduCopter_Tests/" + name + "/"
def sitl_start_location(self):
return SITL_START_LOCATION
def mavproxy_options(self):
ret = super(AutoTestCopter, self).mavproxy_options()
if self.frame != 'heli':
ret.append('--quadcopter')
return ret
def sitl_streamrate(self):
return 5
def vehicleinfo_key(self):
return 'ArduCopter'
def default_frame(self):
return "+"
def apply_defaultfile_parameters(self):
pass
def defaults_filepath(self):
return self.model_defaults_filepath(self.frame)
def wait_disarmed_default_wait_time(self):
return 120
def close(self):
super(AutoTestCopter, self).close()
if self.copy_tlog:
shutil.copy(self.logfile, self.buildlog)
def is_copter(self):
return True
def get_stick_arming_channel(self):
return int(self.get_parameter("RCMAP_YAW"))
def get_disarm_delay(self):
return int(self.get_parameter("DISARM_DELAY"))
def set_autodisarm_delay(self, delay):
self.set_parameter("DISARM_DELAY", delay)
def takeoff(self,
alt_min=30,
takeoff_throttle=1700,
require_absolute=True,
mode="STABILIZE",
timeout=120,
max_err=5,
alt_minimum_duration=0,
):
"""Takeoff get to 30m altitude."""
self.progress("TAKEOFF")
self.change_mode(mode)
if not self.armed():
self.wait_ready_to_arm(require_absolute=require_absolute, timeout=timeout)
self.zero_throttle()
self.arm_vehicle()
if mode == 'GUIDED':
self.user_takeoff(alt_min=alt_min, timeout=timeout, max_err=max_err)
else:
self.set_rc(3, takeoff_throttle)
self.wait_altitude(alt_min-1, alt_min+max_err, relative=True, timeout=timeout, minimum_duration=alt_minimum_duration)
self.hover()
self.progress("TAKEOFF COMPLETE")
def land_and_disarm(self, timeout=60):
"""Land the quad."""
self.progress("STARTING LANDING")
self.change_mode("LAND")
self.wait_landed_and_disarmed(timeout=timeout)
def wait_landed_and_disarmed(self, min_alt=6, timeout=60):
"""Wait to be landed and disarmed"""
m = self.assert_receive_message('GLOBAL_POSITION_INT')
alt = m.relative_alt / 1000.0
if alt > min_alt:
self.wait_altitude(min_alt-1, min_alt+5, relative=True, timeout=timeout)
self.wait_disarmed()
def hover(self, hover_throttle=1500):
self.set_rc(3, hover_throttle)
def setAlt(self, desiredAlt=50):
pos = self.mav.location(relative_alt=True)
if pos.alt > desiredAlt:
self.set_rc(3, 1300)
self.wait_altitude((desiredAlt-5), desiredAlt, relative=True)
if pos.alt < (desiredAlt-5):
self.set_rc(3, 1800)
self.wait_altitude((desiredAlt-5), desiredAlt, relative=True)
self.hover()
def takeoffAndMoveAway(self, dAlt=50, dDist=50):
self.progress("Centering sticks")
self.set_rc_from_map({
1: 1500,
2: 1500,
3: 1000,
4: 1500,
})
self.takeoff(alt_min=dAlt, mode='GUIDED')
self.change_mode("ALT_HOLD")
self.progress("Yaw to east")
self.set_rc(4, 1580)
self.wait_heading(90)
self.set_rc(4, 1500)
self.progress("Fly eastbound away from home")
self.set_rc(2, 1800)
self.delay_sim_time(10)
self.set_rc(2, 1500)
self.hover()
self.progress("Copter staging 50 meters east of home at 50 meters altitude In mode Alt Hold")
def ModeLoiter(self, holdtime=10, maxaltchange=5, maxdistchange=5):
"""Hold loiter position."""
self.takeoff(10, mode="LOITER")
self.progress("turn south east")
self.set_rc(4, 1580)
self.wait_heading(170)
self.set_rc(4, 1500)
self.set_rc(2, 1100)
self.wait_distance(50)
self.set_rc(2, 1500)
self.wait_groundspeed(0, 2)
m = self.assert_receive_message('VFR_HUD')
start_altitude = m.alt
start = self.mav.location()
tstart = self.get_sim_time()
self.progress("Holding loiter at %u meters for %u seconds" %
(start_altitude, holdtime))
while self.get_sim_time_cached() < tstart + holdtime:
m = self.assert_receive_message('VFR_HUD')
pos = self.mav.location()
delta = self.get_distance(start, pos)
alt_delta = math.fabs(m.alt - start_altitude)
self.progress("Loiter Dist: %.2fm, alt:%u" % (delta, m.alt))
if alt_delta > maxaltchange:
raise NotAchievedException(
"Loiter alt shifted %u meters (> limit %u)" %
(alt_delta, maxaltchange))
if delta > maxdistchange:
raise NotAchievedException(
"Loiter shifted %u meters (> limit of %u)" %
(delta, maxdistchange))
self.progress("Loiter OK for %u seconds" % holdtime)
self.progress("Climb to 30m")
self.change_alt(30)
self.progress("Descend to 20m")
self.change_alt(20)
self.do_RTL()
def ModeAltHold(self):
'''Test AltHold Mode'''
self.takeoff(10, mode="ALT_HOLD")
self.watch_altitude_maintained(altitude_min=9, altitude_max=11)
self.set_rc_from_map({
1: 1000,
2: 1000,
})
self.watch_altitude_maintained(altitude_min=9, altitude_max=11)
self.set_rc_from_map({
1: 1500,
2: 1500,
})
self.do_RTL()
def fly_to_origin(self, final_alt=10):
origin = self.poll_message("GPS_GLOBAL_ORIGIN")
self.change_mode("GUIDED")
self.guided_move_global_relative_alt(origin.latitude,
origin.longitude,
final_alt)
def change_alt(self, alt_min, climb_throttle=1920, descend_throttle=1080):
"""Change altitude."""
def adjust_altitude(current_alt, target_alt, accuracy):
if math.fabs(current_alt - target_alt) <= accuracy:
self.hover()
elif current_alt < target_alt:
self.set_rc(3, climb_throttle)
else:
self.set_rc(3, descend_throttle)
self.wait_altitude(
(alt_min - 5),
alt_min,
relative=True,
called_function=lambda current_alt, target_alt: adjust_altitude(current_alt, target_alt, 1)
)
self.hover()
def RecordThenPlayMission(self, side=50, timeout=300):
'''Use switches to toggle in mission, then fly it'''
self.takeoff(20, mode="ALT_HOLD")
"""Fly a square, flying N then E ."""
tstart = self.get_sim_time()
self.set_rc_from_map({
1: 1500,
2: 1500,
3: 1500,
4: 1500,
})
self.change_mode('LOITER')
self.progress("turn right towards north")
self.set_rc(4, 1580)
self.wait_heading(10)
self.set_rc(4, 1500)
self.progress("Save WP 1 & 2")
self.save_wp()
self.change_mode('ALT_HOLD')
self.progress("Going north %u meters" % side)
self.set_rc(2, 1300)
self.wait_distance(side)
self.set_rc(2, 1500)
self.progress("Save WP 3")
self.save_wp()
self.progress("Going east %u meters" % side)
self.set_rc(1, 1700)
self.wait_distance(side)
self.set_rc(1, 1500)
self.progress("Save WP 4")
self.save_wp()
self.progress("Going south %u meters" % side)
self.set_rc(2, 1700)
self.wait_distance(side)
self.set_rc(2, 1500)
self.progress("Save WP 5")
self.save_wp()
self.progress("Going west %u meters" % side)
self.set_rc(1, 1300)
self.wait_distance(side)
self.set_rc(1, 1500)
self.progress("Save WP 6")
self.save_wp()
self.set_rc(3, 1500)
self.progress("Descend to 10m in Loiter")
self.change_mode('LOITER')
self.set_rc(3, 1200)
time_left = timeout - (self.get_sim_time() - tstart)
self.progress("timeleft = %u" % time_left)
if time_left < 20:
time_left = 20
self.wait_altitude(-10, 10, timeout=time_left, relative=True)
self.set_rc(3, 1500)
self.save_wp()
self.land_and_disarm()
self.set_rc(3, 1000)
self.change_mode('LOITER')
mavproxy = self.start_mavproxy()
num_wp = self.save_mission_to_file_using_mavproxy(
mavproxy,
os.path.join(testdir, "ch7_mission.txt"))
self.stop_mavproxy(mavproxy)
if not num_wp:
raise NotAchievedException("save_mission_to_file failed")
self.arm_vehicle()
self.progress("test: Fly a mission from 1 to %u" % num_wp)
self.change_mode('AUTO')
self.set_rc(3, 1500)
self.wait_waypoint(0, num_wp-1, timeout=500)
self.progress("test: MISSION COMPLETE: passed!")
self.land_and_disarm()
def WPArcs(self):
'''Test WP Arc functionality'''
_ = self.load_and_start_mission("ArcWPMissionTest.txt")
self.wait_waypoint(0, 15, max_dist=10, timeout=400)
self.progress("Check that vehicle flys an arc between WP 15 and 16")
tstart = self.get_sim_time()
timeout = 200
last_print_time = 0
while True:
now = self.get_sim_time_cached()
delta = now - tstart
pos = self.assert_receive_message('GLOBAL_POSITION_INT')
current_long = pos.lon * 1e-7
desired_long = 149.16522
current_wp = self.mav.waypoint_current()
if delta > timeout:
raise AutoTestTimeoutException(
"Failed to achieve position within %fs" % (timeout,)
)
if now - last_print_time > 1:
self.progress(
"Waiting for current long (%f) > desired long (%f) "
"(%.2fs remaining before timeout)"
% (current_long, desired_long, timeout - delta)
)
last_print_time = now
if current_wp > 19:
raise NotAchievedException(
"mission progressed beyond WP 19 before current longitude > "
"desired long (%f)" % desired_long
)
if (current_long > desired_long):
self.progress(
"Success: Copter traveled far enough east whilst traveling to "
"WP 19 that we are likely flying a WP Arc"
)
break
self.wait_disarmed()
def circle_from_arc(self, p1, p2, theta_deg):
"""
Compute a single circle center and radius given two points on a circle
and the signed central angle they subtend.
p1, p2: (x, y)
theta: signed central angle in radians
- the absolute value is used for geometry
- the sign determines which side of the chord the center is on
... with thanks to ChatGPT
"""
theta = math.radians(theta_deg)
(x1, y1), (x2, y2) = p1, p2
dx = x2 - x1
dy = y2 - y1
d = math.hypot(dx, dy)
a = abs(theta)
R = d / (2 * math.sin(a / 2))
mx = (x1 + x2) / 2
my = (y1 + y2) / 2
h = math.sqrt(R**2 - (d/2)**2)
ux = -dy / d
uy = dx / d
sign = 1 if theta >= 0 else -1
cx = mx + sign * h * ux
cy = my + sign * h * uy
return (cx, cy), R
def WPArcs2(self):
'''more tests for waypoint arcs'''
self.set_parameters({
"AUTO_OPTIONS": 3,
})
def assert_no_movement(mav, m):
m_type = m.get_type()
if m_type != 'VFR_HUD':
return
if abs(m.climb) > 0.2 or abs(m.groundspeed) > 0.2:
raise NotAchievedException(f"Moved when I shouldn't {m=}")
def assert_no_lateral_movement(mav, m):
m_type = m.get_type()
if m_type != 'VFR_HUD':
return
print(f"{m.groundspeed=}")
if abs(m.groundspeed) > 0.2:
raise NotAchievedException(f"Moved when I shouldn't {m=}")
self.start_subtest("Shouldn't move if no takeoff")
self.change_mode('STABILIZE')
self.context_push()
self.install_message_hook_context(assert_no_movement)
self.fly_simple_relhome_mission([
(mavutil.mavlink.MAV_CMD_NAV_ARC_WAYPOINT, 0, 0, 20, {"p1": 90}),
])
self.context_pop()
self.start_subtest("Shouldn't move if no takeoff")
self.change_mode('STABILIZE')
self.context_push()
self.install_message_hook_context(assert_no_movement)
self.fly_simple_relhome_mission([
(mavutil.mavlink.MAV_CMD_NAV_ARC_WAYPOINT, 20, 20, 20, {"p1": 90}),
])
self.context_pop()
self.context_push()
self.start_subtest("Shouldn't move if no takeoff, even if we repeat ourselves")
self.change_mode('STABILIZE')
self.install_message_hook_context(assert_no_movement)
self.fly_simple_relhome_mission([
(mavutil.mavlink.MAV_CMD_NAV_ARC_WAYPOINT, 0, 0, 20, {"p1": 90}),
(mavutil.mavlink.MAV_CMD_NAV_ARC_WAYPOINT, 0, 0, 20, {"p1": 90}),
])
self.context_pop()
self.start_subtest("Shouldn't move if no start waypoint")
self.change_mode('STABILIZE')
self.context_push()
self.install_message_hook_context(assert_no_lateral_movement)
self.fly_simple_relhome_mission([
(mavutil.mavlink.MAV_CMD_NAV_TAKEOFF, 0, 0, 20),
(mavutil.mavlink.MAV_CMD_NAV_ARC_WAYPOINT, 0, 0, 20, {"p1": 90}),
(mavutil.mavlink.MAV_CMD_NAV_RETURN_TO_LAUNCH, 0, 0, 0),
])
self.context_pop()
self.start_subtest("Shouldn't move if endpoint is startpoint")
self.change_mode('STABILIZE')
self.context_push()
self.install_message_hook_context(assert_no_lateral_movement)
self.fly_simple_relhome_mission([
(mavutil.mavlink.MAV_CMD_NAV_TAKEOFF, 0, 0, 20),
(mavutil.mavlink.MAV_CMD_NAV_WAYPOINT, 0, 0, 20),
(mavutil.mavlink.MAV_CMD_NAV_ARC_WAYPOINT, 0, 0, 20, {"p1": 90}),
(mavutil.mavlink.MAV_CMD_NAV_RETURN_TO_LAUNCH, 0, 0, 0),
])
self.context_pop()
self.progress("Creating a log per-flight")
self.change_mode('STABILIZE')
self.set_parameter("LOG_FILE_DSRMROT", 1)
self.arm_vehicle()
self.disarm_vehicle()
self.start_subtest("Should move if no start waypoint")
self.context_push()
self.start_flying_simple_relhome_mission([
(mavutil.mavlink.MAV_CMD_NAV_TAKEOFF, 0, 0, 20),
(mavutil.mavlink.MAV_CMD_NAV_ARC_WAYPOINT, 30, 0, 20, {"p1": 90}),
(mavutil.mavlink.MAV_CMD_NAV_RETURN_TO_LAUNCH, 0, 0, 0),
])
self.wait_groundspeed(1, 10)
self.wait_distance_to_home(10, 20)
self.wait_disarmed()
self.context_pop()
self.start_subtest("Degenerate arc - should be a straight-line segment")
self.change_mode('STABILIZE')
self.start_flying_simple_relhome_mission([
(mavutil.mavlink.MAV_CMD_NAV_TAKEOFF, 0, 0, 20),
(mavutil.mavlink.MAV_CMD_NAV_WAYPOINT, 0, 0, 20),
(mavutil.mavlink.MAV_CMD_NAV_ARC_WAYPOINT, 30, 0, 20, {"p1": 0}),
(mavutil.mavlink.MAV_CMD_NAV_RETURN_TO_LAUNCH, 0, 0, 0),
])
self.wait_groundspeed(1, 10)
self.wait_distance_to_home(10, 20)
self.wait_disarmed()
self.start_subtest("Degenerate arc - no length")
self.change_mode('STABILIZE')
self.start_flying_simple_relhome_mission([
(mavutil.mavlink.MAV_CMD_NAV_TAKEOFF, 0, 0, 20),
(mavutil.mavlink.MAV_CMD_NAV_WAYPOINT, 10, 0, 20),
(mavutil.mavlink.MAV_CMD_NAV_ARC_WAYPOINT, 10, 0, 20, {"p1": 180}),
(mavutil.mavlink.MAV_CMD_NAV_RETURN_TO_LAUNCH, 0, 0, 0),
])
self.wait_groundspeed(1, 10)
self.wait_distance_to_home(5, 20)
self.wait_disarmed()
self.start_subtest("Should arc if we start with a waypoint")
self.change_mode('STABILIZE')
pos, radius = self.circle_from_arc((0, 0), (90, 0), 90)
loc = self.offset_location_ne(self.home_position_as_mav_location(), pos[0], pos[1])
self.start_flying_simple_relhome_mission([
(mavutil.mavlink.MAV_CMD_NAV_TAKEOFF, 0, 0, 20),
(mavutil.mavlink.MAV_CMD_NAV_WAYPOINT, 0, 0, 20),
(mavutil.mavlink.MAV_CMD_NAV_ARC_WAYPOINT, 90, 0, 20, {"p1": 90}),
(mavutil.mavlink.MAV_CMD_NAV_RETURN_TO_LAUNCH, 0, 0, 0),
])
self.wait_altitude(19, 21, relative=True)
self.wait_groundspeed(1, 10)
self.wait_circling_point_with_radius(
loc, radius,
epsilon=1,
min_circle_time=10,
timeout=30,
track_angle=False,
)
self.wait_distance_to_home(80, 100)
self.wait_disarmed()
self.start_subtest("arc - should end up with a mirrored D")
self.change_mode('STABILIZE')
self.context_push()
pos, radius = self.circle_from_arc((0, 0), (90, 0), -90)
loc = self.offset_location_ne(self.home_position_as_mav_location(), pos[0], pos[1])
self.start_flying_simple_relhome_mission([
(mavutil.mavlink.MAV_CMD_NAV_TAKEOFF, 0, 0, 20),
(mavutil.mavlink.MAV_CMD_NAV_WAYPOINT, 0, 0, 20),
(mavutil.mavlink.MAV_CMD_NAV_ARC_WAYPOINT, 90, 0, 20, {"p1": 270}),
(mavutil.mavlink.MAV_CMD_NAV_RETURN_TO_LAUNCH, 0, 0, 0),
])
self.wait_altitude(19, 21, relative=True)
self.wait_groundspeed(1, 10)
self.wait_circling_point_with_radius(
loc, radius,
epsilon=1,
min_circle_time=10,
timeout=30,
track_angle=False,
)
self.wait_disarmed(timeout=600)
self.context_pop()
self.start_subtest("arc - should end up with a D")
self.change_mode('STABILIZE')
self.context_push()
pos, radius = self.circle_from_arc((0, 0), (90, 0), 90)
loc = self.offset_location_ne(self.home_position_as_mav_location(), pos[0], pos[1])
self.start_flying_simple_relhome_mission([
(mavutil.mavlink.MAV_CMD_NAV_TAKEOFF, 0, 0, 20),
(mavutil.mavlink.MAV_CMD_NAV_WAYPOINT, 0, 0, 20),
(mavutil.mavlink.MAV_CMD_NAV_ARC_WAYPOINT, 90, 0, 20, {"p1": -270}),
(mavutil.mavlink.MAV_CMD_NAV_RETURN_TO_LAUNCH, 0, 0, 0),
])
self.wait_altitude(19, 21, relative=True)
self.wait_groundspeed(1, 10)
self.wait_circling_point_with_radius(
loc, radius,
epsilon=1,
min_circle_time=10,
timeout=30,
track_angle=False,
)
self.wait_disarmed(timeout=600)
self.context_pop()
self.start_subtest("arc - should end up with helix up")
self.change_mode('STABILIZE')
self.context_push()
pos = (45, 0)
radius = 45
loc = self.offset_location_ne(self.home_position_as_mav_location(), pos[0], pos[1])
self.start_flying_simple_relhome_mission([
(mavutil.mavlink.MAV_CMD_NAV_TAKEOFF, 0, 0, 20),
(mavutil.mavlink.MAV_CMD_NAV_WAYPOINT, 0, 0, 20),
(mavutil.mavlink.MAV_CMD_NAV_ARC_WAYPOINT, 90, 0, 100, {"p1": -2700}),
(mavutil.mavlink.MAV_CMD_NAV_RETURN_TO_LAUNCH, 0, 0, 0),
])
self.wait_altitude(19, 21, relative=True)
self.wait_groundspeed(1, 10)
self.wait_circling_point_with_radius(
loc, radius,
epsilon=1,
min_circle_time=360,
timeout=400,
track_angle=False,
)
self.wait_altitude(99, 101, relative=True, timeout=240)
self.wait_disarmed(timeout=600)
self.context_pop()
self.start_subtest("arc - should end up with helix down")
self.change_mode('STABILIZE')
self.context_push()
self.start_flying_simple_relhome_mission([
(mavutil.mavlink.MAV_CMD_NAV_TAKEOFF, 0, 0, 100),
(mavutil.mavlink.MAV_CMD_NAV_WAYPOINT, 0, 0, 100),
(mavutil.mavlink.MAV_CMD_NAV_ARC_WAYPOINT, 30, 0, 20, {"p1": 2700}),
(mavutil.mavlink.MAV_CMD_NAV_RETURN_TO_LAUNCH, 0, 0, 0),
])
self.wait_altitude(90, 100, relative=True, timeout=120)
self.wait_groundspeed(1, 10)
self.wait_distance_to_home(10, 20)
self.wait_disarmed(timeout=600)
self.context_pop()
self.start_subtest("arc - chained arcs")
self.change_mode('STABILIZE')
self.context_push()
self.start_flying_simple_relhome_mission([
(mavutil.mavlink.MAV_CMD_NAV_TAKEOFF, 0, 0, 20),
(mavutil.mavlink.MAV_CMD_NAV_WAYPOINT, 0, 0, 20),
(mavutil.mavlink.MAV_CMD_NAV_ARC_WAYPOINT, 30, 0, 20, {"p1": 90}),
(mavutil.mavlink.MAV_CMD_NAV_ARC_WAYPOINT, 60, 0, 20, {"p1": 90}),
(mavutil.mavlink.MAV_CMD_NAV_ARC_WAYPOINT, 60, 0, 20, {"p1": -90}),
(mavutil.mavlink.MAV_CMD_NAV_ARC_WAYPOINT, 60, 0, 20, {"p1": -90}),
(mavutil.mavlink.MAV_CMD_NAV_RETURN_TO_LAUNCH, 0, 0, 0),
])
self.wait_altitude(15, 25, relative=True, timeout=120)
self.wait_groundspeed(1, 10)
self.wait_distance_to_home(10, 20)
self.wait_disarmed(timeout=600)
self.context_pop()
self.start_subtest("arc - chained arcs - up/down")
self.change_mode('STABILIZE')
self.context_push()
self.start_flying_simple_relhome_mission([
(mavutil.mavlink.MAV_CMD_NAV_TAKEOFF, 0, 0, 20),
(mavutil.mavlink.MAV_CMD_NAV_WAYPOINT, 0, 0, 20),
(mavutil.mavlink.MAV_CMD_NAV_ARC_WAYPOINT, 30, 0, 20, {"p1": 180}),
(mavutil.mavlink.MAV_CMD_NAV_ARC_WAYPOINT, 60, 0, 40, {"p1": -180}),
(mavutil.mavlink.MAV_CMD_NAV_ARC_WAYPOINT, 90, 0, 40, {"p1": 180}),
(mavutil.mavlink.MAV_CMD_NAV_ARC_WAYPOINT, 120, 0, 20, {"p1": -180}),
(mavutil.mavlink.MAV_CMD_NAV_RETURN_TO_LAUNCH, 0, 0, 0),
])
self.wait_altitude(15, 25, relative=True, timeout=120)
self.wait_groundspeed(1, 10)
self.wait_distance_to_home(10, 20)
self.wait_disarmed(timeout=600)
self.context_pop()
def do_RTL(self, distance_min=None, check_alt=True, distance_max=10, timeout=250, quiet=False):
"""Enter RTL mode and wait for the vehicle to disarm at Home."""
self.change_mode("RTL")
self.hover()
self.wait_rtl_complete(check_alt=check_alt, distance_max=distance_max, timeout=timeout, quiet=True)
def wait_rtl_complete(self, check_alt=True, distance_max=10, timeout=250, quiet=False):
"""Wait for RTL to reach home and disarm"""
self.progress("Waiting RTL to reach Home and disarm")
tstart = self.get_sim_time()
while self.get_sim_time_cached() < tstart + timeout:
m = self.assert_receive_message('GLOBAL_POSITION_INT')
alt = m.relative_alt / 1000.0
home_distance = self.distance_to_home(use_cached_home=True)
home = ""
alt_valid = alt <= 1
distance_valid = home_distance < distance_max
if check_alt:
if alt_valid and distance_valid:
home = "HOME"
else:
if distance_valid:
home = "HOME"
if not quiet:
self.progress("Alt: %.02f HomeDist: %.02f %s" %
(alt, home_distance, home))
if not self.armed():
if home == "":
raise NotAchievedException("Did not get home")
return
raise AutoTestTimeoutException("Did not get home and disarm")
def LoiterToAlt(self):
"""Loiter-To-Alt"""
self.context_push()
self.set_parameters({
"PLND_ENABLED": 1,
"PLND_TYPE": 4,
})
self.set_analog_rangefinder_parameters()
self.reboot_sitl()
num_wp = self.load_mission("copter_loiter_to_alt.txt")
self.change_mode('LOITER')
self.install_terrain_handlers_context()
self.wait_ready_to_arm()
self.arm_vehicle()
self.change_mode('AUTO')
self.set_rc(3, 1550)
self.wait_current_waypoint(2)
self.set_rc(3, 1500)
self.wait_waypoint(0, num_wp-1, timeout=500)
self.wait_disarmed()
self.context_pop()
self.reboot_sitl()
def ThrottleFailsafe(self, side=60, timeout=360):
'''Test Throttle Failsafe'''
self.start_subtest("If you haven't taken off yet RC failure should be instant disarm")
self.change_mode("STABILIZE")
self.set_parameter("DISARM_DELAY", 0)
self.arm_vehicle()
self.set_parameter("SIM_RC_FAIL", 1)
self.disarm_wait(timeout=1)
self.set_parameter("SIM_RC_FAIL", 0)
self.set_parameter("DISARM_DELAY", 10)
self.start_subtest("Radio failsafe disabled test: FS_THR_ENABLE=0 should take no failsafe action")
self.set_parameter('FS_THR_ENABLE', 0)
self.set_parameter('FS_OPTIONS', 0)
self.takeoffAndMoveAway()
self.set_parameter("SIM_RC_FAIL", 1)
self.delay_sim_time(5)
self.wait_mode("ALT_HOLD")
self.set_parameter("SIM_RC_FAIL", 0)
self.delay_sim_time(5)
self.wait_mode("ALT_HOLD")
self.end_subtest("Completed Radio failsafe disabled test")
self.start_subtest("Radio failsafe recovery test")
self.set_parameter('FS_THR_ENABLE', 1)
self.set_parameter("SIM_RC_FAIL", 1)
self.wait_mode("RTL")
self.delay_sim_time(5)
self.set_parameter("SIM_RC_FAIL", 0)
self.delay_sim_time(5)
self.set_rc(5, 1050)
self.wait_mode("CIRCLE")
self.set_rc(5, 1950)
self.wait_mode("STABILIZE")
self.end_subtest("Completed Radio failsafe recovery test")
self.start_subtest("Radio failsafe RTL with no options test: FS_THR_ENABLE=1 & FS_OPTIONS=0")
self.set_parameter("SIM_RC_FAIL", 1)
self.wait_mode("RTL")
self.wait_rtl_complete()
self.set_parameter("SIM_RC_FAIL", 0)
self.end_subtest("Completed Radio failsafe RTL with no options test")
self.start_subtest("Radio failsafe LAND with no options test: FS_THR_ENABLE=3 & FS_OPTIONS=0")
self.set_parameter('FS_THR_ENABLE', 3)
self.takeoffAndMoveAway()
self.set_parameter("SIM_RC_FAIL", 1)
self.wait_mode("LAND")
self.wait_landed_and_disarmed()
self.set_parameter("SIM_RC_FAIL", 0)
self.end_subtest("Completed Radio failsafe LAND with no options test")
self.start_subtest("Radio failsafe SmartRTL->RTL with no options test: FS_THR_ENABLE=4 & FS_OPTIONS=0")
self.set_parameter('FS_THR_ENABLE', 4)
self.takeoffAndMoveAway()
self.set_parameter("SIM_RC_FAIL", 1)
self.wait_mode("SMART_RTL")
self.wait_disarmed()
self.set_parameter("SIM_RC_FAIL", 0)
self.end_subtest("Completed Radio failsafe SmartRTL->RTL with no options test")
self.start_subtest("Radio failsafe SmartRTL->Land with no options test: FS_THR_ENABLE=5 & FS_OPTIONS=0")
self.set_parameter('FS_THR_ENABLE', 5)
self.takeoffAndMoveAway()
self.set_parameter("SIM_RC_FAIL", 1)
self.wait_mode("SMART_RTL")
self.wait_disarmed()
self.set_parameter("SIM_RC_FAIL", 0)
self.end_subtest("Completed Radio failsafe SmartRTL_Land with no options test")
self.start_subtest("Radio failsafe RTL fails into land mode due to bad position.")
self.set_parameter('FS_THR_ENABLE', 1)
self.takeoffAndMoveAway()
self.set_parameter('SIM_GPS1_ENABLE', 0)
self.delay_sim_time(5)
self.set_parameter("SIM_RC_FAIL", 1)
self.wait_mode("LAND")
self.wait_landed_and_disarmed()
self.set_parameter("SIM_RC_FAIL", 0)
self.set_parameter('SIM_GPS1_ENABLE', 1)
self.wait_ekf_happy()
self.end_subtest("Completed Radio failsafe RTL fails into land mode due to bad position.")
self.start_subtest("Radio failsafe SmartRTL->RTL fails into land mode due to bad position.")
self.set_parameter('FS_THR_ENABLE', 4)
self.takeoffAndMoveAway()
self.set_parameter('SIM_GPS1_ENABLE', 0)
self.delay_sim_time(5)
self.set_parameter("SIM_RC_FAIL", 1)
self.wait_mode("LAND")
self.wait_landed_and_disarmed()
self.set_parameter("SIM_RC_FAIL", 0)
self.set_parameter('SIM_GPS1_ENABLE', 1)
self.wait_ekf_happy()
self.end_subtest("Completed Radio failsafe SmartRTL->RTL fails into land mode due to bad position.")
self.start_subtest("Radio failsafe SmartRTL->LAND fails into land mode due to bad position.")
self.set_parameter('FS_THR_ENABLE', 5)
self.takeoffAndMoveAway()
self.set_parameter('SIM_GPS1_ENABLE', 0)
self.delay_sim_time(5)
self.set_parameter("SIM_RC_FAIL", 1)
self.wait_mode("LAND")
self.wait_landed_and_disarmed()
self.set_parameter("SIM_RC_FAIL", 0)
self.set_parameter('SIM_GPS1_ENABLE', 1)
self.wait_ekf_happy()
self.end_subtest("Completed Radio failsafe SmartRTL->LAND fails into land mode due to bad position.")
self.start_subtest("Radio failsafe SmartRTL->RTL fails into RTL mode due to no path.")
self.set_parameter('FS_THR_ENABLE', 4)
self.takeoffAndMoveAway()
self.set_parameter('SIM_GPS1_ENABLE', 0)
self.wait_statustext("SmartRTL deactivated: bad position", timeout=60)
self.set_parameter('SIM_GPS1_ENABLE', 1)
self.wait_ekf_happy()
self.delay_sim_time(5)
self.set_parameter("SIM_RC_FAIL", 1)
self.wait_mode("RTL")
self.wait_rtl_complete()
self.set_parameter("SIM_RC_FAIL", 0)
self.end_subtest("Completed Radio failsafe SmartRTL->RTL fails into RTL mode due to no path.")
self.start_subtest("Radio failsafe SmartRTL->LAND fails into land mode due to no path.")
self.set_parameter('FS_THR_ENABLE', 5)
self.takeoffAndMoveAway()
self.set_parameter('SIM_GPS1_ENABLE', 0)
self.wait_statustext("SmartRTL deactivated: bad position", timeout=60)
self.set_parameter('SIM_GPS1_ENABLE', 1)
self.wait_ekf_happy()
self.delay_sim_time(5)
self.set_parameter("SIM_RC_FAIL", 1)
self.wait_mode("LAND")
self.wait_landed_and_disarmed()
self.set_parameter("SIM_RC_FAIL", 0)
self.end_subtest("Completed Radio failsafe SmartRTL->LAND fails into land mode due to no path.")
self.start_subtest("Radio failsafe with option to continue in guided mode: FS_THR_ENABLE=1 & FS_OPTIONS=4")
self.set_parameter("MAV_GCS_SYSID", self.mav.source_system)
self.setGCSfailsafe(1)
self.set_parameter('FS_THR_ENABLE', 1)
self.set_parameter('FS_OPTIONS', 4)
self.takeoffAndMoveAway()
self.change_mode("GUIDED")
self.set_parameter("SIM_RC_FAIL", 1)
self.delay_sim_time(5)
self.wait_mode("GUIDED")
self.set_parameter("SIM_RC_FAIL", 0)
self.delay_sim_time(5)
self.change_mode("ALT_HOLD")
self.setGCSfailsafe(0)
self.end_subtest("Completed Radio failsafe with option to continue in guided mode")
self.start_subtest("Radio failsafe RTL with option to continue mission: FS_THR_ENABLE=1 & FS_OPTIONS=1")
self.set_parameter('FS_OPTIONS', 1)
self.progress("# Load copter_mission")
num_wp = self.load_mission("copter_mission.txt", strict=False)
if not num_wp:
raise NotAchievedException("load copter_mission failed")
self.change_mode("AUTO")
self.set_parameter("SIM_RC_FAIL", 1)
self.delay_sim_time(5)
self.wait_mode("AUTO")
self.set_parameter("SIM_RC_FAIL", 0)
self.delay_sim_time(5)
self.wait_mode("AUTO")
self.end_subtest("Completed Radio failsafe RTL with option to continue mission")
self.start_subtest("Radio failsafe RTL in mission without "
"option to continue should RTL: FS_THR_ENABLE=1 & FS_OPTIONS=0")
self.set_parameter('FS_OPTIONS', 0)
self.set_parameter("SIM_RC_FAIL", 1)
self.wait_mode("RTL")
self.wait_rtl_complete()
self.clear_mission(mavutil.mavlink.MAV_MISSION_TYPE_MISSION)
self.set_parameter("SIM_RC_FAIL", 0)
self.end_subtest("Completed Radio failsafe RTL in mission without option to continue")
self.progress("All radio failsafe tests complete")
self.set_parameter('FS_THR_ENABLE', 0)
self.reboot_sitl()
def ThrottleFailsafePassthrough(self):
'''check servo passthrough on RC failsafe. Make sure it doesn't glitch to the bad RC input value'''
channel = 7
trim_value = 1450
self.set_parameters({
'RC%u_MIN' % channel: 1000,
'RC%u_MAX' % channel: 2000,
'SERVO%u_MIN' % channel: 1000,
'SERVO%u_MAX' % channel: 2000,
'SERVO%u_TRIM' % channel: trim_value,
'SERVO%u_FUNCTION' % channel: 146,
'FS_THR_ENABLE': 1,
'RC_FS_TIMEOUT': 10,
'SERVO_RC_FS_MSK': 1 << (channel-1),
})
self.reboot_sitl()
self.context_set_message_rate_hz('SERVO_OUTPUT_RAW', 200)
self.set_rc(channel, 1799)
expected_servo_output_value = 1778
self.wait_servo_channel_value(channel, expected_servo_output_value)
def ensure_SERVO_values_never_input(mav, m):
if m.get_type() != "SERVO_OUTPUT_RAW":
return
value = getattr(m, "servo%u_raw" % channel)
if value != expected_servo_output_value and value != trim_value:
raise NotAchievedException("Bad servo value %u received" % value)
self.install_message_hook_context(ensure_SERVO_values_never_input)
self.progress("Forcing receiver into failsafe")
self.set_rc_from_map({
3: 800,
channel: 1300,
})
self.wait_servo_channel_value(channel, trim_value)
self.delay_sim_time(10)
def GCSFailsafe(self, side=60, timeout=360):
'''Test GCS Failsafe'''
self.set_parameter("MAV_GCS_SYSID", self.mav.source_system)
self.context_push()
self.start_subtest("GCS failsafe SmartRTL twice")
self.setGCSfailsafe(3)
self.set_parameter('FS_OPTIONS', 8)
self.takeoffAndMoveAway()
self.set_heartbeat_rate(0)
self.wait_mode("SMART_RTL")
self.wait_disarmed()
self.set_heartbeat_rate(self.speedup)
self.wait_statustext("GCS Failsafe Cleared", timeout=60)
self.takeoffAndMoveAway()
self.set_heartbeat_rate(0)
self.wait_statustext("GCS Failsafe")
def ensure_smartrtl(mav, m):
if m.get_type() != "HEARTBEAT":
return
print("Mode: %s" % self.mav.flightmode)
if self.mav.flightmode != "SMART_RTL":
raise NotAchievedException("Not in SMART_RTL")
self.install_message_hook_context(ensure_smartrtl)
self.set_heartbeat_rate(self.speedup)
self.wait_statustext("GCS Failsafe Cleared", timeout=60)
self.set_heartbeat_rate(0)
self.wait_statustext("GCS Failsafe")
self.wait_disarmed()
self.end_subtest("GCS failsafe SmartRTL twice")
self.set_heartbeat_rate(self.speedup)
self.wait_statustext("GCS Failsafe Cleared", timeout=60)
self.context_pop()
self.start_subtest("GCS failsafe disabled test: FS_GCS_ENABLE=0 should take no failsafe action")
self.setGCSfailsafe(0)
self.takeoffAndMoveAway()
self.set_heartbeat_rate(0)
self.delay_sim_time(5)
self.wait_mode("ALT_HOLD")
self.set_heartbeat_rate(self.speedup)
self.delay_sim_time(5)
self.wait_mode("ALT_HOLD")
self.end_subtest("Completed GCS failsafe disabled test")
self.start_subtest("GCS failsafe recovery test: FS_GCS_ENABLE=1 & FS_OPTIONS=0")
self.setGCSfailsafe(1)
self.set_parameter('FS_OPTIONS', 0)
self.set_heartbeat_rate(0)
self.wait_mode("RTL")
self.set_heartbeat_rate(self.speedup)
self.wait_statustext("GCS Failsafe Cleared", timeout=60)
self.change_mode("LOITER")
self.end_subtest("Completed GCS failsafe recovery test")
self.start_subtest("GCS failsafe recovery test: FS_GCS_ENABLE=1 & FS_OPTIONS=0 & FS_GCS_TIMEOUT=10")
self.setGCSfailsafe(1)
self.set_parameter('FS_OPTIONS', 0)
old_gcs_timeout = self.get_parameter("FS_GCS_TIMEOUT")
new_gcs_timeout = old_gcs_timeout * 2
self.set_parameter("FS_GCS_TIMEOUT", new_gcs_timeout)
self.set_heartbeat_rate(0)
self.delay_sim_time(old_gcs_timeout + (new_gcs_timeout - old_gcs_timeout) / 2)
self.assert_mode("LOITER")
self.wait_mode("RTL")
self.set_heartbeat_rate(self.speedup)
self.wait_statustext("GCS Failsafe Cleared", timeout=60)
self.change_mode("LOITER")
self.set_parameter('FS_GCS_TIMEOUT', old_gcs_timeout)
self.end_subtest("Completed GCS failsafe recovery test")
self.start_subtest("GCS failsafe RTL with no options test: FS_GCS_ENABLE=1 & FS_OPTIONS=0")
self.setGCSfailsafe(1)
self.set_parameter('FS_OPTIONS', 0)
self.set_heartbeat_rate(0)
self.wait_mode("RTL")
self.wait_rtl_complete()
self.set_heartbeat_rate(self.speedup)
self.wait_statustext("GCS Failsafe Cleared", timeout=60)
self.end_subtest("Completed GCS failsafe RTL with no options test")
self.start_subtest("GCS failsafe LAND with no options test: FS_GCS_ENABLE=5 & FS_OPTIONS=0")
self.setGCSfailsafe(5)
self.takeoffAndMoveAway()
self.set_heartbeat_rate(0)
self.wait_mode("LAND")
self.wait_landed_and_disarmed()
self.set_heartbeat_rate(self.speedup)
self.wait_statustext("GCS Failsafe Cleared", timeout=60)
self.end_subtest("Completed GCS failsafe land with no options test")
self.start_subtest("GCS failsafe SmartRTL->RTL with no options test: FS_GCS_ENABLE=3 & FS_OPTIONS=0")
self.setGCSfailsafe(3)
self.takeoffAndMoveAway()
self.set_heartbeat_rate(0)
self.wait_mode("SMART_RTL")
self.wait_disarmed()
self.set_heartbeat_rate(self.speedup)
self.wait_statustext("GCS Failsafe Cleared", timeout=60)
self.end_subtest("Completed GCS failsafe SmartRTL->RTL with no options test")
self.start_subtest("GCS failsafe SmartRTL->Land with no options test: FS_GCS_ENABLE=4 & FS_OPTIONS=0")
self.setGCSfailsafe(4)
self.takeoffAndMoveAway()
self.set_heartbeat_rate(0)
self.wait_mode("SMART_RTL")
self.wait_disarmed()
self.set_heartbeat_rate(self.speedup)
self.wait_statustext("GCS Failsafe Cleared", timeout=60)
self.end_subtest("Completed GCS failsafe SmartRTL->Land with no options test")
self.start_subtest("GCS failsafe invalid value with no options test: FS_GCS_ENABLE=99 & FS_OPTIONS=0")
self.setGCSfailsafe(99)
self.takeoffAndMoveAway()
self.set_heartbeat_rate(0)
self.wait_mode("RTL")
self.wait_rtl_complete()
self.set_heartbeat_rate(self.speedup)
self.wait_statustext("GCS Failsafe Cleared", timeout=60)
self.end_subtest("Completed GCS failsafe invalid value with no options test")
self.start_subtest("GCS failsafe with option bit tests: FS_GCS_ENABLE=1 & FS_OPTIONS=64/2/16")
num_wp = self.load_mission("copter_mission.txt", strict=False)
if not num_wp:
raise NotAchievedException("load copter_mission failed")
self.setGCSfailsafe(1)
self.set_parameter('FS_OPTIONS', 16)
self.takeoffAndMoveAway()
self.progress("Testing continue in pilot controlled modes")
self.set_heartbeat_rate(0)
self.wait_statustext("GCS Failsafe - Continuing Pilot Control", timeout=60)
self.delay_sim_time(5)
self.wait_mode("ALT_HOLD")
self.set_heartbeat_rate(self.speedup)
self.wait_statustext("GCS Failsafe Cleared", timeout=60)
self.progress("Testing continue in auto mission")
self.set_parameter('FS_OPTIONS', 2)
self.change_mode("AUTO")
self.delay_sim_time(5)
self.set_heartbeat_rate(0)
self.wait_statustext("GCS Failsafe - Continuing Auto Mode", timeout=60)
self.delay_sim_time(5)
self.wait_mode("AUTO")
self.set_heartbeat_rate(self.speedup)
self.wait_statustext("GCS Failsafe Cleared", timeout=60)
self.progress("Testing continue landing in land mode")
self.set_parameter('FS_OPTIONS', 8)
self.change_mode("LAND")
self.delay_sim_time(5)
self.set_heartbeat_rate(0)
self.wait_statustext("GCS Failsafe - Continuing Landing", timeout=60)
self.delay_sim_time(5)
self.wait_mode("LAND")
self.wait_landed_and_disarmed()
self.set_heartbeat_rate(self.speedup)
self.wait_statustext("GCS Failsafe Cleared", timeout=60)
self.end_subtest("Completed GCS failsafe with option bits")
self.setGCSfailsafe(0)
self.set_parameter('FS_OPTIONS', 0)
self.progress("All GCS failsafe tests complete")
def CustomController(self, timeout=300):
'''Test Custom Controller'''
self.progress("Configure custom controller parameters")
self.set_parameters({
'CC_TYPE': 2,
'CC_AXIS_MASK': 7,
'RC6_OPTION': 109,
})
self.set_rc(6, 1000)
self.reboot_sitl()
if self.get_parameter("CC_TYPE") != 2 :
raise NotAchievedException("Custom controller is not switched to PID backend.")
self.get_parameter("CC2_RAT_YAW_P")
self.takeoff(10, mode="LOITER", takeoff_throttle=2000)
self.change_mode("CIRCLE")
self.context_push()
self.context_collect('STATUSTEXT')
self.set_rc(6, 2000)
self.wait_statustext("Custom controller is ON", check_context=True)
if self.wait_altitude(7, 13, relative=True, minimum_duration=20) :
raise NotAchievedException("Custom controller is not stable.")
self.set_rc(6, 1000)
self.wait_statustext("Custom controller is OFF", check_context=True)
self.context_pop()
self.do_RTL()
self.progress("Custom controller test complete")
def BatteryFailsafe(self, timeout=300):
'''Fly Battery Failsafe'''
self.progress("Configure battery failsafe parameters")
self.set_parameters({
'SIM_SPEEDUP': 4,
'BATT_LOW_VOLT': 11.5,
'BATT_CRT_VOLT': 10.1,
'BATT_FS_LOW_ACT': 0,
'BATT_FS_CRT_ACT': 0,
'FS_OPTIONS': 0,
'SIM_BATT_VOLTAGE': 12.5,
})
self.start_subtest("Batt failsafe disabled test")
self.takeoffAndMoveAway()
m = self.assert_receive_message('BATTERY_STATUS')
if m.charge_state != mavutil.mavlink.MAV_BATTERY_CHARGE_STATE_OK:
raise NotAchievedException("Expected state ok")
self.set_parameter('SIM_BATT_VOLTAGE', 11.4)
self.wait_statustext("Battery 1 is low", timeout=60)
m = self.assert_receive_message('BATTERY_STATUS')
if m.charge_state != mavutil.mavlink.MAV_BATTERY_CHARGE_STATE_LOW:
raise NotAchievedException("Expected state low")
self.delay_sim_time(5)
self.wait_mode("ALT_HOLD")
self.set_parameter('SIM_BATT_VOLTAGE', 10.0)
self.wait_statustext("Battery 1 is critical", timeout=60)
m = self.assert_receive_message('BATTERY_STATUS')
if m.charge_state != mavutil.mavlink.MAV_BATTERY_CHARGE_STATE_CRITICAL:
raise NotAchievedException("Expected state critical")
self.delay_sim_time(5)
self.wait_mode("ALT_HOLD")
self.change_mode("RTL")
self.wait_rtl_complete()
self.set_parameter('SIM_BATT_VOLTAGE', 12.5)
self.reboot_sitl()
self.end_subtest("Completed Batt failsafe disabled test")
self.start_subtest("Two stage battery failsafe test with RTL and Land")
self.takeoffAndMoveAway()
self.delay_sim_time(3)
self.set_parameters({
'BATT_FS_LOW_ACT': 2,
'BATT_FS_CRT_ACT': 1,
'SIM_BATT_VOLTAGE': 11.4,
})
self.wait_statustext("Battery 1 is low", timeout=60)
self.delay_sim_time(5)
self.wait_mode("RTL")
self.delay_sim_time(10)
self.set_parameter('SIM_BATT_VOLTAGE', 10.0)
self.wait_statustext("Battery 1 is critical", timeout=60)
self.delay_sim_time(5)
self.wait_mode("LAND")
self.wait_landed_and_disarmed()
self.set_parameter('SIM_BATT_VOLTAGE', 12.5)
self.reboot_sitl()
self.end_subtest("Completed two stage battery failsafe test with RTL and Land")
self.start_subtest("Two stage battery failsafe test with SmartRTL")
self.takeoffAndMoveAway()
self.set_parameter('BATT_FS_LOW_ACT', 3)
self.set_parameter('BATT_FS_CRT_ACT', 4)
self.delay_sim_time(10)
self.set_parameter('SIM_BATT_VOLTAGE', 11.4)
self.wait_statustext("Battery 1 is low", timeout=60)
self.delay_sim_time(5)
self.wait_mode("SMART_RTL")
self.change_mode("LOITER")
self.delay_sim_time(10)
self.set_parameter('SIM_BATT_VOLTAGE', 10.0)
self.wait_statustext("Battery 1 is critical", timeout=60)
self.delay_sim_time(5)
self.wait_mode("SMART_RTL")
self.wait_disarmed()
self.set_parameter('SIM_BATT_VOLTAGE', 12.5)
self.reboot_sitl()
self.end_subtest("Completed two stage battery failsafe test with SmartRTL")
self.start_subtest("Battery failsafe with FS_OPTIONS set to continue landing")
self.takeoffAndMoveAway()
self.set_parameter('FS_OPTIONS', 8)
self.change_mode("LAND")
self.delay_sim_time(5)
self.set_parameter('SIM_BATT_VOLTAGE', 11.4)
self.wait_statustext("Battery 1 is low", timeout=60)
self.delay_sim_time(5)
self.wait_mode("LAND")
self.wait_landed_and_disarmed()
self.set_parameter('SIM_BATT_VOLTAGE', 12.5)
self.reboot_sitl()
self.end_subtest("Completed battery failsafe with FS_OPTIONS set to continue landing")
self.start_subtest("Battery failsafe critical landing")
self.takeoffAndMoveAway(100, 50)
self.set_parameters({
'FS_OPTIONS': 0,
'BATT_FS_LOW_ACT': 1,
'BATT_FS_CRT_ACT': 1,
'FS_THR_ENABLE': 1,
})
self.delay_sim_time(5)
self.set_parameter('SIM_BATT_VOLTAGE', 10.0)
self.wait_statustext("Battery 1 is critical", timeout=60)
self.wait_mode("LAND")
self.delay_sim_time(10)
self.set_parameter("SIM_RC_FAIL", 1)
self.delay_sim_time(10)
self.wait_mode("LAND")
self.wait_landed_and_disarmed()
self.set_parameter('SIM_BATT_VOLTAGE', 12.5)
self.set_parameter("SIM_RC_FAIL", 0)
self.reboot_sitl()
self.end_subtest("Completed battery failsafe critical landing")
self.start_subtest("Battery failsafe brake/land")
self.context_push()
self.takeoffAndMoveAway()
self.set_parameter('BATT_FS_LOW_ACT', 7)
self.delay_sim_time(10)
self.change_mode('LOITER')
self.set_rc(1, 2000)
self.wait_groundspeed(8, 10)
self.set_parameter('SIM_BATT_VOLTAGE', 11.4)
self.wait_statustext("Battery 1 is low", timeout=60)
self.wait_mode('BRAKE')
self.set_rc(1, 1500)
self.disarm_vehicle(force=True)
self.context_pop()
self.reboot_sitl()
self.end_subtest("Completed brake/land failsafe test")
self.start_subtest("Battery failsafe brake/land - land")
self.context_push()
self.takeoffAndMoveAway()
self.set_parameter('BATT_FS_LOW_ACT', 7)
self.set_parameter('SIM_GPS1_ENABLE', 0)
self.delay_sim_time(10)
self.set_parameter('SIM_BATT_VOLTAGE', 11.4)
self.wait_statustext("Battery 1 is low", timeout=60)
self.wait_mode('LAND')
self.disarm_vehicle(force=True)
self.reboot_sitl()
self.context_pop()
self.end_subtest("Completed brake/land failsafe test")
self.start_subtest("Battery failsafe terminate")
self.takeoffAndMoveAway()
self.set_parameter('BATT_FS_LOW_ACT', 5)
self.delay_sim_time(10)
self.set_parameter('SIM_BATT_VOLTAGE', 11.4)
self.wait_statustext("Battery 1 is low", timeout=60)
self.wait_disarmed()
self.end_subtest("Completed terminate failsafe test")
self.progress("All Battery failsafe tests complete")
def BatteryMissing(self):
''' Test battery health pre-arm and missing failsafe'''
self.context_push()
self.wait_ready_to_arm()
self.set_parameters({
'BATT_VOLT_PIN': -1,
})
self.drain_mav()
self.assert_prearm_failure("Battery 1 unhealthy", other_prearm_failures_fatal=False)
self.context_pop()
self.context_push()
self.wait_ready_to_arm()
self.takeoff(10, mode="LOITER")
self.set_parameters({
'BATT_VOLT_PIN': -1,
})
self.wait_statustext("Battery 1 is missing")
self.land_and_disarm()
self.context_pop()
self.reboot_sitl()
def VibrationFailsafe(self):
'''Test Vibration Failsafe'''
self.context_push()
self.takeoff(20, mode="LOITER")
self.set_parameters({
'SIM_ACC1_BIAS_Z': 2,
'SIM_ACC2_BIAS_Z': 2,
'SIM_ACC3_BIAS_Z': 2,
})
self.wait_statustext("Vibration compensation ON", timeout=30)
self.change_mode("LAND")
self.wait_altitude(-5, 2, timeout=50, relative=True)
self.disarm_vehicle(force=True)
self.context_pop()
self.reboot_sitl()
def TakeoffCheck(self):
'''Test takeoff check'''
self.set_parameters({
"AHRS_EKF_TYPE": 10,
'SIM_ESC_TELEM': 1,
'SIM_ESC_ARM_RPM': 500,
'TKOFF_RPM_MIN': 1000,
})
self.test_takeoff_check_mode("STABILIZE")
self.test_takeoff_check_mode("ACRO")
self.test_takeoff_check_mode("LOITER")
self.test_takeoff_check_mode("ALT_HOLD")
self.test_takeoff_check_mode("GUIDED", True)
self.test_takeoff_check_mode("POSHOLD")
self.set_parameters({
"AHRS_EKF_TYPE": 10,
'SIM_ESC_TELEM': 1,
'TKOFF_RPM_MIN': 1,
'TKOFF_RPM_MAX': 3,
})
self.test_takeoff_check_mode("STABILIZE")
self.test_takeoff_check_mode("ACRO")
self.test_takeoff_check_mode("LOITER")
self.test_takeoff_check_mode("ALT_HOLD")
self.test_takeoff_check_mode("GUIDED", True)
self.test_takeoff_check_mode("POSHOLD")
def assert_dataflash_message_field_level_at(self,
mtype,
field,
level,
maintain=1,
tolerance=0.05,
timeout=30,
condition=None,
dfreader_start_timestamp=None,
verbose=False):
'''wait for EKF's accel bias to reach a level and maintain it'''
if verbose:
self.progress("current onboard log filepath: %s" % self.current_onboard_log_filepath())
dfreader = self.dfreader_for_current_onboard_log()
achieve_start = None
current_value = None
while True:
m = dfreader.recv_match(type=mtype, condition=condition)
if m is None:
raise NotAchievedException("%s.%s did not maintain %f" %
(mtype, field, level))
if dfreader_start_timestamp is not None:
if m.TimeUS < dfreader_start_timestamp:
continue
if verbose:
print("m=%s" % str(m))
current_value = getattr(m, field)
if abs(current_value - level) > tolerance:
if achieve_start is not None:
self.progress("Achieve stop at %u" % m.TimeUS)
achieve_start = None
continue
dfreader_now = m.TimeUS
if achieve_start is None:
self.progress("Achieve start at %u (got=%f want=%f)" %
(dfreader_now, current_value, level))
if maintain is None:
return
achieve_start = m.TimeUS
continue
if dfreader_now - achieve_start > maintain*1e6:
return dfreader_now
def EK3AccelBias(self):
'''Test EK3 Accel Bias data'''
self.context_push()
self.start_test("Test zero bias")
self.delay_sim_time(2)
dfreader_tstart = self.assert_dataflash_message_field_level_at(
"XKF2",
"AZ",
0.0,
condition="XKF2.C==1",
maintain=1,
)
self.set_parameters({
'SIM_ACC2_BIAS_Z': 0.7,
})
self.start_subtest("Ensuring second core has bias")
self.delay_sim_time(30)
dfreader_tstart = self.assert_dataflash_message_field_level_at(
"XKF2", "AZ", 0.7,
condition="XKF2.C==1",
)
self.start_subtest("Ensuring earth frame is compensated")
self.assert_dataflash_message_field_level_at(
"RATE", "A", 0,
maintain=1,
tolerance=2,
dfreader_start_timestamp=dfreader_tstart,
)
self.set_parameters({
'SIM_ACC2_BIAS_Z': 0.0,
"EK3_IMU_MASK": 0b10,
})
self.reboot_sitl()
self.delay_sim_time(30)
dfreader_tstart = self.assert_dataflash_message_field_level_at(
"XKF2", "AZ", 0.0,
condition="XKF2.C==0",
)
self.set_parameters({
'SIM_ACC2_BIAS_Z': 0.7,
})
self.start_subtest("Ensuring first core now has bias")
self.delay_sim_time(30)
dfreader_tstart = self.assert_dataflash_message_field_level_at(
"XKF2", "AZ", 0.7,
condition="XKF2.C==0",
)
self.start_subtest("Ensuring earth frame is compensated")
self.assert_dataflash_message_field_level_at(
"RATE", "A", 0,
maintain=1,
tolerance=2,
dfreader_start_timestamp=dfreader_tstart,
verbose=True,
)
self.context_pop()
self.reboot_sitl()
def StabilityPatch(self,
holdtime=30,
maxaltchange=5,
maxdistchange=10):
'''Fly stability patch'''
self.takeoff(10, mode="LOITER")
self.progress("turn south")
self.set_rc(4, 1580)
self.wait_heading(180)
self.set_rc(4, 1500)
self.set_rc(2, 1100)
self.wait_distance(80)
self.set_rc(2, 1500)
self.wait_groundspeed(0, 2)
m = self.assert_receive_message('VFR_HUD')
start_altitude = m.alt
start = self.mav.location()
tstart = self.get_sim_time()
self.progress("Holding loiter at %u meters for %u seconds" %
(start_altitude, holdtime))
self.progress("Cutting motor 1 to 65% efficiency")
self.set_parameters({
"SIM_ENGINE_MUL": 0.65,
"SIM_ENGINE_FAIL": 1 << 0,
})
while self.get_sim_time_cached() < tstart + holdtime:
m = self.assert_receive_message('VFR_HUD')
pos = self.mav.location()
delta = self.get_distance(start, pos)
alt_delta = math.fabs(m.alt - start_altitude)
self.progress("Loiter Dist: %.2fm, alt:%u" % (delta, m.alt))
if alt_delta > maxaltchange:
raise NotAchievedException(
"Loiter alt shifted %u meters (> limit %u)" %
(alt_delta, maxaltchange))
if delta > maxdistchange:
raise NotAchievedException(
("Loiter shifted %u meters (> limit of %u)" %
(delta, maxdistchange)))
self.set_parameter("SIM_ENGINE_MUL", 1.0)
self.progress("Stability patch and Loiter OK for %us" % holdtime)
self.progress("RTL after stab patch")
self.do_RTL()
def debug_arming_issue(self):
while True:
self.send_mavlink_arm_command()
m = self.mav.recv_match(blocking=True, timeout=1)
if m is None:
continue
if m.get_type() in ["STATUSTEXT", "COMMAND_ACK"]:
print("Got: %s" % str(m))
if self.mav.motors_armed():
self.progress("Armed")
return
avoid_behave_slide = 0
def fly_fence_avoid_test_radius_check(self, timeout=180, avoid_behave=avoid_behave_slide):
using_mode = "LOITER"
self.change_mode(using_mode)
fence_radius = 15
fence_margin = 3
self.set_parameters({
"FENCE_ENABLE": 1,
"FENCE_TYPE": 2,
"FENCE_RADIUS": fence_radius,
"FENCE_MARGIN": fence_margin,
"AVOID_ENABLE": 1,
"AVOID_BEHAVE": avoid_behave,
"RC10_OPTION": 40,
})
self.wait_ready_to_arm()
self.set_rc(10, 2000)
home_distance = self.distance_to_home(use_cached_home=True)
if home_distance > 5:
raise PreconditionFailedException("Expected to be within 5m of home")
self.zero_throttle()
self.arm_vehicle()
self.set_rc(3, 1700)
self.wait_altitude(10, 100, relative=True)
self.set_rc(3, 1500)
self.set_rc(2, 1400)
self.wait_distance_to_home(12, 20, timeout=30)
tstart = self.get_sim_time()
push_time = 70
failed_max = False
failed_min = False
while True:
if self.get_sim_time() - tstart > push_time:
self.progress("Push time up")
break
if not self.mode_is(using_mode):
raise NotAchievedException("Changed mode away from %s" % using_mode)
distance = self.distance_to_home(use_cached_home=True)
inner_radius = fence_radius - fence_margin
want_min = inner_radius - 1
want_max = inner_radius + 1
self.progress("Push: distance=%f %f<want<%f" %
(distance, want_min, want_max))
if distance < want_min:
if failed_min is False:
self.progress("Failed min")
failed_min = True
if distance > want_max:
if failed_max is False:
self.progress("Failed max")
failed_max = True
if failed_min and failed_max:
raise NotAchievedException("Failed both min and max checks. Clever")
if failed_min:
raise NotAchievedException("Failed min")
if failed_max:
raise NotAchievedException("Failed max")
self.set_rc(2, 1500)
self.do_RTL()
def HorizontalAvoidFence(self, timeout=180):
'''Test horizontal Avoidance fence'''
self.fly_fence_avoid_test_radius_check(avoid_behave=1, timeout=timeout)
self.fly_fence_avoid_test_radius_check(avoid_behave=0, timeout=timeout)
def HorizontalFence(self, timeout=180):
'''Test horizontal fence'''
self.set_parameters({
"FENCE_ENABLE": 1,
"AVOID_ENABLE": 0,
})
self.change_mode("LOITER")
self.wait_ready_to_arm()
m = self.poll_home_position(quiet=False)
self.start_subtest("ensure we can't arm if outside fence")
self.load_fence("fence-in-middle-of-nowhere.txt")
self.delay_sim_time(5)
self.assert_prearm_failure("Vehicle breaching Polygon fence")
self.progress("Failed to arm outside fence (good!)")
self.clear_fence()
self.delay_sim_time(5)
self.drain_mav()
self.end_subtest("ensure we can't arm if outside fence")
self.start_subtest("ensure we can't arm with bad radius")
self.context_push()
self.set_parameter("FENCE_RADIUS", -1)
self.assert_prearm_failure("Invalid Circle FENCE_RADIUS value")
self.context_pop()
self.progress("Failed to arm with bad radius")
self.drain_mav()
self.end_subtest("ensure we can't arm with bad radius")
self.start_subtest("ensure we can't arm with bad alt")
self.context_push()
self.set_parameter("FENCE_ALT_MAX", -1)
self.assert_prearm_failure("Invalid FENCE_ALT_MAX value")
self.context_pop()
self.progress("Failed to arm with bad altitude")
self.end_subtest("ensure we can't arm with bad radius")
self.start_subtest("Check breach-fence behaviour")
self.set_parameter("FENCE_TYPE", 2)
self.takeoff(10, mode="LOITER")
self.progress("turn east")
self.set_rc(4, 1580)
self.wait_heading(160, timeout=60)
self.set_rc(4, 1500)
fence_radius = self.get_parameter("FENCE_RADIUS")
self.progress("flying forward (east) until we hit fence")
pitching_forward = True
self.set_rc(2, 1100)
self.progress("Waiting for fence breach")
tstart = self.get_sim_time()
while not self.mode_is("RTL"):
if self.get_sim_time_cached() - tstart > 30:
raise NotAchievedException("Did not breach fence")
m = self.assert_receive_message('GLOBAL_POSITION_INT')
alt = m.relative_alt / 1000.0
home_distance = self.distance_to_home(use_cached_home=True)
self.progress("Alt: %.02f HomeDistance: %.02f (fence radius=%f)" %
(alt, home_distance, fence_radius))
self.progress("Waiting until we get home and disarm")
tstart = self.get_sim_time()
while self.get_sim_time_cached() < tstart + timeout:
m = self.assert_receive_message('GLOBAL_POSITION_INT')
alt = m.relative_alt / 1000.0
home_distance = self.distance_to_home(use_cached_home=True)
self.progress("Alt: %.02f HomeDistance: %.02f" %
(alt, home_distance))
if pitching_forward and home_distance < 50:
pitching_forward = False
self.set_rc(2, 1475)
self.set_parameter("FENCE_ENABLE", 0)
if (alt <= 1 and home_distance < 10) or (not self.armed() and home_distance < 10):
self.zero_throttle()
self.change_mode("LAND")
self.wait_landed_and_disarmed()
self.progress("Reached home OK")
self.zero_throttle()
return
home_distance = self.distance_to_home(use_cached_home=True)
raise AutoTestTimeoutException(
"Fence test failed to reach home (%fm distance) - "
"timed out after %u seconds" % (home_distance, timeout,))
def MaxAltFence(self):
'''Test Max Alt Fence'''
self.takeoff(10, mode="LOITER")
"""Hold loiter position."""
self.set_parameters({
"FENCE_ENABLE": 1,
"AVOID_ENABLE": 0,
"FENCE_TYPE": 1,
"FENCE_ENABLE" : 1,
})
self.change_alt(10)
self.progress("turning east")
self.set_rc(4, 1580)
self.wait_heading(160, timeout=60)
self.set_rc(4, 1500)
self.progress("flying east 20m")
self.set_rc(2, 1100)
self.wait_distance(20)
self.progress("flying up")
self.set_rc_from_map({
2: 1500,
3: 1800,
})
self.wait_mode('RTL', timeout=120)
self.wait_rtl_complete()
self.zero_throttle()
def MaxAltFenceAvoid(self):
'''Test Max Alt Fence Avoidance'''
self.takeoff(10, mode="LOITER")
"""Hold loiter position."""
self.set_parameters({
"FENCE_ENABLE": 1,
"FENCE_TYPE": 1,
"FENCE_ACTION": 0,
})
self.set_rc(3, 1920)
try:
self.wait_altitude(140, 150, timeout=90, relative=True)
raise NotAchievedException("Avoid should prevent reaching altitude")
except AutoTestTimeoutException:
pass
except Exception as e:
raise e
max_descent_rate = -self.get_parameter("AVOID_BACKUP_SPD") * 1.1
def get_climb_rate(mav, m):
m_type = m.get_type()
if m_type != 'VFR_HUD':
return
if m.climb < max_descent_rate:
raise NotAchievedException("Descending too fast want %f got %f" % (max_descent_rate, m.climb))
self.context_push()
self.install_message_hook_context(get_climb_rate)
self.set_parameters({
"FENCE_ALT_MAX": 50,
})
self.wait_altitude(40, 50, timeout=90, relative=True)
self.context_pop()
self.set_rc(3, 1500)
self.do_RTL()
def MinAltFence(self):
'''Test Min Alt Fence'''
self.takeoff(30, mode="LOITER", timeout=60)
self.set_parameters({
"AVOID_ENABLE": 0,
"FENCE_ENABLE" : 1,
"FENCE_TYPE": 8,
"FENCE_ALT_MIN": 20,
})
self.change_alt(30)
self.do_fence_enable()
self.progress("turn east")
self.set_rc(4, 1580)
self.wait_heading(160, timeout=60)
self.set_rc(4, 1500)
self.set_rc(2, 1100)
self.wait_distance(20)
self.set_rc_from_map({
2: 1500,
3: 1200,
})
self.wait_mode('RTL', timeout=120)
self.wait_rtl_complete()
self.do_fence_disable()
self.zero_throttle()
def MinAltFenceAvoid(self):
'''Test Min Alt Fence Avoidance'''
self.set_parameters({
"FENCE_ENABLE": 1,
"FENCE_TYPE": 8,
"FENCE_ALT_MIN": 20,
"FENCE_ACTION": 0,
})
self.reboot_sitl()
self.takeoff(30, mode="LOITER")
"""Hold loiter position."""
self.set_rc(3, 1120)
try:
self.wait_altitude(10, 15, timeout=90, relative=True)
raise NotAchievedException("Avoid should prevent reaching altitude")
except AutoTestTimeoutException:
pass
except Exception as e:
raise e
max_ascent_rate = self.get_parameter("AVOID_BACKUP_SPD") * 1.1
def get_climb_rate(mav, m):
m_type = m.get_type()
if m_type != 'VFR_HUD':
return
if m.climb > max_ascent_rate:
raise NotAchievedException("Ascending too fast want %f got %f" % (max_ascent_rate, m.climb))
self.context_push()
self.install_message_hook_context(get_climb_rate)
self.set_parameters({
"FENCE_ALT_MIN": 30,
})
self.wait_altitude(30, 40, timeout=90, relative=True)
self.context_pop()
self.set_rc(3, 1500)
self.do_RTL()
def FenceFloorEnabledLanding(self):
"""Ensures we can initiate and complete an RTL while the fence is
enabled.
"""
fence_bit = mavutil.mavlink.MAV_SYS_STATUS_GEOFENCE
self.progress("Test Landing while fence floor enabled")
self.set_parameters({
"AVOID_ENABLE": 0,
"FENCE_ENABLE" : 1,
"FENCE_TYPE": 15,
"FENCE_ALT_MIN": 20,
"FENCE_ALT_MAX": 30,
})
self.change_mode("GUIDED")
self.wait_ready_to_arm()
self.arm_vehicle()
self.user_takeoff(alt_min=25)
self.assert_fence_enabled()
self.change_mode('LOITER')
self.set_rc(3, 1800)
self.wait_mode('RTL', timeout=120)
self.set_rc(3, 1500)
self.wait_altitude(5, 15, relative=True)
self.change_mode('LOITER')
self.delay_sim_time(15)
self.set_rc(3, 1300)
self.wait_altitude(0, 2, relative=True)
self.zero_throttle()
self.wait_landed_and_disarmed()
self.assert_fence_enabled()
self.assert_mode("LOITER")
self.assert_sensor_state(fence_bit, healthy=True)
self.do_fence_disable()
self.assert_fence_disabled()
def FenceFloorAutoDisableLanding(self):
"""Ensures we can initiate and complete an RTL while the fence is enabled"""
fence_bit = mavutil.mavlink.MAV_SYS_STATUS_GEOFENCE
self.progress("Test Landing while fence floor enabled")
self.set_parameters({
"AVOID_ENABLE": 0,
"FENCE_TYPE": 11,
"FENCE_ALT_MIN": 10,
"FENCE_ALT_MAX": 20,
"FENCE_AUTOENABLE" : 1,
})
self.change_mode("GUIDED")
self.wait_ready_to_arm()
self.arm_vehicle()
self.takeoff(alt_min=15, mode="GUIDED")
self.assert_fence_enabled()
self.change_mode('LOITER')
self.set_rc(3, 1800)
self.wait_mode('RTL', timeout=120)
self.wait_landed_and_disarmed(0)
self.assert_fence_disabled()
self.assert_sensor_state(fence_bit, present=True, enabled=False)
def FenceFloorAutoEnableOnArming(self):
"""Ensures we can auto-enable fences on arming and still takeoff and land"""
fence_bit = mavutil.mavlink.MAV_SYS_STATUS_GEOFENCE
self.set_parameters({
"AVOID_ENABLE": 0,
"FENCE_TYPE": 11,
"FENCE_ALT_MIN": 10,
"FENCE_ALT_MAX": 20,
"FENCE_AUTOENABLE" : 3,
})
self.change_mode("GUIDED")
self.assert_fence_disabled()
self.wait_ready_to_arm()
self.arm_vehicle()
self.takeoff(alt_min=15, mode="GUIDED")
self.assert_fence_enabled()
self.change_mode('LOITER')
self.set_rc(3, 1800)
self.wait_mode('RTL', timeout=120)
self.assert_sensor_state(fence_bit, healthy=False)
self.wait_landed_and_disarmed(0)
self.assert_fence_disabled()
self.assert_sensor_state(fence_bit, present=True, enabled=False)
self.assert_fence_disabled()
def FenceMargin(self, timeout=180):
'''Test warning on crossing fence margin'''
self.set_parameters({
"FENCE_ENABLE": 1,
"FENCE_TYPE": 6,
"FENCE_MARGIN" : 30,
"FENCE_RADIUS" : 150,
"AVOID_ENABLE": 0,
"FENCE_OPTIONS": 4
})
self.change_mode("LOITER")
self.wait_ready_to_arm()
m = self.poll_home_position(quiet=False)
home_loc = self.mav.location()
radius = self.get_parameter("FENCE_RADIUS")
if self.use_map and self.mavproxy is not None:
self.mavproxy.send("map circle %f %f %f green\n" % (home_loc.lat, home_loc.lng, radius))
locs = [
self.offset_location_ne(home_loc, -110, -110),
self.offset_location_ne(home_loc, 110, -110),
self.offset_location_ne(home_loc, 110, 110),
self.offset_location_ne(home_loc, -110, 110),
]
self.upload_fences_from_locations([
(mavutil.mavlink.MAV_CMD_NAV_FENCE_POLYGON_VERTEX_INCLUSION, locs),
])
self.takeoff(10, mode="LOITER")
self.progress("turn east")
self.set_rc(4, 1580)
self.wait_heading(160, timeout=60)
self.set_rc(4, 1500)
fence_radius = self.get_parameter("FENCE_RADIUS")
self.progress("flying forward (east) until we hit fence")
pitching_forward = True
self.set_rc(2, 1100)
self.wait_statustext("Polygon fence in ([0-9]+[.])?[0-9]?m", regex=True)
self.wait_statustext("Circle and Polygon fences in ([0-9]+[.])?[0-9]?m", regex=True)
self.progress("Waiting for fence breach")
tstart = self.get_sim_time()
while not self.mode_is("RTL"):
if self.get_sim_time_cached() - tstart > 30:
raise NotAchievedException("Did not breach fence")
m = self.assert_receive_message('GLOBAL_POSITION_INT')
alt = m.relative_alt / 1000.0
home_distance = self.distance_to_home(use_cached_home=True)
self.progress("Alt: %.02f HomeDistance: %.02f (fence radius=%f)" %
(alt, home_distance, fence_radius))
self.wait_statustext("Circle fence cleared margin breach")
self.progress("Waiting until we get home and disarm")
tstart = self.get_sim_time()
while self.get_sim_time_cached() < tstart + timeout:
m = self.assert_receive_message('GLOBAL_POSITION_INT')
alt = m.relative_alt / 1000.0
home_distance = self.distance_to_home(use_cached_home=True)
self.progress("Alt: %.02f HomeDistance: %.02f" %
(alt, home_distance))
if pitching_forward and home_distance < 50:
pitching_forward = False
self.set_rc(2, 1475)
self.set_parameter("FENCE_ENABLE", 0)
if (alt <= 1 and home_distance < 10) or (not self.armed() and home_distance < 10):
self.zero_throttle()
self.change_mode("LAND")
self.wait_landed_and_disarmed()
self.progress("Reached home OK")
self.zero_throttle()
return
home_distance = self.distance_to_home(use_cached_home=True)
raise AutoTestTimeoutException(
"Fence test failed to reach home (%fm distance) - "
"timed out after %u seconds" % (home_distance, timeout,))
def FenceUpload_MissionItem(self, timeout=180):
'''Test MISSION_ITEM fence upload/download'''
self.set_parameters({
"FENCE_ENABLE": 1,
"FENCE_TYPE": 6,
})
self.poll_home_position(quiet=False)
home_loc = self.mav.location()
fence_loc = [
self.offset_location_ne(home_loc, -110, -110),
self.offset_location_ne(home_loc, 110, -110),
self.offset_location_ne(home_loc, 110, 110),
self.offset_location_ne(home_loc, -110, 110),
]
seq = 0
items = []
mission_type = mavutil.mavlink.MAV_MISSION_TYPE_FENCE
count = len(fence_loc)
for loc in fence_loc:
item = self.mav.mav.mission_item_encode(
1,
1,
seq,
mavutil.mavlink.MAV_FRAME_GLOBAL,
mavutil.mavlink.MAV_CMD_NAV_FENCE_POLYGON_VERTEX_INCLUSION,
0, 0,
count, 0, 0, 0,
loc.lat, loc.lng, 33.0,
mission_type
)
items.append(item)
seq += 1
self.upload_using_mission_protocol(mission_type, items)
downloaded_items = self.download_using_mission_protocol(mission_type)
if len(downloaded_items) != len(items):
raise NotAchievedException(f"Mismatch in number of items: sent={len(items)} received={len(downloaded_items)}")
for i, (sent, received) in enumerate(zip(items, downloaded_items)):
mismatches = []
sent_lat = sent.x
sent_lng = sent.y
recv_lat = received.x / 1e7 if isinstance(received.x, int) else received.x
recv_lng = received.y / 1e7 if isinstance(received.y, int) else received.y
if sent.command != received.command:
mismatches.append(f"command: {sent.command} != {received.command}")
if not math.isclose(sent_lat, recv_lat, abs_tol=1e-2):
mismatches.append(f"lat: {sent_lat} != {recv_lat}")
if not math.isclose(sent_lng, recv_lng, abs_tol=1e-2):
mismatches.append(f"lng: {sent_lng} != {recv_lng}")
if not math.isclose(sent.param1, received.param1, abs_tol=1e-3):
mismatches.append(f"param1: {sent.param1} != {received.param1}")
if mismatches:
raise NotAchievedException(f"Mismatch in item {i}: " + "; ".join(mismatches))
print("Fence upload/download verification passed.")
def assert_fence_not_breached(self):
m = self.assert_receive_message('FENCE_STATUS', timeout=2)
if m.breach_status != 0:
raise NotAchievedException("Fence is breached")
def assert_fence_breached(self):
m = self.assert_receive_message('FENCE_STATUS', timeout=2)
if m.breach_status == 0:
raise NotAchievedException("Fence is not breached")
def BackupFence(self):
'''ensure the lateral backup fence functionality works'''
self.set_parameters({
"FENCE_RADIUS": 10,
"FENCE_TYPE": 2,
"FENCE_ENABLE": 1,
"AVOID_ENABLE": 0,
})
self.wait_sensor_state(mavutil.mavlink.MAV_SYS_STATUS_GEOFENCE,
present=True,
enabled=True,
healthy=True,
timeout=30)
self.assert_fence_not_breached()
self.takeoff(10, mode='LOITER')
self.set_rc(2, 1300)
self.wait_mode('RTL', timeout=30)
self.assert_fence_breached()
self.change_mode('LOITER')
self.wait_mode('RTL', timeout=30)
self.assert_fence_breached()
self.set_rc(2, 1500)
self.wait_disarmed()
self.assert_at_home()
def GPSGlitchLoiter(self, timeout=30, max_distance=20):
"""fly_gps_glitch_loiter_test. Fly south east in loiter and test
reaction to gps glitch."""
self.takeoff(10, mode="LOITER")
if self.use_map:
self.show_gps_and_sim_positions(True)
glitch_lat = [0.0002996,
0.0006958,
0.0009431,
0.0009991,
0.0009444,
0.0007716,
0.0006221]
glitch_lon = [0.0000717,
0.0000912,
0.0002761,
0.0002626,
0.0002807,
0.0002049,
0.0001304]
glitch_num = len(glitch_lat)
self.progress("GPS Glitches:")
for i in range(1, glitch_num):
self.progress("glitch %d %.7f %.7f" %
(i, glitch_lat[i], glitch_lon[i]))
self.progress("turn south east")
self.set_rc(4, 1580)
try:
self.wait_heading(150)
self.set_rc(4, 1500)
self.set_rc(2, 1100)
self.wait_distance(60)
self.set_rc(2, 1500)
except Exception as e:
if self.use_map:
self.show_gps_and_sim_positions(False)
raise e
tstart = self.get_sim_time()
tnow = tstart
start_pos = self.sim_location()
glitch_current = 0
self.progress("Apply first glitch")
self.set_parameters({
"SIM_GPS1_GLTCH_X": glitch_lat[glitch_current],
"SIM_GPS1_GLTCH_Y": glitch_lon[glitch_current],
})
while tnow < tstart + timeout:
tnow = self.get_sim_time_cached()
desired_glitch_num = int((tnow - tstart) * 2.2)
if desired_glitch_num > glitch_current and glitch_current != -1:
glitch_current = desired_glitch_num
if glitch_current >= glitch_num:
glitch_current = -1
self.progress("Completed Glitches")
self.set_parameters({
"SIM_GPS1_GLTCH_X": 0,
"SIM_GPS1_GLTCH_Y": 0,
})
else:
self.progress("Applying glitch %u" % glitch_current)
self.set_parameters({
"SIM_GPS1_GLTCH_X": glitch_lat[glitch_current],
"SIM_GPS1_GLTCH_Y": glitch_lon[glitch_current],
})
if glitch_current == -1:
m = self.assert_receive_message(type='GLOBAL_POSITION_INT')
alt = m.alt/1000.0
curr_pos = self.sim_location()
moved_distance = self.get_distance(curr_pos, start_pos)
self.progress("Alt: %.02f Moved: %.0f" %
(alt, moved_distance))
if moved_distance > max_distance:
raise NotAchievedException(
"Moved over %u meters, Failed!" % max_distance)
else:
self.drain_mav()
if glitch_current != -1:
self.set_parameters({
"SIM_GPS1_GLTCH_X": 0,
"SIM_GPS1_GLTCH_Y": 0,
})
if self.use_map:
self.show_gps_and_sim_positions(False)
self.progress("GPS glitch test passed!"
" stayed within %u meters for %u seconds" %
(max_distance, timeout))
self.do_RTL()
self.reboot_sitl()
def GPSGlitchLoiter2(self):
"""test vehicle handles GPS glitch (aka EKF Reset) without twitching"""
self.context_push()
self.takeoff(10, mode="LOITER")
self.wait_attitude(desroll=0, despitch=0, timeout=10, tolerance=1)
self.set_parameter("SIM_GPS1_GLTCH_X", 0.001)
tstart = self.get_sim_time()
while self.get_sim_time_cached() - tstart < 20:
m = self.assert_receive_message('ATTITUDE')
roll_deg = math.degrees(m.roll)
pitch_deg = math.degrees(m.pitch)
self.progress("checking att: roll=%f pitch=%f " % (roll_deg, pitch_deg))
if abs(roll_deg) > 2 or abs(pitch_deg) > 2:
raise NotAchievedException("fly_gps_glitch_loiter_test2 failed, roll or pitch moved during GPS glitch")
self.do_RTL()
self.context_pop()
self.reboot_sitl()
def GPSGlitchAuto(self, timeout=180):
'''fly mission and test reaction to gps glitch'''
glitch_lat = [0.0002996,
0.0006958,
0.0009431,
0.0009991,
0.0009444,
0.0007716,
0.0006221]
glitch_lon = [0.0000717,
0.0000912,
0.0002761,
0.0002626,
0.0002807,
0.0002049,
0.0001304]
glitch_num = len(glitch_lat)
self.progress("GPS Glitches:")
for i in range(1, glitch_num):
self.progress("glitch %d %.7f %.7f" %
(i, glitch_lat[i], glitch_lon[i]))
self.progress("# Load copter_glitch_mission")
num_wp = self.load_mission("copter_glitch_mission.txt", strict=False)
if not num_wp:
raise NotAchievedException("load copter_glitch_mission failed")
if self.use_map:
self.show_gps_and_sim_positions(True)
self.progress("test: Fly a mission from 1 to %u" % num_wp)
self.set_current_waypoint(1)
self.change_mode("STABILIZE")
self.wait_ready_to_arm()
self.zero_throttle()
self.arm_vehicle()
self.change_mode('AUTO')
self.set_rc(3, 1500)
try:
self.wait_distance(100, 5, 90)
except Exception as e:
if self.use_map:
self.show_gps_and_sim_positions(False)
raise e
self.change_mode("LOITER")
self.set_parameters({
"SIM_GPS1_ENABLE": 0,
})
self.delay_sim_time(2)
self.set_parameters({
"SIM_GPS1_ENABLE": 1,
})
self.wait_ekf_flags(mavutil.mavlink.ESTIMATOR_POS_HORIZ_ABS, 0, timeout=1)
self.delay_sim_time(10)
self.wait_ekf_flags(mavutil.mavlink.ESTIMATOR_POS_HORIZ_ABS, 0, timeout=1)
self.change_mode("AUTO")
tstart = self.get_sim_time()
glitch_current = 0
self.progress("Apply first glitch")
self.set_parameters({
"SIM_GPS1_GLTCH_X": glitch_lat[glitch_current],
"SIM_GPS1_GLTCH_Y": glitch_lon[glitch_current],
})
while glitch_current < glitch_num:
tnow = self.get_sim_time()
desired_glitch_num = int((tnow - tstart) * 2.2)
if desired_glitch_num > glitch_current and glitch_current != -1:
glitch_current = desired_glitch_num
if glitch_current < glitch_num:
self.progress("Applying glitch %u" % glitch_current)
self.set_parameters({
"SIM_GPS1_GLTCH_X": glitch_lat[glitch_current],
"SIM_GPS1_GLTCH_Y": glitch_lon[glitch_current],
})
self.progress("Completed Glitches")
self.set_parameters({
"SIM_GPS1_GLTCH_X": 0,
"SIM_GPS1_GLTCH_Y": 0,
})
self.wait_waypoint(0, num_wp-1, timeout=500)
self.wait_distance_to_home(0, 10, timeout=timeout)
if self.use_map:
self.show_gps_and_sim_positions(False)
self.progress("GPS Glitch test Auto completed: passed!")
self.wait_disarmed()
self.reboot_sitl()
def SimpleMode(self, side=50):
'''Fly in SIMPLE mode'''
self.takeoff(10, mode="LOITER")
self.set_parameter("SIMPLE", 63)
self.change_mode('STABILIZE')
self.set_rc(3, 1545)
self.progress("# Flying south %u meters" % side)
self.set_rc(1, 1300)
self.wait_distance(side, 5, 60)
self.set_rc(1, 1500)
self.progress("# Flying west for 8 seconds")
self.set_rc(2, 1300)
tstart = self.get_sim_time()
while self.get_sim_time_cached() < (tstart + 8):
self.assert_receive_message('VFR_HUD')
self.set_rc(2, 1500)
self.progress("# Flying north %u meters" % (side/2.0))
self.set_rc(1, 1700)
self.wait_distance(side/2, 5, 60)
self.set_rc(1, 1500)
self.progress("# Flying east for 8 seconds")
self.set_rc(2, 1700)
tstart = self.get_sim_time()
while self.get_sim_time_cached() < (tstart + 8):
self.assert_receive_message('VFR_HUD')
self.set_rc(2, 1500)
self.hover()
self.do_RTL(timeout=500)
def SuperSimpleCircle(self, timeout=45):
'''Fly a circle in SUPER SIMPLE mode'''
self.takeoff(10, mode="LOITER")
self.progress("# Flying forward 20 meters")
self.set_rc(2, 1300)
self.wait_distance(20, 5, 60)
self.set_rc(2, 1500)
self.set_parameter("SUPER_SIMPLE", 63)
self.change_mode("ALT_HOLD")
self.set_rc(3, 1500)
self.set_rc(4, 1550)
self.progress("# rolling left from pilot's POV for %u seconds"
% timeout)
self.set_rc(1, 1300)
tstart = self.get_sim_time()
while self.get_sim_time_cached() < (tstart + timeout):
self.assert_receive_message('VFR_HUD')
self.set_rc(1, 1500)
self.set_rc(4, 1500)
self.set_parameter("SUPER_SIMPLE", 0)
self.hover()
self.do_RTL()
def ModeCircle(self, holdtime=36):
'''Fly CIRCLE mode'''
self.reboot_sitl()
self.takeoff(10, mode="LOITER")
self.progress("turn west")
self.set_rc(4, 1580)
self.wait_heading(270)
self.set_rc(4, 1500)
self.set_parameter("CIRCLE_RADIUS_M", 30)
self.set_rc(2, 1100)
self.wait_distance(100)
self.set_rc(2, 1500)
self.change_mode('CIRCLE')
m = self.assert_receive_message('VFR_HUD')
start_altitude = m.alt
tstart = self.get_sim_time()
self.progress("Circle at %u meters for %u seconds" %
(start_altitude, holdtime))
while self.get_sim_time_cached() < tstart + holdtime:
m = self.assert_receive_message('VFR_HUD')
self.progress("heading %d" % m.heading)
self.progress("CIRCLE OK for %u seconds" % holdtime)
self.do_RTL()
def CompassMot(self):
'''test code that adjust mag field for motor interference'''
self.run_cmd(
mavutil.mavlink.MAV_CMD_PREFLIGHT_CALIBRATION,
0,
0,
0,
0,
0,
1,
0
)
self.context_collect("STATUSTEXT")
self.wait_statustext("Starting calibration", check_context=True)
self.wait_statustext("Current", check_context=True)
rc3_min = self.get_parameter('RC3_MIN')
rc3_max = self.get_parameter('RC3_MAX')
rc3_dz = self.get_parameter('RC3_DZ')
def set_rc3_for_throttle_pct(thr_pct):
value = int((rc3_min+rc3_dz) + (thr_pct/100.0) * (rc3_max-(rc3_min+rc3_dz)))
self.progress("Setting rc3 to %u" % value)
self.set_rc(3, value)
throttle_in_pct = 0
set_rc3_for_throttle_pct(throttle_in_pct)
self.assert_received_message_field_values("COMPASSMOT_STATUS", {
"interference": 0,
"throttle": throttle_in_pct
}, verbose=True, very_verbose=True)
tstart = self.get_sim_time()
delta = 5
while True:
if self.get_sim_time_cached() - tstart > 60:
raise NotAchievedException("did not run through entire range")
throttle_in_pct += delta
self.progress("Using throttle %f%%" % throttle_in_pct)
set_rc3_for_throttle_pct(throttle_in_pct)
self.wait_message_field_values("COMPASSMOT_STATUS", {
"throttle": throttle_in_pct * 10.0,
}, verbose=True, very_verbose=True, epsilon=1)
if throttle_in_pct == 0:
break
if throttle_in_pct == 100:
delta = -delta
m = self.wait_message_field_values("COMPASSMOT_STATUS", {
"throttle": 0,
}, verbose=True)
for axis in "X", "Y", "Z":
fieldname = "Compensation" + axis
if getattr(m, fieldname) <= 0:
raise NotAchievedException("Expected non-zero %s" % fieldname)
self.mav.mav.command_ack_send(0, 1)
self.wait_statustext("Calibration successful")
def MagFail(self):
'''test failover of compass in EKF'''
self.set_parameters({
"EK2_ENABLE": 1,
"EK3_ENABLE": 1,
})
self.takeoff(10, mode="LOITER")
self.change_mode('CIRCLE')
self.delay_sim_time(20)
self.context_collect("STATUSTEXT")
self.progress("Failing first compass")
self.set_parameter("SIM_MAG1_FAIL", 1)
self.wait_statustext("EKF2 IMU0 switching to compass 1", check_context=True)
self.wait_statustext("EKF3 IMU0 switching to compass 1", check_context=True)
self.progress("compass switch 1 OK")
self.delay_sim_time(2)
self.context_clear_collection("STATUSTEXT")
self.progress("Failing 2nd compass")
self.set_parameter("SIM_MAG2_FAIL", 1)
self.wait_statustext("EKF2 IMU0 switching to compass 2", check_context=True)
self.wait_statustext("EKF3 IMU0 switching to compass 2", check_context=True)
self.progress("compass switch 2 OK")
self.delay_sim_time(2)
self.context_clear_collection("STATUSTEXT")
self.progress("Failing 3rd compass")
self.set_parameter("SIM_MAG3_FAIL", 1)
self.delay_sim_time(2)
self.set_parameter("SIM_MAG1_FAIL", 0)
self.wait_statustext("EKF2 IMU0 switching to compass 0", check_context=True)
self.wait_statustext("EKF3 IMU0 switching to compass 0", check_context=True)
self.progress("compass switch 0 OK")
self.do_RTL()
def TestEKF3CompassFailover(self):
'''Test if changed compasses goes back to primary in EKF3 on disarm'''
self.set_parameters({
"EK3_ENABLE": 1,
})
self.takeoff(10, mode="LOITER")
self.context_collect("STATUSTEXT")
self.progress("Disabling compass")
self.set_parameter("SIM_MAG1_FAIL", 1)
self.wait_statustext("EKF3 IMU0 switching to compass 1", check_context=True)
self.wait_statustext("EKF3 IMU1 switching to compass 1", check_context=True)
self.progress("Compass switch detected")
self.context_clear_collection("STATUSTEXT")
self.delay_sim_time(2)
self.progress("Re-enabling compass")
self.set_parameter("SIM_MAG1_FAIL", 0)
self.context_clear_collection("STATUSTEXT")
self.progress("Landing and disarming")
self.land_and_disarm()
self.wait_statustext("EKF3 IMU0 switching to compass 0", check_context=True)
self.wait_statustext("EKF3 IMU1 switching to compass 0", check_context=True)
self.progress("Compass restored")
self.progress("Landing and disarming")
self.land_and_disarm()
def ModeFlip(self):
'''Fly Flip Mode'''
self.context_set_message_rate_hz(mavutil.mavlink.MAVLINK_MSG_ID_ATTITUDE, 100)
self.takeoff(20)
self.progress("Flipping in roll")
self.set_rc(1, 1700)
self.send_cmd_do_set_mode('FLIP')
self.wait_attitude(despitch=0, desroll=45, tolerance=30)
self.wait_attitude(despitch=0, desroll=90, tolerance=30)
self.wait_attitude(despitch=0, desroll=-45, tolerance=30)
self.progress("Waiting for level")
self.set_rc(1, 1500)
self.wait_attitude(despitch=0, desroll=0, tolerance=5)
self.progress("Regaining altitude")
self.change_mode('ALT_HOLD')
self.wait_altitude(19, 60, relative=True)
self.progress("Flipping in pitch")
self.set_rc(2, 1700)
self.send_cmd_do_set_mode('FLIP')
self.wait_attitude(despitch=45, desroll=0, tolerance=30)
self.wait_attitude(despitch=90, tolerance=30)
self.wait_attitude(despitch=-45, tolerance=30)
self.progress("Waiting for level")
self.set_rc(2, 1500)
self.wait_attitude(despitch=0, desroll=0, tolerance=5)
self.do_RTL()
def configure_EKFs_to_use_optical_flow_instead_of_GPS(self):
'''configure EKF to use optical flow instead of GPS'''
ahrs_ekf_type = self.get_parameter("AHRS_EKF_TYPE")
if ahrs_ekf_type == 2:
self.set_parameter("EK2_GPS_TYPE", 3)
if ahrs_ekf_type == 3:
self.set_parameters({
"EK3_SRC1_POSXY": 0,
"EK3_SRC1_VELXY": 5,
"EK3_SRC1_VELZ": 0,
})
def OpticalFlowLocation(self):
'''test optical flow doesn't supply location'''
self.context_push()
self.assert_sensor_state(mavutil.mavlink.MAV_SYS_STATUS_SENSOR_OPTICAL_FLOW, False, False, False, verbose=True)
self.start_subtest("Make sure no crash if no rangefinder")
self.set_parameter("SIM_FLOW_ENABLE", 1)
self.set_parameter("FLOW_TYPE", 10)
self.configure_EKFs_to_use_optical_flow_instead_of_GPS()
self.reboot_sitl()
self.wait_sensor_state(mavutil.mavlink.MAV_SYS_STATUS_SENSOR_OPTICAL_FLOW, True, True, True, verbose=True)
self.change_mode('LOITER')
self.delay_sim_time(5)
self.wait_statustext("Need Position Estimate", timeout=300)
self.context_pop()
self.reboot_sitl()
def OpticalFlow(self):
'''test OpticalFlow in flight'''
self.start_subtest("Make sure no crash if no rangefinder")
self.set_parameters({
"SIM_FLOW_ENABLE": 1,
"FLOW_TYPE": 10,
})
self.set_analog_rangefinder_parameters()
self.reboot_sitl()
self.change_mode('LOITER')
class CheckOpticalFlow(vehicle_test_suite.TestSuite.MessageHook):
'''ensures OPTICAL_FLOW data matches other data'''
def __init__(self, suite):
super().__init__(suite)
self.flow_rate_rads = 0
self.rangefinder_distance = 0
self.gps_speed = 0
self.last_debug_time = 0
self.distance_sensor_distance = 0
def progress_prefix(self):
return "COF: "
def process(self, mav, m):
m_type = m.get_type()
if m_type == "OPTICAL_FLOW":
self.flow_rate_rads = math.sqrt(m.flow_comp_m_x**2+m.flow_comp_m_y**2)
elif m_type == "RANGEFINDER":
self.rangefinder_distance = m.distance
elif m_type == "DISTANCE_SENSOR":
self.distance_sensor_distance = m.current_distance * 0.01
elif m_type == "GPS_RAW_INT":
self.gps_speed = m.vel/100.0
of_speed = self.flow_rate_rads * self.rangefinder_distance
if abs(of_speed - self.gps_speed) > 3:
raise NotAchievedException(f"gps={self.gps_speed} vs of={of_speed} mismatch")
of_speed = self.flow_rate_rads * self.distance_sensor_distance
if abs(of_speed - self.gps_speed) > 3:
raise NotAchievedException(f"gps={self.gps_speed} vs of={of_speed} mismatch")
now = self.suite.get_sim_time_cached()
if now - self.last_debug_time > 5:
self.last_debug_time = now
self.progress(f"gps={self.gps_speed} of={of_speed}")
self.context_set_message_rate_hz('RANGEFINDER', self.sitl_streamrate())
check_optical_flow = CheckOpticalFlow(self)
self.install_message_hook_context(check_optical_flow)
self.fly_generic_mission("CMAC-copter-navtest.txt")
def OpticalFlowLimits(self):
'''test EKF navigation limiting'''
self.set_parameters({
"SIM_FLOW_ENABLE": 1,
"FLOW_TYPE": 10,
"SIM_GPS1_ENABLE": 0,
"SIM_TERRAIN": 0,
})
self.configure_EKFs_to_use_optical_flow_instead_of_GPS()
self.set_analog_rangefinder_parameters()
self.reboot_sitl()
self.takeoff(alt_min=5, mode='ALT_HOLD', require_absolute=False, takeoff_throttle=1800)
self.change_mode('LOITER')
self.set_rc(2, 1000)
tstart = self.get_sim_time()
timeout = 60
started_climb = False
while self.get_sim_time_cached() - tstart < timeout:
m = self.assert_receive_message('GLOBAL_POSITION_INT')
spd = math.sqrt(m.vx**2 + m.vy**2) * 0.01
alt = m.relative_alt*0.001
margin = 2.0
max_speed = alt * 1.5 + margin
self.progress("%0.1f: Low Speed: %f (want <= %u) alt=%.1f" %
(self.get_sim_time_cached() - tstart,
spd,
max_speed, alt))
if spd > max_speed:
raise NotAchievedException(("Speed should be limited by"
"EKF optical flow limits"))
if not started_climb and self.get_sim_time_cached() - tstart > 30:
started_climb = True
self.set_rc(3, 1900)
self.progress("Moving higher")
if alt > 35:
raise NotAchievedException("Alt should be limited by EKF optical flow limits")
self.reboot_sitl(force=True)
def OpticalFlowCalibration(self):
'''test optical flow calibration'''
ex = None
self.context_push()
try:
self.set_parameter("SIM_FLOW_ENABLE", 1)
self.set_parameter("FLOW_TYPE", 10)
self.set_analog_rangefinder_parameters()
self.set_parameter("RC9_OPTION", 158)
self.set_parameter("FLOW_FXSCALER", -200)
self.set_parameter("FLOW_FYSCALER", 200)
self.reboot_sitl()
self.set_rc(9, 1000)
self.takeoff(10, mode="LOITER", require_absolute=True, timeout=720)
self.set_rc(9, 2000)
tstart = self.get_sim_time()
timeout = 90
veh_dir_tstart = self.get_sim_time()
veh_dir = 0
while self.get_sim_time_cached() - tstart < timeout:
if self.get_sim_time_cached() - veh_dir_tstart > 2:
veh_dir_tstart = self.get_sim_time()
veh_dir = veh_dir + 1
if veh_dir > 3:
veh_dir = 0
if veh_dir == 0:
self.set_rc(1, 1800)
self.set_rc(2, 1500)
if veh_dir == 1:
self.set_rc(1, 1200)
self.set_rc(2, 1500)
if veh_dir == 2:
self.set_rc(1, 1500)
self.set_rc(2, 1200)
if veh_dir == 3:
self.set_rc(1, 1500)
self.set_rc(2, 1800)
self.set_rc(1, 1500)
self.set_rc(2, 1500)
self.set_rc(9, 1000)
flow_scalar_x = self.get_parameter("FLOW_FXSCALER")
flow_scalar_y = self.get_parameter("FLOW_FYSCALER")
if ((flow_scalar_x > 30) or (flow_scalar_x < -30)):
raise NotAchievedException("FlowCal failed to set FLOW_FXSCALER correctly")
if ((flow_scalar_y > 30) or (flow_scalar_y < -30)):
raise NotAchievedException("FlowCal failed to set FLOW_FYSCALER correctly")
except Exception as e:
self.print_exception_caught(e)
ex = e
self.disarm_vehicle(force=True)
self.context_pop()
self.reboot_sitl()
if ex is not None:
raise ex
def AutoTune(self):
"""Test autotune mode"""
self.customise_SITL_commandline([])
self.set_parameters({
"AUTOTUNE_AGGR": 0.1,
"AUTOTUNE_MIN_D": 0.0004,
})
gain_names = [
"ATC_RAT_RLL_D",
"ATC_RAT_RLL_I",
"ATC_RAT_RLL_P",
]
ogains = self.get_parameters(gain_names)
self.set_parameters(ogains)
self.takeoff(10)
tstart = self.get_sim_time()
self.change_mode('AUTOTUNE')
self.wait_statustext("AutoTune: Success", timeout=5000)
now = self.get_sim_time()
self.progress("AUTOTUNE OK (%u seconds)" % (now - tstart))
self.change_mode('LAND')
self.wait_landed_and_disarmed()
self.progress("checking the original gains have been re-instated")
ngains = self.get_parameters(gain_names)
for g in ngains.keys():
if ogains[g] != ngains[g]:
raise NotAchievedException(f"AUTOTUNE gains not discarded {ogains=} {ngains=}")
def AutoTuneYawD(self):
"""Test autotune mode"""
self.customise_SITL_commandline([])
self.set_parameters({
"AUTOTUNE_AGGR": 0.1,
"AUTOTUNE_AXES": 12,
})
gain_names = [
"ATC_RAT_RLL_D",
"ATC_RAT_RLL_I",
"ATC_RAT_RLL_P",
"ATC_RAT_YAW_D",
]
ogains = self.get_parameters(gain_names)
self.set_parameters(ogains)
self.takeoff(10)
tstart = self.get_sim_time()
self.change_mode('AUTOTUNE')
self.install_message_hook_context(vehicle_test_suite.TestSuite.FailFastStatusText(
self, "determination failed"
))
self.wait_statustext("AutoTune: Success", timeout=5000)
now = self.get_sim_time()
self.progress("AUTOTUNE OK (%u seconds)" % (now - tstart))
self.change_mode('LAND')
self.wait_landed_and_disarmed()
self.progress("checking the original gains have been re-instated")
ngains = self.get_parameters(gain_names)
for g in ngains.keys():
if ogains[g] != ngains[g]:
raise NotAchievedException(f"AUTOTUNE gains not discarded {ogains=} {ngains=}")
def EKF3SRCPerCore(self):
'''Check SP/SH values in XKF4 for GPS (core 0) vs VICON (core 1) with targeted glitches.'''
self.set_parameters({
"EK3_ENABLE": 1,
"AHRS_EKF_TYPE": 3,
"VISO_TYPE": 2,
"SERIAL5_PROTOCOL": 2,
"EK3_SRC2_POSXY": 6,
"EK3_SRC2_VELXY": 6,
"EK3_SRC2_POSZ": 6,
"EK3_SRC2_VELZ": 6,
"EK3_SRC2_YAW": 6,
"EK3_SRC_OPTIONS": 8
})
self.customise_SITL_commandline(["--serial5=sim:vicon"])
self.reboot_sitl()
self.wait_ready_to_arm()
self.takeoff(3)
self.delay_sim_time(5)
self.progress("Injecting VICON glitch")
self.set_parameter("SIM_VICON_GLIT_X", 100)
self.set_parameter("SIM_VICON_GLIT_Y", 100)
self.delay_sim_time(5)
self.set_parameter("SIM_VICON_GLIT_X", 0)
self.set_parameter("SIM_VICON_GLIT_Y", 0)
self.progress("Injecting BARO glitch")
self.set_parameter("SIM_BARO_GLITCH", 10)
self.delay_sim_time(5)
self.set_parameter("SIM_BARO_GLITCH", 0)
self.do_RTL()
self.wait_disarmed(timeout=100)
dfreader = self.dfreader_for_current_onboard_log()
max_SP_core0 = 0
max_SP_core1 = 0
max_SH_core0 = 0
max_SH_core1 = 0
while True:
m = dfreader.recv_match(type="XKF4")
if m is None:
break
if not hasattr(m, "C"):
continue
if m.C == 0:
max_SP_core0 = max(max_SP_core0, m.SP)
max_SH_core0 = max(max_SH_core0, m.SH)
elif m.C == 1:
max_SP_core1 = max(max_SP_core1, m.SP)
max_SH_core1 = max(max_SH_core1, m.SH)
self.progress(f"Core 0 (GPS): SP={max_SP_core0:.2f}, SH={max_SH_core0:.2f}")
self.progress(f"Core 1 (VICON): SP={max_SP_core1:.2f}, SH={max_SH_core1:.2f}")
if max_SP_core0 > 0.1:
raise NotAchievedException("Unexpected high SP in core 0 (GPS) during VICON glitch")
if max_SP_core1 < 0.9:
raise NotAchievedException("VICON glitch did not raise SP in core 1")
if max_SH_core1 > 0.5:
raise NotAchievedException("Unexpected high SH in core 1 (VICON) during BARO glitch")
if max_SH_core0 < 0.9:
raise NotAchievedException("BARO glitch did not raise SH in core 0")
def AutoTuneSwitch(self):
"""Test autotune on a switch with gains being saved"""
self.customise_SITL_commandline([])
self.set_parameters({
"RC8_OPTION": 17,
"AUTOTUNE_AGGR": 0.1,
"AUTOTUNE_MIN_D": 0.0004,
})
self.takeoff(10, mode='LOITER')
def print_gains(name, gains):
self.progress(f"AUTOTUNE {name} gains are P:%f I:%f D:%f" % (
gains["ATC_RAT_RLL_P"],
gains["ATC_RAT_RLL_I"],
gains["ATC_RAT_RLL_D"]
))
def get_roll_gains(name):
ret = self.get_parameters([
"ATC_RAT_RLL_D",
"ATC_RAT_RLL_I",
"ATC_RAT_RLL_P",
], verbose=False)
print_gains(name, ret)
return ret
def gains_same(gains1, gains2):
for c in 'P', 'I', 'D':
p_name = f"ATC_RAT_RLL_{c}"
if abs(gains1[p_name] - gains2[p_name]) > 0.00001:
return False
return True
self.progress("Take a copy of original gains")
original_gains = get_roll_gains("pre-tuning")
scaled_original_gains = copy.copy(original_gains)
scaled_original_gains["ATC_RAT_RLL_I"] *= 0.1
pre_rllt = self.get_parameter("ATC_RAT_RLL_FLTT")
self.set_rc(8, 1850)
self.wait_mode('AUTOTUNE')
tstart = self.get_sim_time()
sim_time_expected = 5000
deadline = tstart + sim_time_expected
while self.get_sim_time_cached() < deadline:
now = self.get_sim_time_cached()
m = self.mav.recv_match(type='STATUSTEXT',
blocking=True,
timeout=1)
if m is None:
continue
self.progress("STATUSTEXT (%u<%u): %s" % (now, deadline, m.text))
if "Determination Failed" in m.text:
break
if "AutoTune: Success" in m.text:
self.progress("AUTOTUNE OK (%u seconds)" % (now - tstart))
post_gains = get_roll_gains("post")
self.progress("Check original gains are used after tuning finished")
if not gains_same(original_gains, post_gains):
raise NotAchievedException("AUTOTUNE original gains not restored")
self.progress("Check original gains are re-instated by switch")
self.set_rc(8, 1100)
self.delay_sim_time(1)
current_gains = get_roll_gains("set-original")
if not gains_same(original_gains, current_gains):
raise NotAchievedException("AUTOTUNE original gains not restored")
self.progress("Use autotuned gains")
self.set_rc(8, 1850)
self.delay_sim_time(1)
tuned_gains = get_roll_gains("tuned")
if gains_same(tuned_gains, original_gains):
raise NotAchievedException("AUTOTUNE tuned gains same as pre gains")
if gains_same(tuned_gains, scaled_original_gains):
raise NotAchievedException("AUTOTUNE tuned gains same as scaled pre gains")
self.progress("land without changing mode")
self.set_rc(3, 1000)
self.wait_altitude(-1, 5, relative=True)
self.wait_disarmed()
self.progress("Check gains are still there after disarm")
disarmed_gains = get_roll_gains("post-disarm")
if not gains_same(tuned_gains, disarmed_gains):
raise NotAchievedException("AUTOTUNE gains not present on disarm")
self.reboot_sitl()
self.progress("Check gains are still there after reboot")
reboot_gains = get_roll_gains("post-reboot")
if not gains_same(tuned_gains, reboot_gains):
raise NotAchievedException("AUTOTUNE gains not present on reboot")
self.progress("Check FLTT is unchanged")
if pre_rllt != self.get_parameter("ATC_RAT_RLL_FLTT"):
raise NotAchievedException("AUTOTUNE FLTT was modified")
return
raise NotAchievedException("AUTOTUNE failed (%u seconds)" %
(self.get_sim_time() - tstart))
def AutoTuneAux(self):
"""Test autotune with gains being tested using the Aux function"""
self.customise_SITL_commandline([])
self.set_parameters({
"RC8_OPTION": 180,
"AUTOTUNE_AGGR": 0.1,
"AUTOTUNE_MIN_D": 0.0004,
})
self.takeoff(10, mode='LOITER')
def print_gains(name, gains):
self.progress(f"AUTOTUNE {name} gains are P:%f I:%f D:%f" % (
gains["ATC_RAT_RLL_P"],
gains["ATC_RAT_RLL_I"],
gains["ATC_RAT_RLL_D"]
))
def get_roll_gains(name):
ret = self.get_parameters([
"ATC_RAT_RLL_D",
"ATC_RAT_RLL_I",
"ATC_RAT_RLL_P",
], verbose=False)
print_gains(name, ret)
return ret
def gains_same(gains1, gains2):
for c in 'P', 'I', 'D':
p_name = f"ATC_RAT_RLL_{c}"
if abs(gains1[p_name] - gains2[p_name]) > 0.00001:
return False
return True
self.progress("Take a copy of original gains")
original_gains = get_roll_gains("pre-tuning")
scaled_original_gains = copy.copy(original_gains)
scaled_original_gains["ATC_RAT_RLL_I"] *= 0.1
pre_rllt = self.get_parameter("ATC_RAT_RLL_FLTT")
self.change_mode('AUTOTUNE')
tstart = self.get_sim_time()
sim_time_expected = 5000
deadline = tstart + sim_time_expected
while self.get_sim_time_cached() < deadline:
now = self.get_sim_time_cached()
m = self.mav.recv_match(type='STATUSTEXT',
blocking=True,
timeout=1)
if m is None:
continue
self.progress("STATUSTEXT (%u<%u): %s" % (now, deadline, m.text))
if "Determination Failed" in m.text:
break
if "AutoTune: Success" in m.text:
self.progress("AUTOTUNE OK (%u seconds)" % (now - tstart))
post_gains = get_roll_gains("post")
self.progress("Check original gains are used after tuning finished")
if not gains_same(original_gains, post_gains):
raise NotAchievedException("AUTOTUNE original gains not restored")
self.change_mode('LOITER')
self.wait_mode('LOITER')
self.progress("Use autotuned gains")
self.set_rc(8, 1850)
self.delay_sim_time(1)
tuned_gains = get_roll_gains("tuned")
if gains_same(tuned_gains, original_gains):
raise NotAchievedException("AUTOTUNE tuned gains same as pre gains")
if gains_same(tuned_gains, scaled_original_gains):
raise NotAchievedException("AUTOTUNE tuned gains same as scaled pre gains")
self.progress("Check original gains are re-instated by switch")
self.set_rc(8, 1100)
self.delay_sim_time(1)
current_gains = get_roll_gains("set-original")
if not gains_same(original_gains, current_gains):
raise NotAchievedException("AUTOTUNE original gains not restored")
self.progress("land using Autotune Gains")
self.set_rc(8, 1850)
self.delay_sim_time(1)
self.set_rc(3, 1000)
self.wait_altitude(-1, 5, relative=True)
self.wait_disarmed()
self.progress("Check gains are still there after disarm")
disarmed_gains = get_roll_gains("post-disarm")
if not gains_same(tuned_gains, disarmed_gains):
raise NotAchievedException("AUTOTUNE gains not present on disarm")
self.reboot_sitl()
self.progress("Check gains are still there after reboot")
reboot_gains = get_roll_gains("post-reboot")
if not gains_same(tuned_gains, reboot_gains):
raise NotAchievedException("AUTOTUNE gains not present on reboot")
self.progress("Check FLTT is unchanged")
if pre_rllt != self.get_parameter("ATC_RAT_RLL_FLTT"):
raise NotAchievedException("AUTOTUNE FLTT was modified")
return
raise NotAchievedException("AUTOTUNE failed (%u seconds)" %
(self.get_sim_time() - tstart))
def EK3_RNG_USE_HGT(self):
'''basic tests for using rangefinder when speed and height below thresholds'''
self.context_push()
self.set_analog_rangefinder_parameters()
self.set_parameters({
'EK3_RNG_USE_HGT': (20 / self.get_parameter('RNGFND1_MAX')) * 100,
})
self.reboot_sitl()
global alt
alt = None
def verify_innov(mav, m):
global alt
if m.get_type() == 'GLOBAL_POSITION_INT':
alt = m.relative_alt * 0.001
return
if m.get_type() != 'EKF_STATUS_REPORT':
return
if alt is None:
return
if alt > 1 and alt < 8:
zero_variance_wanted = False
elif alt > 20:
zero_variance_wanted = True
else:
return
variance = m.terrain_alt_variance
if zero_variance_wanted and variance > 0.00001:
raise NotAchievedException("Wanted zero variance at height %f, got %f" % (alt, variance))
elif not zero_variance_wanted and variance == 0:
raise NotAchievedException("Wanted non-zero variance at alt=%f, got zero" % alt)
self.install_message_hook_context(verify_innov)
self.takeoff(50, mode='GUIDED')
current_alt = self.mav.location().alt
target_position = mavutil.location(
-35.362938,
149.165185,
current_alt,
0
)
self.fly_guided_move_to(target_position, timeout=300)
self.change_mode('LAND')
self.wait_disarmed()
self.context_pop()
self.reboot_sitl()
def TerrainDBPreArm(self):
'''test that pre-arm checks are working correctly for terrain database'''
self.context_push()
self.progress("# Load msission with terrain alt")
num_wp = self.load_mission("terrain_wp.txt", strict=False)
if not num_wp:
raise NotAchievedException("load terrain_wp failed")
self.set_analog_rangefinder_parameters()
self.set_parameters({
"WPNAV_RFND_USE": 1,
"TERRAIN_ENABLE": 1,
})
self.reboot_sitl()
self.wait_ready_to_arm()
self.set_parameter("TERRAIN_ENABLE", 0)
self.wait_ready_to_arm()
self.progress("# Vehicle armed with terrain db disabled")
self.set_parameter("WPNAV_RFND_USE", 0)
self.assert_prearm_failure("terrain disabled")
self.context_pop()
self.reboot_sitl()
def CopterMission(self):
'''fly mission which tests a significant number of commands'''
self.progress("# Load copter_mission")
num_wp = self.load_mission("copter_mission.txt", strict=False)
if not num_wp:
raise NotAchievedException("load copter_mission failed")
self.fly_loaded_mission(num_wp)
self.progress("Auto mission completed: passed!")
def set_origin(self, loc, timeout=60):
'''set the GPS global origin to loc'''
tstart = self.get_sim_time()
while True:
if self.get_sim_time_cached() - tstart > timeout:
raise AutoTestTimeoutException("Did not get non-zero lat")
target_system = 1
self.mav.mav.set_gps_global_origin_send(
target_system,
int(loc.lat * 1e7),
int(loc.lng * 1e7),
int(loc.alt * 1e3)
)
self.delay_sim_time(2)
gpi = self.assert_receive_message('GLOBAL_POSITION_INT')
self.progress("gpi=%s" % str(gpi))
if gpi.lat != 0:
break
def MAV_CMD_DO_SET_GLOBAL_ORIGIN(self):
'''test MAV_CMD_DO_SET_GLOBAL_ORIGIN command'''
try:
mavutil.mavlink.MAV_CMD_DO_SET_GLOBAL_ORIGIN
except AttributeError:
return
self.set_parameters({
"SIM_GPS1_ENABLE": 0,
"GPS1_TYPE": 0,
})
self.context_collect('STATUSTEXT')
self.reboot_sitl()
self.wait_statustext("EKF3 active", check_context=True)
self.run_cmd_int(
mavutil.mavlink.MAV_CMD_DO_SET_GLOBAL_ORIGIN,
p5=0,
p6=0,
p7=0,
want_result=mavutil.mavlink.MAV_RESULT_DENIED
)
self.run_cmd_int(
mavutil.mavlink.MAV_CMD_DO_SET_GLOBAL_ORIGIN,
p5=0,
p6=int(214*1e7),
p7=0,
want_result=mavutil.mavlink.MAV_RESULT_DENIED
)
origin_lat = -23.322332
origin_lng = 123.45678
origin_alt = 23.67
self.run_cmd_int(
mavutil.mavlink.MAV_CMD_DO_SET_GLOBAL_ORIGIN,
p5=int(origin_lat*1e7),
p6=int(origin_lng*1e7),
p7=origin_alt
)
ggo = self.poll_message('GPS_GLOBAL_ORIGIN')
self.assert_message_field_values(ggo, {
"latitude": int(origin_lat*1e7),
"longitude": int(origin_lng*1e7),
"altitude": int(origin_alt*1000),
})
def FarOrigin(self):
'''fly a mission far from the vehicle origin'''
self.set_parameters({
"SIM_GPS1_ENABLE": 0,
})
self.reboot_sitl()
nz = mavutil.location(-43.730171, 169.983118, 1466.3, 270)
self.set_origin(nz)
self.set_parameters({
"SIM_GPS1_ENABLE": 1,
})
self.progress("# Load copter_mission")
num_wp = self.load_mission("copter_mission.txt", strict=False)
if not num_wp:
raise NotAchievedException("load copter_mission failed")
self.fly_loaded_mission(num_wp)
self.progress("Auto mission completed: passed!")
def fly_loaded_mission(self, num_wp):
'''fly mission loaded on vehicle. FIXME: get num_wp from vehicle'''
self.progress("test: Fly a mission from 1 to %u" % num_wp)
self.set_current_waypoint(1)
self.change_mode("LOITER")
self.wait_ready_to_arm()
self.arm_vehicle()
self.change_mode("AUTO")
self.set_rc(3, 1500)
self.wait_waypoint(0, num_wp-1, timeout=500)
self.zero_throttle()
self.wait_disarmed()
self.progress("MOTORS DISARMED OK")
def CANGPSCopterMission(self):
'''fly mission which tests normal operation alongside CAN GPS'''
self.set_parameters({
"CAN_P1_DRIVER": 1,
"GPS1_TYPE": 9,
"GPS2_TYPE": 9,
"SIM_GPS1_ENABLE": 0,
"SIM_GPS2_ENABLE": 0,
"SIM_BARO_COUNT" : 0,
"SIM_MAG1_DEVID" : 0,
"SIM_MAG2_DEVID" : 0,
"SIM_MAG3_DEVID" : 0,
"COMPASS_USE2" : 0,
"COMPASS_USE3" : 0,
"RNGFND1_TYPE" : 24,
"RNGFND1_MAX" : 110.00,
"BATT_MONITOR" : 8,
"BATT_ARM_VOLT" : 12.0,
"SIM_SPEEDUP": 2,
})
self.context_push()
self.set_parameter("ARMING_SKIPCHK", ~(1 << 3))
self.context_collect('STATUSTEXT')
self.reboot_sitl()
gps1_det_text = self.wait_text("GPS 1: specified as DroneCAN.*", regex=True, check_context=True)
gps2_det_text = self.wait_text("GPS 2: specified as DroneCAN.*", regex=True, check_context=True)
gps1_nodeid = int(gps1_det_text.split('-')[1])
gps2_nodeid = int(gps2_det_text.split('-')[1])
if gps1_nodeid is None or gps2_nodeid is None:
raise NotAchievedException("GPS not ordered per the order of Node IDs")
self.context_stop_collecting('STATUSTEXT')
GPS_Order_Tests = [[gps2_nodeid, gps2_nodeid, gps2_nodeid, 0,
"PreArm: Same Node Id {} set for multiple GPS".format(gps2_nodeid)],
[gps1_nodeid, int(gps2_nodeid/2), gps1_nodeid, 0,
"Selected GPS Node {} not set as instance {}".format(int(gps2_nodeid/2), 2)],
[int(gps1_nodeid/2), gps2_nodeid, 0, gps2_nodeid,
"Selected GPS Node {} not set as instance {}".format(int(gps1_nodeid/2), 1)],
[gps1_nodeid, gps2_nodeid, gps1_nodeid, gps2_nodeid, ""],
[gps2_nodeid, gps1_nodeid, gps2_nodeid, gps1_nodeid, ""],
[gps1_nodeid, 0, gps1_nodeid, gps2_nodeid, ""],
[0, gps2_nodeid, gps1_nodeid, gps2_nodeid, ""]]
for case in GPS_Order_Tests:
self.progress("############################### Trying Case: " + str(case))
self.set_parameters({
"GPS1_CAN_OVRIDE": case[0],
"GPS2_CAN_OVRIDE": case[1],
})
self.drain_mav()
self.context_collect('STATUSTEXT')
self.reboot_sitl()
gps1_det_text = None
gps2_det_text = None
try:
gps1_det_text = self.wait_text("GPS 1: specified as DroneCAN.*", regex=True, check_context=True)
except AutoTestTimeoutException:
pass
try:
gps2_det_text = self.wait_text("GPS 2: specified as DroneCAN.*", regex=True, check_context=True)
except AutoTestTimeoutException:
pass
self.context_stop_collecting('STATUSTEXT')
self.change_mode('LOITER')
if case[2] == 0 and case[3] == 0:
if gps1_det_text or gps2_det_text:
raise NotAchievedException("Failed ordering for requested CASE:", case)
if case[2] == 0 or case[3] == 0:
if bool(gps1_det_text is not None) == bool(gps2_det_text is not None):
print(gps1_det_text)
print(gps2_det_text)
raise NotAchievedException("Failed ordering for requested CASE:", case)
if gps1_det_text:
if case[2] != int(gps1_det_text.split('-')[1]):
raise NotAchievedException("Failed ordering for requested CASE:", case)
if gps2_det_text:
if case[3] != int(gps2_det_text.split('-')[1]):
raise NotAchievedException("Failed ordering for requested CASE:", case)
if len(case[4]):
self.context_collect('STATUSTEXT')
self.run_cmd(
mavutil.mavlink.MAV_CMD_COMPONENT_ARM_DISARM,
p1=1,
timeout=10,
want_result=mavutil.mavlink.MAV_RESULT_FAILED,
)
self.wait_statustext(case[4], check_context=True)
self.context_stop_collecting('STATUSTEXT')
self.progress("############################### All GPS Order Cases Tests Passed")
self.progress("############################### Test Healthy Prearm check")
self.set_parameter("ARMING_SKIPCHK", 0)
self.stop_sup_program(instance=0)
self.start_sup_program(instance=0, args="-M")
self.stop_sup_program(instance=1)
self.start_sup_program(instance=1, args="-M")
self.delay_sim_time(2)
self.context_collect('STATUSTEXT')
self.run_cmd(
mavutil.mavlink.MAV_CMD_COMPONENT_ARM_DISARM,
p1=1,
timeout=10,
want_result=mavutil.mavlink.MAV_RESULT_FAILED,
)
self.wait_statustext(".*Node .* unhealthy", check_context=True, regex=True)
self.stop_sup_program(instance=0)
self.start_sup_program(instance=0)
self.stop_sup_program(instance=1)
self.start_sup_program(instance=1)
self.context_stop_collecting('STATUSTEXT')
self.context_pop()
self.set_parameters({
"CAN_D1_UC_ESC_BM" : 0x0f,
"SIM_CAN_SRV_MSK" : 0xFF,
"SIM_SPEEDUP" : 5,
})
self.CopterMission()
def TakeoffAlt(self):
'''Test Takeoff command altitude'''
self.progress("Testing relative alt from the ground")
self.do_takeoff_alt("copter_takeoff.txt", 1, False)
self.progress("Testing relative alt during flight")
self.do_takeoff_alt("copter_takeoff.txt", 10, True)
self.progress("Takeoff mission completed: passed!")
def do_takeoff_alt(self, mission_file, target_alt, during_flight=False):
self.progress("# Load %s" % mission_file)
num_wp = self.load_mission(mission_file, strict=False)
if not num_wp:
raise NotAchievedException("load %s failed" % mission_file)
self.set_current_waypoint(1)
self.change_mode("GUIDED")
self.wait_ready_to_arm()
self.arm_vehicle()
if during_flight:
self.user_takeoff(alt_min=target_alt)
self.change_mode("AUTO")
self.set_rc(3, 1500)
self.wait_waypoint(0, num_wp-1, timeout=500)
self.wait_altitude(target_alt - 1 , target_alt + 1, relative=True)
self.change_mode('LAND')
self.zero_throttle()
self.wait_disarmed()
self.progress("MOTORS DISARMED OK")
def GuidedEKFLaneChange(self):
'''test lane change with GPS diff on startup'''
self.set_parameters({
"EK3_SRC1_POSZ": 3,
"EK3_AFFINITY" : 1,
"GPS2_TYPE" : 1,
"SIM_GPS2_ENABLE" : 1,
"SIM_GPS2_GLTCH_Z" : -30
})
self.reboot_sitl()
self.change_mode("GUIDED")
self.wait_ready_to_arm()
self.delay_sim_time(10, reason='"both EKF lanes to init"')
self.set_parameters({
"SIM_GPS2_GLTCH_Z" : 0
})
self.delay_sim_time(20, reason="EKF to do a position Z reset")
self.arm_vehicle()
self.user_takeoff(alt_min=20)
gps_alt = self.get_altitude(altitude_source='GPS_RAW_INT.alt')
self.progress("Initial guided alt=%.1fm" % gps_alt)
self.context_collect('STATUSTEXT')
self.progress("force a lane change")
self.set_parameters({
"INS_ACCOFFS_X" : 5
})
self.wait_statustext("EKF3 lane switch 1", timeout=10, check_context=True)
self.watch_altitude_maintained(
altitude_min=gps_alt-2,
altitude_max=gps_alt+2,
altitude_source='GPS_RAW_INT.alt',
minimum_duration=10,
)
self.disarm_vehicle(force=True)
self.reboot_sitl()
def test_EKF3_option_disable_lane_switch(self):
'''Test that EK3_OPTION disables lane switching, and EK3_PRIMARY forces switch when re-enabled'''
self.set_parameters({
"EK3_ENABLE": 1,
"EK2_ENABLE": 0,
"AHRS_EKF_TYPE": 3,
"EK3_IMU_MASK": 3,
"EK3_OPTIONS": 2,
"EK3_PRIMARY": 0,
})
self.reboot_sitl()
self.lane_switches = []
def statustext_hook(mav, message):
if message.get_type() != 'STATUSTEXT':
return
if message.text.startswith("EKF primary changed:"):
try:
lane = int(message.text.strip().split(":")[-1])
self.lane_switches.append(lane)
except ValueError:
pass
self.install_message_hook_context(statustext_hook)
self.takeoff(50, mode='ALT_HOLD')
self.delay_sim_time(5)
self.start_subtest("Ensure no lane switch occurs with EK3_OPTIONS = 2")
self.context_collect("STATUSTEXT")
self.set_parameters({
"INS_ACCOFFS_X" : 5
})
self.delay_sim_time(10)
if self.lane_switches:
raise NotAchievedException(f"Unexpected lane switch occurred: {self.lane_switches}")
self.progress("Success: No lane switch occurred with EK3_OPTIONS = 2")
self.context_clear_collection("STATUSTEXT")
self.set_parameters({
"INS_ACCOFFS_X" : 0.01,
})
self.start_subtest("EK3_PRIMARY = 1 (expect switch)")
self.context_collect("STATUSTEXT")
self.set_parameters({
"EK3_PRIMARY": 1,
})
self.wait_statustext(
text="EKF primary changed:1",
timeout=30,
check_context=True
)
self.context_clear_collection("STATUSTEXT")
self.disarm_vehicle(force=True)
def MotorFail(self, ):
"""Test flight with reduced motor efficiency"""
self.MotorFail_test_frame('octa', 8, frame_class=3)
def MotorFail_test_frame(self, model, servo_count, frame_class, fail_servo=0, fail_mul=0.0, holdtime=30):
self.set_parameters({
'FRAME_CLASS': frame_class,
})
self.customise_SITL_commandline([], model=model)
self.takeoff(25, mode="LOITER")
start_hud = self.assert_receive_message('VFR_HUD')
start_attitude = self.assert_receive_message('ATTITUDE')
hover_time = 5
tstart = self.get_sim_time()
int_error_alt = 0
int_error_yaw_rate = 0
int_error_yaw = 0
self.progress("Hovering for %u seconds" % hover_time)
failed = False
while True:
now = self.get_sim_time_cached()
if now - tstart > holdtime + hover_time:
break
servo = self.assert_receive_message('SERVO_OUTPUT_RAW')
hud = self.assert_receive_message('VFR_HUD')
attitude = self.assert_receive_message('ATTITUDE')
if not failed and now - tstart > hover_time:
self.progress("Killing motor %u (%u%%)" %
(fail_servo+1, fail_mul))
self.set_parameters({
"SIM_ENGINE_MUL": fail_mul,
"SIM_ENGINE_FAIL": 1 << fail_servo,
})
failed = True
if failed:
self.progress("Hold Time: %f/%f" % (now-tstart, holdtime))
servo_pwm = [
servo.servo1_raw,
servo.servo2_raw,
servo.servo3_raw,
servo.servo4_raw,
servo.servo5_raw,
servo.servo6_raw,
servo.servo7_raw,
servo.servo8_raw,
]
self.progress("PWM output per motor")
for i, pwm in enumerate(servo_pwm[0:servo_count]):
if pwm > 1900:
state = "oversaturated"
elif pwm < 1200:
state = "undersaturated"
else:
state = "OK"
if failed and i == fail_servo:
state += " (failed)"
self.progress("servo %u [pwm=%u] [%s]" % (i+1, pwm, state))
alt_delta = hud.alt - start_hud.alt
yawrate_delta = attitude.yawspeed - start_attitude.yawspeed
yaw_delta = attitude.yaw - start_attitude.yaw
self.progress("Alt=%fm (delta=%fm)" % (hud.alt, alt_delta))
self.progress("Yaw rate=%f (delta=%f) (rad/s)" %
(attitude.yawspeed, yawrate_delta))
self.progress("Yaw=%f (delta=%f) (deg)" %
(attitude.yaw, yaw_delta))
dt = self.get_sim_time() - now
int_error_alt += abs(alt_delta/dt)
int_error_yaw_rate += abs(yawrate_delta/dt)
int_error_yaw += abs(yaw_delta/dt)
self.progress("## Error Integration ##")
self.progress(" Altitude: %fm" % int_error_alt)
self.progress(" Yaw rate: %f rad/s" % int_error_yaw_rate)
self.progress(" Yaw: %f deg" % int_error_yaw)
self.progress("----")
if int_error_yaw > 5:
raise NotAchievedException("Vehicle is spinning")
if alt_delta < -20:
raise NotAchievedException("Vehicle is descending")
self.progress("Fixing motors")
self.set_parameter("SIM_ENGINE_FAIL", 0)
self.do_RTL()
def hover_for_interval(self, hover_time):
'''hovers for an interval of hover_time seconds. Returns the bookend
times for that interval (in time-since-boot frame), and the
output throttle level at the end of the period.
'''
self.progress("Hovering for %u seconds" % hover_time)
tstart = self.get_sim_time()
self.delay_sim_time(hover_time, reason='data collection')
vfr_hud = self.poll_message('VFR_HUD')
tend = self.get_sim_time()
return tstart, tend, vfr_hud.throttle
def MotorVibration(self):
"""Test flight with motor vibration"""
self.set_parameters({
"AHRS_EKF_TYPE": 10,
"INS_LOG_BAT_MASK": 3,
"INS_LOG_BAT_OPT": 0,
"LOG_BITMASK": 958,
"LOG_DISARMED": 0,
"SIM_VIB_MOT_MAX": 350,
"SIM_GYR1_RND": 20,
"SIM_ACC1_RND": 5,
"SIM_ACC2_RND": 5,
"SIM_INS_THR_MIN": 0.1,
})
self.reboot_sitl()
self.takeoff(15, mode="ALT_HOLD")
tstart, tend, hover_throttle = self.hover_for_interval(15)
self.set_parameter("SIM_VIB_MOT_MAX", 0)
self.do_RTL()
psd = self.mavfft_fttd(1, 0, tstart * 1.0e6, tend * 1.0e6)
ignore_bins = int(100 * 1.024)
freq = psd["F"][numpy.argmax(psd["X"][ignore_bins:]) + ignore_bins]
if numpy.amax(psd["X"][ignore_bins:]) < -15 or freq < 100 or freq > 300:
raise NotAchievedException(
"Did not detect a motor peak, found %f at %f dB" %
(freq, numpy.amax(psd["X"][ignore_bins:])))
else:
self.progress("Detected motor peak at %fHz" % freq)
self.set_parameters({
"INS_LOG_BAT_OPT": 2,
"INS_HNTC2_ENABLE": 1,
"INS_HNTC2_FREQ": freq,
"INS_HNTC2_ATT": 50,
"INS_HNTC2_BW": freq/2,
"INS_HNTC2_MODE": 0,
"SIM_VIB_MOT_MAX": 350,
})
self.reboot_sitl()
self.takeoff(15, mode="ALT_HOLD")
tstart, tend, hover_throttle = self.hover_for_interval(15)
self.set_parameter("SIM_VIB_MOT_MAX", 0)
self.do_RTL()
psd = self.mavfft_fttd(1, 0, tstart * 1.0e6, tend * 1.0e6)
freq = psd["F"][numpy.argmax(psd["X"][ignore_bins:]) + ignore_bins]
peakdB = numpy.amax(psd["X"][ignore_bins:])
if peakdB < -23:
self.progress("Did not detect a motor peak, found %f at %f dB" % (freq, peakdB))
else:
raise NotAchievedException("Detected peak %.1f Hz %.2f dB" % (freq, peakdB))
def VisionPosition(self):
"""Disable GPS navigation, enable Vicon input."""
self.customise_SITL_commandline(["--serial5=sim:vicon:"])
self.progress("Waiting for location")
self.change_mode('LOITER')
self.wait_ready_to_arm()
old_pos = self.assert_receive_message('GLOBAL_POSITION_INT')
print("old_pos=%s" % str(old_pos))
ahrs_ekf_type = self.get_parameter("AHRS_EKF_TYPE")
if ahrs_ekf_type == 2:
self.set_parameter("EK2_GPS_TYPE", 3)
if ahrs_ekf_type == 3:
self.set_parameters({
"EK3_SRC1_POSXY": 6,
"EK3_SRC1_VELXY": 6,
"EK3_SRC1_POSZ": 6,
"EK3_SRC1_VELZ": 6,
})
self.set_parameters({
"GPS1_TYPE": 0,
"VISO_TYPE": 1,
"SERIAL5_PROTOCOL": 1,
})
self.reboot_sitl()
self.mav.mav.system_time_send(int(time.time() * 1000000), 0)
self.progress("Waiting for non-zero-lat")
tstart = self.get_sim_time()
while True:
if self.get_sim_time_cached() - tstart > 60:
raise AutoTestTimeoutException("Did not get non-zero lat")
self.mav.mav.set_gps_global_origin_send(1,
old_pos.lat,
old_pos.lon,
old_pos.alt)
self.delay_sim_time(2)
gpi = self.assert_receive_message('GLOBAL_POSITION_INT')
self.progress("gpi=%s" % str(gpi))
if gpi.lat != 0:
break
self.takeoff()
self.set_rc(1, 1600)
tstart = self.get_sim_time()
while True:
vicon_pos = self.assert_receive_message('VISION_POSITION_ESTIMATE')
if vicon_pos.x > 40:
break
if self.get_sim_time_cached() - tstart > 100:
raise AutoTestTimeoutException("Vicon showed no movement")
self.set_rc(1, 1500)
self.progress("# Enter RTL")
self.change_mode('RTL')
self.set_rc(3, 1500)
tstart = self.get_sim_time()
self.wait_disarmed(timeout=200)
def BodyFrameOdom(self):
"""Disable GPS navigation, enable input of VISION_POSITION_DELTA."""
if self.get_parameter("AHRS_EKF_TYPE") != 3:
return
self.customise_SITL_commandline(["--serial5=sim:vicon:"])
if self.current_onboard_log_contains_message("XKFD"):
raise NotAchievedException("Found unexpected XKFD message")
self.progress("Waiting for location")
self.change_mode('LOITER')
self.wait_ready_to_arm()
old_pos = self.assert_receive_message('GLOBAL_POSITION_INT')
print("old_pos=%s" % str(old_pos))
self.set_parameters({
"EK3_SRC1_POSXY": 6,
"EK3_SRC1_VELXY": 6,
"EK3_SRC1_POSZ": 6,
"EK3_SRC1_VELZ": 6,
"GPS1_TYPE": 0,
"VISO_TYPE": 1,
"SERIAL5_PROTOCOL": 1,
"SIM_VICON_TMASK": 8,
})
self.reboot_sitl()
self.mav.mav.system_time_send(int(time.time() * 1000000), 0)
self.progress("Waiting for non-zero-lat")
tstart = self.get_sim_time()
while True:
self.mav.mav.set_gps_global_origin_send(1,
old_pos.lat,
old_pos.lon,
old_pos.alt)
self.delay_sim_time(2)
gpi = self.assert_receive_message('GLOBAL_POSITION_INT')
self.progress("gpi=%s" % str(gpi))
if gpi.lat != 0:
break
if self.get_sim_time_cached() - tstart > 60:
raise AutoTestTimeoutException("Did not get non-zero lat")
self.takeoff(alt_min=5, mode='ALT_HOLD', require_absolute=False, takeoff_throttle=1800)
self.change_mode('LAND')
self.wait_disarmed()
if not self.current_onboard_log_contains_message("XKFD"):
raise NotAchievedException("Did not find expected XKFD message")
def FlyMissionTwice(self):
'''fly a mission twice in a row without changing modes in between.
Seeks to show bugs in mission state machine'''
self.upload_simple_relhome_mission([
(mavutil.mavlink.MAV_CMD_NAV_TAKEOFF, 0, 0, 20),
(mavutil.mavlink.MAV_CMD_NAV_WAYPOINT, 20, 0, 20),
(mavutil.mavlink.MAV_CMD_NAV_RETURN_TO_LAUNCH, 0, 0, 0),
])
num_wp = self.get_mission_count()
self.set_parameter("AUTO_OPTIONS", 3)
self.change_mode('AUTO')
self.wait_ready_to_arm()
for i in 1, 2:
self.progress("run %u" % i)
self.arm_vehicle()
self.wait_waypoint(num_wp-1, num_wp-1)
self.wait_disarmed()
self.delay_sim_time(20)
def FlyMissionTwiceWithReset(self):
'''Fly a mission twice in a row without changing modes in between.
Allow the mission to complete, then reset the mission state machine and restart the mission.'''
self.upload_simple_relhome_mission([
(mavutil.mavlink.MAV_CMD_NAV_TAKEOFF, 0, 0, 20),
(mavutil.mavlink.MAV_CMD_NAV_WAYPOINT, 20, 0, 20),
(mavutil.mavlink.MAV_CMD_NAV_RETURN_TO_LAUNCH, 0, 0, 0),
])
num_wp = self.get_mission_count()
self.set_parameter("AUTO_OPTIONS", 3)
self.change_mode('AUTO')
self.wait_ready_to_arm()
for i in 1, 2:
self.progress("run %u" % i)
self.set_current_waypoint_using_mav_cmd_do_set_mission_current(seq=0, reset=1)
self.arm_vehicle()
self.wait_waypoint(num_wp-1, num_wp-1)
self.wait_disarmed()
self.delay_sim_time(20)
def MissionIndexValidity(self):
'''Confirm that attempting to select an invalid mission item is rejected.'''
self.upload_simple_relhome_mission([
(mavutil.mavlink.MAV_CMD_NAV_TAKEOFF, 0, 0, 20),
(mavutil.mavlink.MAV_CMD_NAV_WAYPOINT, 20, 0, 20),
(mavutil.mavlink.MAV_CMD_NAV_RETURN_TO_LAUNCH, 0, 0, 0),
])
num_wp = self.get_mission_count()
accepted_indices = [0, 1, num_wp-1]
denied_indices = [-1, num_wp]
for seq in accepted_indices:
self.run_cmd(mavutil.mavlink.MAV_CMD_DO_SET_MISSION_CURRENT,
p1=seq,
timeout=1,
want_result=mavutil.mavlink.MAV_RESULT_ACCEPTED)
for seq in denied_indices:
self.run_cmd(mavutil.mavlink.MAV_CMD_DO_SET_MISSION_CURRENT,
p1=seq,
timeout=1,
want_result=mavutil.mavlink.MAV_RESULT_DENIED)
def InvalidJumpTags(self):
'''Verify the behaviour when selecting invalid jump tags.'''
MAX_TAG_NUM = 65535
self.run_cmd(mavutil.mavlink.MAV_CMD_DO_JUMP_TAG,
p1=MAX_TAG_NUM,
timeout=1,
want_result=mavutil.mavlink.MAV_RESULT_FAILED)
self.run_cmd(mavutil.mavlink.MAV_CMD_DO_JUMP_TAG,
p1=MAX_TAG_NUM+1,
timeout=1,
want_result=mavutil.mavlink.MAV_RESULT_DENIED)
def GPSViconSwitching(self):
"""Fly GPS and Vicon switching test"""
"""Setup parameters including switching to EKF3"""
self.set_parameters({
"VISO_TYPE": 2,
"SERIAL5_PROTOCOL": 2,
"EK3_ENABLE": 1,
"EK3_SRC2_POSXY": 6,
"EK3_SRC2_POSZ": 6,
"EK3_SRC2_VELXY": 6,
"EK3_SRC2_VELZ": 6,
"EK3_SRC2_YAW": 6,
"RC7_OPTION": 80,
"RC8_OPTION": 90,
"EK2_ENABLE": 0,
"AHRS_EKF_TYPE": 3,
})
self.customise_SITL_commandline(["--serial5=sim:vicon:"])
self.set_rc(8, 1000)
self.poll_home_position(timeout=120)
old_pos = self.get_global_position_int()
print("old_pos=%s" % str(old_pos))
self.set_rc(7, 2000)
self.progress("Moving to ensure location is tracked")
self.takeoff(10, mode="LOITER", require_absolute=True, timeout=720)
self.set_rc(2, 1300)
self.set_parameter("SIM_VICON_FAIL", 1)
tstart = self.get_sim_time()
while self.get_sim_time() - tstart < 15:
if not self.mode_is('LOITER'):
raise NotAchievedException("Expected to stay in loiter for >15 seconds")
self.set_parameter("SIM_VICON_FAIL", 0)
self.set_rc(8, 1500)
self.set_parameter("GPS1_TYPE", 0)
tstart = self.get_sim_time()
while self.get_sim_time() - tstart < 15:
if not self.mode_is('LOITER'):
raise NotAchievedException("Expected to stay in loiter for >15 seconds")
self.set_rc(2, 1500)
self.do_RTL()
def RTLSpeed(self):
"""Test RTL Speed parameters"""
rtl_speed_ms = 7
wpnav_speed_ms = 4
wpnav_accel_mss = 3
tolerance = 0.5
self.load_mission("copter_rtl_speed.txt")
self.set_parameters({
'WPNAV_ACCEL': wpnav_accel_mss * 100,
'RTL_SPEED_MS': rtl_speed_ms,
'WPNAV_SPEED': wpnav_speed_ms * 100,
})
self.change_mode('LOITER')
self.wait_ready_to_arm()
self.arm_vehicle()
self.change_mode('AUTO')
self.set_rc(3, 1600)
self.wait_altitude(19, 25, relative=True)
self.wait_groundspeed(wpnav_speed_ms-tolerance, wpnav_speed_ms+tolerance)
self.monitor_groundspeed(wpnav_speed_ms, timeout=20)
self.change_mode('RTL')
self.wait_groundspeed(rtl_speed_ms-tolerance, rtl_speed_ms+tolerance)
self.monitor_groundspeed(rtl_speed_ms, timeout=5)
self.change_mode('AUTO')
self.wait_groundspeed(0-tolerance, 0+tolerance)
self.wait_groundspeed(wpnav_speed_ms-tolerance, wpnav_speed_ms+tolerance)
self.monitor_groundspeed(wpnav_speed_ms, tolerance=0.6, timeout=5)
self.do_RTL()
def NavDelay(self):
"""Fly a simple mission that has a delay in it."""
self.load_mission("copter_nav_delay.txt")
self.set_parameter("DISARM_DELAY", 0)
self.change_mode("LOITER")
self.wait_ready_to_arm()
self.arm_vehicle()
self.change_mode("AUTO")
self.set_rc(3, 1600)
count_start = -1
count_stop = -1
tstart = self.get_sim_time()
last_mission_current_msg = 0
last_seq = None
while self.armed():
now = self.get_sim_time_cached()
if now - tstart > 200:
raise AutoTestTimeoutException("Did not disarm as expected")
m = self.assert_receive_message('MISSION_CURRENT')
at_delay_item = ""
if m.seq == 3:
at_delay_item = "(At delay item)"
if count_start == -1:
count_start = now
if ((now - last_mission_current_msg) > 1 or m.seq != last_seq):
dist = None
x = self.mav.messages.get("NAV_CONTROLLER_OUTPUT", None)
if x is not None:
dist = x.wp_dist
self.progress("MISSION_CURRENT.seq=%u dist=%s %s" %
(m.seq, dist, at_delay_item))
last_mission_current_msg = self.get_sim_time_cached()
last_seq = m.seq
if m.seq > 3:
if count_stop == -1:
count_stop = now
calculated_delay = count_stop - count_start
want_delay = 59
self.progress("Stopped for %u seconds (want >=%u seconds)" %
(calculated_delay, want_delay))
if calculated_delay < want_delay:
raise NotAchievedException("Did not delay for long enough")
def RangeFinder(self):
'''Test RangeFinder Basic Functionality'''
self.assert_not_receiving_message('RANGEFINDER', timeout=5)
self.assert_not_receiving_message('DISTANCE_SENSOR', timeout=5)
self.progress("Ensure no RFND messages in log")
self.set_parameter("LOG_DISARMED", 1)
if self.current_onboard_log_contains_message("RFND"):
raise NotAchievedException("Found unexpected RFND message")
self.set_analog_rangefinder_parameters()
self.set_parameter("RC9_OPTION", 10)
self.set_rc(9, 2000)
self.reboot_sitl()
self.progress("Making sure we now get RANGEFINDER messages")
self.context_set_message_rate_hz('RANGEFINDER', self.sitl_streamrate())
m = self.assert_receive_message('RANGEFINDER', timeout=10)
self.progress("Making sure we now get DISTANCE_SENSOR messages")
self.assert_receive_message('DISTANCE_SENSOR', timeout=10)
self.progress("Checking RangeFinder is marked as enabled in mavlink")
m = self.assert_receive_message('SYS_STATUS', timeout=10)
flags = m.onboard_control_sensors_enabled
if not flags & mavutil.mavlink.MAV_SYS_STATUS_SENSOR_LASER_POSITION:
raise NotAchievedException("Laser not enabled in SYS_STATUS")
self.progress("Disabling laser using switch")
self.set_rc(9, 1000)
self.delay_sim_time(1)
self.progress("Checking RangeFinder is marked as disabled in mavlink")
m = self.assert_receive_message('SYS_STATUS', timeout=10)
flags = m.onboard_control_sensors_enabled
if flags & mavutil.mavlink.MAV_SYS_STATUS_SENSOR_LASER_POSITION:
raise NotAchievedException("Laser enabled in SYS_STATUS")
self.progress("Re-enabling rangefinder")
self.set_rc(9, 2000)
self.delay_sim_time(1)
m = self.assert_receive_message('SYS_STATUS', timeout=10)
flags = m.onboard_control_sensors_enabled
if not flags & mavutil.mavlink.MAV_SYS_STATUS_SENSOR_LASER_POSITION:
raise NotAchievedException("Laser not enabled in SYS_STATUS")
self.takeoff(10, mode="LOITER")
m_p = self.assert_receive_message('GLOBAL_POSITION_INT')
m_r = self.assert_receive_message('RANGEFINDER')
if abs(m_r.distance - m_p.relative_alt/1000) > 1:
raise NotAchievedException(
"RANGEFINDER/GLOBAL_POSITION_INT mismatch %0.2f vs %0.2f" %
(m_r.distance, m_p.relative_alt/1000))
m_ds = self.assert_receive_message('DISTANCE_SENSOR')
if abs(m_ds.current_distance * 0.01 - m_p.relative_alt/1000) > 1:
raise NotAchievedException(
"DISTANCE_SENSOR/GLOBAL_POSITION_INT mismatch %0.2f vs %0.2f" %
(m_ds.current_distance*0.01, m_p.relative_alt/1000))
self.land_and_disarm()
if not self.current_onboard_log_contains_message("RFND"):
raise NotAchievedException("Did not see expected RFND message")
def SplineTerrain(self):
'''Test Splines and Terrain'''
self.set_parameter("TERRAIN_ENABLE", 0)
self.fly_mission("wp.txt")
def WPNAV_SPEED(self):
'''ensure resetting WPNAV_SPEED during a mission works'''
loc = self.poll_home_position()
alt = 20
loc.alt = alt
items = []
items.append((mavutil.mavlink.MAV_CMD_NAV_WAYPOINT, 2000, 0, alt),)
items.append((mavutil.mavlink.MAV_CMD_NAV_RETURN_TO_LAUNCH, 0, 0, 0))
self.upload_simple_relhome_mission(items)
start_speed_ms = self.get_parameter('WPNAV_SPEED') / 100.0
self.takeoff(20)
self.change_mode('AUTO')
self.wait_groundspeed(start_speed_ms-1, start_speed_ms+1, minimum_duration=10)
for speed_ms in 7, 8, 7, 8, 9, 10, 11, 7:
self.set_parameter('WPNAV_SPEED', speed_ms*100)
self.wait_groundspeed(speed_ms-1, speed_ms+1, minimum_duration=10)
self.do_RTL()
def WPNAV_SPEED_UP(self):
'''Change speed (up) during mission'''
items = []
items.append((mavutil.mavlink.MAV_CMD_NAV_WAYPOINT, 0, 0, 20000),)
items.append((mavutil.mavlink.MAV_CMD_NAV_RETURN_TO_LAUNCH, 0, 0, 0))
self.upload_simple_relhome_mission(items)
start_speed_ms = self.get_parameter('WPNAV_SPEED_UP') / 100.0
minimum_duration = 5
self.takeoff(20)
self.change_mode('AUTO')
self.wait_climbrate(start_speed_ms-1, start_speed_ms+1, minimum_duration=minimum_duration)
for speed_ms in 7, 8, 7, 8, 6, 2:
self.set_parameter('WPNAV_SPEED_UP', speed_ms*100)
self.wait_climbrate(speed_ms-1, speed_ms+1, minimum_duration=minimum_duration)
self.do_RTL(timeout=240)
def WPNAV_SPEED_DN(self):
'''Change speed (down) during mission'''
items = []
items.append((mavutil.mavlink.MAV_CMD_NAV_WAYPOINT, 0, 0, 10),)
items.append((mavutil.mavlink.MAV_CMD_NAV_RETURN_TO_LAUNCH, 0, 0, 0))
self.upload_simple_relhome_mission(items)
minimum_duration = 5
self.takeoff(500, timeout=70)
self.change_mode('AUTO')
start_speed_ms = self.get_parameter('WPNAV_SPEED_DN') / 100.0
self.wait_climbrate(-start_speed_ms-1, -start_speed_ms+1, minimum_duration=minimum_duration)
for speed_ms in 7, 8, 7, 8, 6, 2:
self.set_parameter('WPNAV_SPEED_DN', speed_ms*100)
self.wait_climbrate(-speed_ms-1, -speed_ms+1, minimum_duration=minimum_duration)
self.do_RTL()
def load_and_start_mission(self, filename, strict=True):
num_wp = self.load_mission(filename, strict=strict)
self.set_parameter("AUTO_OPTIONS", 3)
self.change_mode('AUTO')
self.wait_ready_to_arm()
self.arm_vehicle()
return num_wp
def fly_mission(self, filename, strict=True):
num_wp = self.load_and_start_mission(filename, strict)
self.wait_waypoint(num_wp-1, num_wp-1)
self.wait_disarmed()
def fly_generic_mission(self, filename, strict=True):
num_wp = self.load_generic_mission(filename, strict=strict)
self.set_parameter("AUTO_OPTIONS", 3)
self.change_mode('AUTO')
self.wait_ready_to_arm()
self.arm_vehicle()
self.wait_waypoint(num_wp-1, num_wp-1)
self.wait_disarmed()
def SurfaceTracking(self):
'''Test Surface Tracking'''
ex = None
self.context_push()
self.install_terrain_handlers_context()
try:
self.set_analog_rangefinder_parameters()
self.set_parameter("RC9_OPTION", 10)
self.set_rc(9, 2000)
self.reboot_sitl()
self.assert_vehicle_location_is_at_startup_location()
self.takeoff(10, mode="LOITER")
lower_surface_pos = mavutil.location(-35.362421, 149.164534, 584, 270)
here = self.mav.location()
bearing = self.get_bearing(here, lower_surface_pos)
self.change_mode("GUIDED")
self.guided_achieve_heading(bearing)
self.change_mode("LOITER")
self.delay_sim_time(2)
m = self.assert_receive_message('GLOBAL_POSITION_INT')
orig_absolute_alt_mm = m.alt
self.progress("Original alt: absolute=%f" % orig_absolute_alt_mm)
self.progress("Flying somewhere which surface is known lower compared to takeoff point")
self.set_rc(2, 1450)
tstart = self.get_sim_time()
while True:
if self.get_sim_time() - tstart > 200:
raise NotAchievedException("Did not reach lower point")
m = self.assert_receive_message('GLOBAL_POSITION_INT')
x = mavutil.location(m.lat/1e7, m.lon/1e7, m.alt/1e3, 0)
dist = self.get_distance(x, lower_surface_pos)
delta = (orig_absolute_alt_mm - m.alt)/1000.0
self.progress("Distance: %fm abs-alt-delta: %fm" %
(dist, delta))
if dist < 15:
if delta < 0.8:
raise NotAchievedException("Did not dip in altitude as expected")
break
self.set_rc(2, 1500)
self.do_RTL()
except Exception as e:
self.print_exception_caught(e)
self.disarm_vehicle(force=True)
ex = e
self.context_pop()
self.reboot_sitl()
if ex is not None:
raise ex
def test_rangefinder_switchover(self):
"""test that the EKF correctly handles the switchover between baro and rangefinder"""
self.set_analog_rangefinder_parameters()
self.set_parameters({
"RNGFND1_MAX": 15.00
})
ahrs_ekf_type = self.get_parameter("AHRS_EKF_TYPE")
if ahrs_ekf_type == 2:
self.set_parameter("EK2_RNG_USE_HGT", 70)
if ahrs_ekf_type == 3:
self.set_parameter("EK3_RNG_USE_HGT", 70)
self.reboot_sitl()
self.assert_vehicle_location_is_at_startup_location()
self.change_mode("LOITER")
self.wait_ready_to_arm()
self.arm_vehicle()
self.set_rc(3, 1800)
self.set_rc(2, 1200)
self.wait_altitude(50, 52, True, 60)
self.change_mode("RTL")
self.wait_altitude(25, 27, True, 120)
self.set_rc(2, 1500)
self.wait_altitude(14, 15, relative=True)
self.wait_rtl_complete()
def _Parachute(self, command):
'''Test Parachute Functionality using specific mavlink command'''
self.set_rc(9, 1000)
self.set_parameters({
"CHUTE_ENABLED": 1,
"CHUTE_TYPE": 10,
"SERVO9_FUNCTION": 27,
"SIM_PARA_ENABLE": 1,
"SIM_PARA_PIN": 9,
})
self.progress("Test triggering parachute in mission")
self.load_mission("copter_parachute_mission.txt")
self.change_mode('LOITER')
self.wait_ready_to_arm()
self.arm_vehicle()
self.change_mode('AUTO')
self.set_rc(3, 1600)
self.wait_statustext('BANG', timeout=60)
self.disarm_vehicle(force=True)
self.reboot_sitl()
self.progress("Test triggering with mavlink message")
self.takeoff(20)
command(
mavutil.mavlink.MAV_CMD_DO_PARACHUTE,
p1=2,
)
self.wait_statustext('BANG', timeout=60)
self.disarm_vehicle(force=True)
self.reboot_sitl()
self.progress("Testing three-position switch")
self.set_parameter("RC9_OPTION", 23)
self.progress("Test manual triggering")
self.takeoff(20)
self.set_rc(9, 2000)
self.wait_statustext('BANG', timeout=60)
self.set_rc(9, 1000)
self.disarm_vehicle(force=True)
self.reboot_sitl()
self.progress("Test mavlink triggering")
self.takeoff(20)
command(
mavutil.mavlink.MAV_CMD_DO_PARACHUTE,
p1=mavutil.mavlink.PARACHUTE_DISABLE,
)
ok = False
try:
self.wait_statustext('BANG', timeout=2)
except AutoTestTimeoutException:
ok = True
if not ok:
raise NotAchievedException("Disabled parachute fired")
command(
mavutil.mavlink.MAV_CMD_DO_PARACHUTE,
p1=mavutil.mavlink.PARACHUTE_ENABLE,
)
ok = False
try:
self.wait_statustext('BANG', timeout=2)
except AutoTestTimeoutException:
ok = True
if not ok:
raise NotAchievedException("Enabled parachute fired")
self.set_rc(9, 1000)
self.disarm_vehicle(force=True)
self.reboot_sitl()
self.takeoff(20)
command(
mavutil.mavlink.MAV_CMD_DO_PARACHUTE,
p1=mavutil.mavlink.PARACHUTE_RELEASE,
)
ok = False
try:
self.wait_statustext('BANG', timeout=2)
except AutoTestTimeoutException:
ok = True
if not ok:
raise NotAchievedException("Parachute fired when going straight from disabled to release")
command(
mavutil.mavlink.MAV_CMD_DO_PARACHUTE,
p1=mavutil.mavlink.PARACHUTE_ENABLE,
)
command(
mavutil.mavlink.MAV_CMD_DO_PARACHUTE,
p1=mavutil.mavlink.PARACHUTE_RELEASE,
)
self.wait_statustext('BANG! Parachute deployed', timeout=2)
self.disarm_vehicle(force=True)
self.reboot_sitl()
self.context_push()
self.progress("Crashing with 3pos switch in enable position")
self.takeoff(40)
self.set_rc(9, 1500)
self.set_parameters({
"SIM_ENGINE_FAIL": 1 << 1,
})
self.wait_statustext('BANG! Parachute deployed', timeout=60)
self.set_rc(9, 1000)
self.disarm_vehicle(force=True)
self.reboot_sitl()
self.context_pop()
self.progress("Crashing with 3pos switch in disable position")
loiter_alt = 10
self.takeoff(loiter_alt, mode='LOITER')
self.set_rc(9, 1100)
self.set_parameters({
"SIM_ENGINE_FAIL": 1 << 1,
})
tstart = self.get_sim_time()
while self.get_sim_time_cached() < tstart + 5:
m = self.assert_receive_message('STATUSTEXT')
if m is None:
continue
if "BANG" in m.text:
self.set_rc(9, 1000)
self.reboot_sitl()
raise NotAchievedException("Parachute deployed when disabled")
self.set_rc(9, 1000)
self.disarm_vehicle(force=True)
self.reboot_sitl()
def Parachute(self):
'''Test Parachute Functionality'''
self._Parachute(self.run_cmd)
self._Parachute(self.run_cmd_int)
def PrecisionLanding(self):
"""Use PrecLand backends precision messages to land aircraft."""
self.context_push()
for backend in [4, 2]:
ex = None
try:
self.set_parameters({
"PLND_ENABLED": 1,
"PLND_TYPE": backend,
})
self.set_analog_rangefinder_parameters()
self.set_parameter("SIM_SONAR_SCALE", 12)
start = self.mav.location()
target = start
(target.lat, target.lng) = mavextra.gps_offset(start.lat, start.lng, 4, -4)
self.progress("Setting target to %f %f" % (target.lat, target.lng))
self.set_parameters({
"SIM_PLD_ENABLE": 1,
"SIM_PLD_LAT": target.lat,
"SIM_PLD_LON": target.lng,
"SIM_PLD_HEIGHT": 0,
"SIM_PLD_ALT_LMT": 15,
"SIM_PLD_DIST_LMT": 10,
})
self.reboot_sitl()
self.progress("Waiting for location")
self.zero_throttle()
self.takeoff(10, 1800, mode="LOITER")
self.change_mode("LAND")
self.zero_throttle()
self.wait_landed_and_disarmed()
self.assert_receive_message('GLOBAL_POSITION_INT')
new_pos = self.mav.location()
delta = self.get_distance(target, new_pos)
self.progress("Landed %f metres from target position" % delta)
max_delta = 1.5
if delta > max_delta:
raise NotAchievedException("Did not land close enough to target position (%fm > %fm" % (delta, max_delta))
if not self.current_onboard_log_contains_message("PL"):
raise NotAchievedException("Did not see expected PL message")
except Exception as e:
self.print_exception_caught(e)
ex = e
self.reboot_sitl()
self.zero_throttle()
self.context_pop()
self.reboot_sitl()
self.progress("All done")
if ex is not None:
raise ex
def Landing(self):
"""Test landing the aircraft."""
def check_landing_speeds(land_speed_high, land_speed_low, land_alt_low, land_speed_high_accuracy=0.1):
self.progress("Checking landing speeds (speed_high=%f speed_low=%f alt_low=%f" %
(land_speed_high, land_speed_low, land_alt_low))
land_high_maintain = 5
land_low_maintain = land_alt_low / land_speed_low / 2
takeoff_alt = (land_high_maintain * land_speed_high + land_alt_low) + 20
self.takeoff(takeoff_alt, mode='STABILIZE', timeout=200, takeoff_throttle=2000)
self.change_mode('LAND')
self.wait_descent_rate(land_speed_high,
minimum_duration=land_high_maintain,
accuracy=land_speed_high_accuracy)
self.wait_descent_rate(land_speed_low)
self.assert_altitude(land_alt_low, relative=True)
self.wait_descent_rate(land_speed_low, minimum_duration=land_low_maintain)
self.wait_disarmed()
check_landing_speeds(
self.get_parameter("WPNAV_SPEED_DN") / 100,
self.get_parameter("LAND_SPD_MS"),
self.get_parameter("LAND_ALT_LOW_M")
)
def test_landing_speeds(land_speed_high, land_speed_low, land_alt_low, **kwargs):
self.set_parameters({
"LAND_SPD_HIGH_MS": land_speed_high,
"LAND_SPD_MS": land_speed_low,
"LAND_ALT_LOW_M": land_alt_low
})
check_landing_speeds(land_speed_high, land_speed_low, land_alt_low, **kwargs)
test_landing_speeds(
5,
1,
30,
land_speed_high_accuracy=0.5
)
def get_system_clock_utc(self, time_seconds):
time_ms = time_seconds * 1000
ms = time_ms % 1000
sec_ms = (time_ms % (60 * 1000)) - ms
min_ms = (time_ms % (60 * 60 * 1000)) - sec_ms - ms
hour_ms = (time_ms % (24 * 60 * 60 * 1000)) - min_ms - sec_ms - ms
secs = sec_ms / 1000
mins = min_ms / (60 * 1000)
hours = hour_ms / (60 * 60 * 1000)
return (hours, mins, secs, 0)
def calc_delay(self, seconds, delay_for_seconds):
if delay_for_seconds >= 3600:
raise ValueError("Won't handle large delays")
(hours,
mins,
secs,
ms) = self.get_system_clock_utc(seconds)
self.progress("Now is %uh %um %us" % (hours, mins, secs))
secs += delay_for_seconds
mins += int(secs/60)
secs %= 60
hours += int(mins / 60)
mins %= 60
if hours > 24:
raise ValueError("Way too big a delay")
self.progress("Delay until %uh %um %us" %
(hours, mins, secs))
return (hours, mins, secs, 0)
def reset_delay_item(self, seq, seconds_in_future):
frame = mavutil.mavlink.MAV_FRAME_GLOBAL_RELATIVE_ALT_INT
command = mavutil.mavlink.MAV_CMD_NAV_DELAY
tried_set = False
hours = None
mins = None
secs = None
while True:
self.progress("Requesting item")
self.mav.mav.mission_request_send(1,
1,
seq)
st = self.mav.recv_match(type='MISSION_ITEM',
blocking=True,
timeout=1)
if st is None:
continue
print("Item: %s" % str(st))
have_match = (tried_set and
st.seq == seq and
st.command == command and
st.param2 == hours and
st.param3 == mins and
st.param4 == secs)
if have_match:
return
self.progress("Mission mismatch")
m = None
tstart = self.get_sim_time()
while True:
if self.get_sim_time_cached() - tstart > 3:
raise NotAchievedException(
"Did not receive MISSION_REQUEST")
self.mav.mav.mission_write_partial_list_send(1,
1,
seq,
seq)
m = self.mav.recv_match(type='MISSION_REQUEST',
blocking=True,
timeout=1)
if m is None:
continue
if m.seq != st.seq:
continue
break
self.progress("Sending absolute-time mission item")
now = self.mav.messages["SYSTEM_TIME"]
if now is None:
raise PreconditionFailedException("Never got SYSTEM_TIME")
if now.time_unix_usec == 0:
raise PreconditionFailedException("system time is zero")
(hours, mins, secs, ms) = self.calc_delay(now.time_unix_usec/1000000, seconds_in_future)
self.mav.mav.mission_item_send(
1,
1,
seq,
frame,
command,
0,
1,
0,
hours,
mins,
secs,
0,
0,
0)
tried_set = True
ack = self.mav.recv_match(type='MISSION_ACK',
blocking=True,
timeout=1)
self.progress("Received ack: %s" % str(ack))
def NavDelayAbsTime(self):
"""fly a simple mission that has a delay in it"""
self.fly_nav_delay_abstime_x(87)
def fly_nav_delay_abstime_x(self, delay_for, expected_delay=None):
"""fly a simple mission that has a delay in it, expect a delay"""
if expected_delay is None:
expected_delay = delay_for
self.load_mission("copter_nav_delay.txt")
self.change_mode("LOITER")
self.wait_ready_to_arm()
delay_item_seq = 3
self.reset_delay_item(delay_item_seq, delay_for)
delay_for_seconds = delay_for
reset_at_m = self.assert_receive_message('SYSTEM_TIME')
reset_at = reset_at_m.time_unix_usec/1000000
self.arm_vehicle()
self.change_mode("AUTO")
self.set_rc(3, 1600)
count_stop = -1
tstart = self.get_sim_time()
while self.armed():
now = self.get_sim_time_cached()
if now - tstart > 240:
raise AutoTestTimeoutException("Did not disarm as expected")
m = self.assert_receive_message('MISSION_CURRENT')
at_delay_item = ""
if m.seq == delay_item_seq:
at_delay_item = "(delay item)"
self.progress("MISSION_CURRENT.seq=%u %s" % (m.seq, at_delay_item))
if m.seq > delay_item_seq:
if count_stop == -1:
count_stop_m = self.assert_receive_message('SYSTEM_TIME')
count_stop = count_stop_m.time_unix_usec/1000000
calculated_delay = count_stop - reset_at
error = abs(calculated_delay - expected_delay)
self.progress("Stopped for %u seconds (want >=%u seconds)" %
(calculated_delay, delay_for_seconds))
if error > 2:
raise NotAchievedException("delay outside expectations")
def NavDelayTakeoffAbsTime(self):
"""make sure taking off at a specific time works"""
self.load_mission("copter_nav_delay_takeoff.txt")
self.change_mode("LOITER")
self.wait_ready_to_arm()
delay_item_seq = 2
delay_for_seconds = 77
self.reset_delay_item(delay_item_seq, delay_for_seconds)
reset_at = self.get_sim_time_cached()
self.arm_vehicle()
self.change_mode("AUTO")
self.set_rc(3, 1600)
tstart = self.get_sim_time()
took_off = False
while self.armed():
now = self.get_sim_time_cached()
if now - tstart > 200:
break
m = self.assert_receive_message('MISSION_CURRENT')
now = self.get_sim_time_cached()
self.progress("%s" % str(m))
if m.seq > delay_item_seq:
if not took_off:
took_off = True
delta_time = now - reset_at
if abs(delta_time - delay_for_seconds) > 2:
raise NotAchievedException((
"Did not take off on time "
"measured=%f want=%f" %
(delta_time, delay_for_seconds)))
if not took_off:
raise NotAchievedException("Did not take off")
def ModeZigZag(self):
'''test zigzag mode'''
self.set_parameter("RC8_OPTION", 61)
self.set_parameter("LOIT_OPTIONS", 0)
self.takeoff(alt_min=5, mode='LOITER')
ZIGZAG = 24
j = 0
slowdown_speed = 0.3
self.start_subtest("Conduct ZigZag test for all 4 directions")
while j < 4:
self.progress("## Align heading with the run-way (j=%d)##" % j)
self.set_rc(8, 1500)
self.set_rc(4, 1420)
self.wait_heading(352-j*90)
self.set_rc(4, 1500)
self.change_mode(ZIGZAG)
self.progress("## Record Point A ##")
self.set_rc(8, 1100)
self.set_rc(1, 1700)
self.wait_distance(20)
self.set_rc(1, 1500)
self.wait_groundspeed(0, slowdown_speed)
self.progress("## Record Point A ##")
self.set_rc(8, 1500)
self.set_rc(8, 1900)
i = 1
while i < 2:
self.start_subtest("Run zigzag A->B and B->A (i=%d)" % i)
self.progress("## fly forward for 10 meter ##")
self.set_rc(2, 1300)
self.wait_distance(10)
self.set_rc(2, 1500)
self.wait_groundspeed(0, slowdown_speed)
self.set_rc(8, 1500)
self.progress("## auto execute vector BA ##")
self.set_rc(8, 1100)
self.wait_distance(17)
self.wait_groundspeed(0, slowdown_speed)
self.progress("## fly forward for 10 meter ##")
self.set_rc(2, 1300)
self.wait_distance(10)
self.set_rc(2, 1500)
self.wait_groundspeed(0, slowdown_speed)
self.set_rc(8, 1500)
self.progress("## auto execute vector AB ##")
self.set_rc(8, 1900)
self.wait_distance(17)
self.wait_groundspeed(0, slowdown_speed)
i = i + 1
self.start_subtest("test the case when pilot switch to manual control during the auto flight")
self.progress("## fly forward for 10 meter ##")
self.set_rc(2, 1300)
self.wait_distance(10)
self.set_rc(2, 1500)
self.wait_groundspeed(0, 0.3)
self.set_rc(8, 1500)
self.progress("## auto execute vector BA ##")
self.set_rc(8, 1100)
self.wait_distance(8)
self.set_rc(8, 1500)
self.wait_groundspeed(0, slowdown_speed)
self.progress("## Manual control to fly forward ##")
self.set_rc(2, 1300)
self.wait_distance(8)
self.set_rc(2, 1500)
self.wait_groundspeed(0, slowdown_speed)
self.progress("## continue vector BA ##")
self.set_rc(8, 1100)
self.wait_distance(8)
self.wait_groundspeed(0, slowdown_speed)
self.set_rc(8, 1500)
self.progress("## auto execute vector AB ##")
self.set_rc(8, 1900)
self.wait_distance(17)
self.wait_groundspeed(0, slowdown_speed)
self.change_mode('LOITER')
j = j + 1
self.do_RTL()
def SetModesViaModeSwitch(self):
'''Set modes via modeswitch'''
fltmode_ch = 5
self.set_parameter("FLTMODE_CH", fltmode_ch)
self.set_rc(fltmode_ch, 1000)
testmodes = [("FLTMODE1", 4, "GUIDED", 1165),
("FLTMODE2", 2, "ALT_HOLD", 1295),
("FLTMODE3", 6, "RTL", 1425),
("FLTMODE4", 7, "CIRCLE", 1555),
("FLTMODE5", 1, "ACRO", 1685),
("FLTMODE6", 17, "BRAKE", 1815),
]
for mode in testmodes:
(parm, parm_value, name, pwm) = mode
self.set_parameter(parm, parm_value)
for mode in reversed(testmodes):
(parm, parm_value, name, pwm) = mode
self.set_rc(fltmode_ch, pwm)
self.wait_mode(name)
for mode in testmodes:
(parm, parm_value, name, pwm) = mode
self.set_rc(fltmode_ch, pwm)
self.wait_mode(name)
for mode in reversed(testmodes):
(parm, parm_value, name, pwm) = mode
self.set_rc(fltmode_ch, pwm)
self.wait_mode(name)
def SetModesViaAuxSwitch(self):
'''"Set modes via auxswitch"'''
fltmode_ch = int(self.get_parameter("FLTMODE_CH"))
self.set_rc(fltmode_ch, 1000)
self.wait_mode("CIRCLE")
self.set_rc(9, 1000)
self.set_rc(10, 1000)
self.set_parameters({
"RC9_OPTION": 18,
"RC10_OPTION": 55,
})
self.set_rc(9, 1900)
self.wait_mode("LAND")
self.set_rc(10, 1900)
self.wait_mode("GUIDED")
self.set_rc(10, 1000)
self.wait_mode("CIRCLE")
def fly_guided_stop(self,
timeout=20,
groundspeed_tolerance=0.05,
climb_tolerance=0.01):
"""stop the vehicle moving in guided mode"""
self.progress("Stopping vehicle")
tstart = self.get_sim_time()
self.mav.mav.set_position_target_local_ned_send(
0,
1,
1,
mavutil.mavlink.MAV_FRAME_BODY_NED,
MAV_POS_TARGET_TYPE_MASK.POS_ONLY | MAV_POS_TARGET_TYPE_MASK.LAST_BYTE,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
)
while True:
if self.get_sim_time_cached() - tstart > timeout:
raise NotAchievedException("Vehicle did not stop")
m = self.assert_receive_message('VFR_HUD')
print("%s" % str(m))
if (m.groundspeed < groundspeed_tolerance and
m.climb < climb_tolerance):
break
def send_set_position_target_global_int(self, lat, lon, alt):
self.mav.mav.set_position_target_global_int_send(
0,
1,
1,
mavutil.mavlink.MAV_FRAME_GLOBAL_RELATIVE_ALT_INT,
MAV_POS_TARGET_TYPE_MASK.POS_ONLY,
lat,
lon,
alt,
0,
0,
0,
0,
0,
0,
0,
0,
)
def fly_guided_move_global_relative_alt(self, lat, lon, alt):
startpos = self.assert_receive_message('GLOBAL_POSITION_INT')
self.send_set_position_target_global_int(lat, lon, alt)
tstart = self.get_sim_time()
while True:
if self.get_sim_time_cached() - tstart > 200:
raise NotAchievedException("Did not move far enough")
pos = self.assert_receive_message('GLOBAL_POSITION_INT')
delta = self.get_distance_int(startpos, pos)
self.progress("delta=%f (want >10)" % delta)
if delta > 10:
break
def fly_guided_move_local(self, x, y, z_up, timeout=100):
"""move the vehicle using MAVLINK_MSG_ID_SET_POSITION_TARGET_LOCAL_NED"""
startpos = self.assert_receive_message('LOCAL_POSITION_NED')
self.progress("startpos=%s" % str(startpos))
tstart = self.get_sim_time()
self.mav.mav.set_position_target_local_ned_send(
0,
1,
1,
mavutil.mavlink.MAV_FRAME_LOCAL_NED,
MAV_POS_TARGET_TYPE_MASK.POS_ONLY | MAV_POS_TARGET_TYPE_MASK.LAST_BYTE,
x,
y,
-z_up,
0,
0,
0,
0,
0,
0,
0,
0,
)
while True:
if self.get_sim_time_cached() - tstart > timeout:
raise NotAchievedException("Did not reach destination")
dist = self.distance_to_local_position((x, y, -z_up))
if dist < 1:
self.progress(f"Reach distance ({dist})")
self.progress("endpos=%s" % str(startpos))
break
def test_guided_local_position_target(self, x, y, z_up):
""" Check target position being received by vehicle """
self.progress("Setting local target in NED: (%f, %f, %f)" % (x, y, -z_up))
self.progress("Setting rate to 1 Hz")
self.set_message_rate_hz(mavutil.mavlink.MAVLINK_MSG_ID_POSITION_TARGET_LOCAL_NED, 1)
target_typemask = MAV_POS_TARGET_TYPE_MASK.POS_ONLY
self.mav.mav.set_position_target_local_ned_send(
0,
1,
1,
mavutil.mavlink.MAV_FRAME_LOCAL_NED,
target_typemask | MAV_POS_TARGET_TYPE_MASK.LAST_BYTE,
x,
y,
-z_up,
0,
0,
0,
0,
0,
0,
0,
0,
)
m = self.assert_receive_message('POSITION_TARGET_LOCAL_NED', timeout=2)
self.progress("Received local target: %s" % str(m))
if not (m.type_mask == (target_typemask | MAV_POS_TARGET_TYPE_MASK.LAST_BYTE) or m.type_mask == target_typemask):
raise NotAchievedException("Did not receive proper mask: expected=%u or %u, got=%u" %
((target_typemask | MAV_POS_TARGET_TYPE_MASK.LAST_BYTE), target_typemask, m.type_mask))
if x - m.x > 0.1:
raise NotAchievedException("Did not receive proper target position x: wanted=%f got=%f" % (x, m.x))
if y - m.y > 0.1:
raise NotAchievedException("Did not receive proper target position y: wanted=%f got=%f" % (y, m.y))
if z_up - (-m.z) > 0.1:
raise NotAchievedException("Did not receive proper target position z: wanted=%f got=%f" % (z_up, -m.z))
def test_guided_local_velocity_target(self, vx, vy, vz_up, timeout=3):
" Check local target velocity being received by vehicle "
self.progress("Setting local NED velocity target: (%f, %f, %f)" % (vx, vy, -vz_up))
self.progress("Setting POSITION_TARGET_LOCAL_NED message rate to 10Hz")
self.set_message_rate_hz(mavutil.mavlink.MAVLINK_MSG_ID_POSITION_TARGET_LOCAL_NED, 10)
target_typemask = (MAV_POS_TARGET_TYPE_MASK.POS_IGNORE |
MAV_POS_TARGET_TYPE_MASK.YAW_IGNORE | MAV_POS_TARGET_TYPE_MASK.YAW_RATE_IGNORE)
tstart = self.get_sim_time()
while self.get_sim_time_cached() - tstart < timeout:
self.mav.mav.set_position_target_local_ned_send(
0,
1,
1,
mavutil.mavlink.MAV_FRAME_LOCAL_NED,
target_typemask | MAV_POS_TARGET_TYPE_MASK.LAST_BYTE,
0,
0,
0,
vx,
vy,
-vz_up,
0,
0,
0,
0,
0,
)
m = self.assert_receive_message('POSITION_TARGET_LOCAL_NED')
self.progress("Received local target: %s" % str(m))
if not (m.type_mask == (target_typemask | MAV_POS_TARGET_TYPE_MASK.LAST_BYTE) or m.type_mask == target_typemask):
raise NotAchievedException("Did not receive proper mask: expected=%u or %u, got=%u" %
((target_typemask | MAV_POS_TARGET_TYPE_MASK.LAST_BYTE), target_typemask, m.type_mask))
if vx - m.vx > 0.1:
raise NotAchievedException("Did not receive proper target velocity vx: wanted=%f got=%f" % (vx, m.vx))
if vy - m.vy > 0.1:
raise NotAchievedException("Did not receive proper target velocity vy: wanted=%f got=%f" % (vy, m.vy))
if vz_up - (-m.vz) > 0.1:
raise NotAchievedException("Did not receive proper target velocity vz: wanted=%f got=%f" % (vz_up, -m.vz))
self.progress("Received proper target velocity commands")
def wait_for_local_velocity(self, vx, vy, vz_up, timeout=10):
""" Wait for local target velocity"""
self.progress("Waiting for local NED velocity target: (%f, %f, %f)" % (vx, vy, -vz_up))
self.progress("Setting LOCAL_POSITION_NED message rate to 10Hz")
self.set_message_rate_hz(mavutil.mavlink.MAVLINK_MSG_ID_LOCAL_POSITION_NED, 10)
tstart = self.get_sim_time()
while self.get_sim_time_cached() - tstart < timeout:
m = self.mav.recv_match(type="LOCAL_POSITION_NED", blocking=True, timeout=1)
if m is None:
raise NotAchievedException("Did not receive any position local ned message for 1 second!")
else:
self.progress("Received local position ned message: %s" % str(m))
if vx - m.vx <= 0.1 and vy - m.vy <= 0.1 and vz_up - (-m.vz) <= 0.1:
self.progress("Vehicle successfully reached to target velocity!")
return
error_message = "Did not receive target velocities vx, vy, vz_up, wanted=(%f, %f, %f) got=(%f, %f, %f)"
error_message = error_message % (vx, vy, vz_up, m.vx, m.vy, -m.vz)
raise NotAchievedException(error_message)
def test_position_target_message_mode(self):
" Ensure that POSITION_TARGET_LOCAL_NED messages are sent in Guided Mode only "
self.hover()
self.change_mode('LOITER')
self.progress("Setting POSITION_TARGET_LOCAL_NED message rate to 10Hz")
self.set_message_rate_hz(mavutil.mavlink.MAVLINK_MSG_ID_POSITION_TARGET_LOCAL_NED, 10)
self.assert_not_receiving_message('POSITION_TARGET_LOCAL_NED')
self.progress("Did not receive any POSITION_TARGET_LOCAL_NED message in LOITER mode. Success")
def earth_to_body(self, vector):
r = mavextra.rotation(self.mav.messages["ATTITUDE"]).invert()
return r * vector
def precision_loiter_to_pos(self, x, y, z, send_bf=True, timeout=40):
'''send landing_target messages at vehicle until it arrives at
location to x, y, z from origin (in metres), z is *up*
if send_bf is True then targets sent in body-frame, if False then sent in local FRD frame'''
dest_ned = rotmat.Vector3(x, y, -z)
tstart = self.get_sim_time()
success_start = -1
while True:
now = self.get_sim_time_cached()
if now - tstart > timeout:
raise NotAchievedException("Did not loiter to position!")
m_pos = self.assert_receive_message('LOCAL_POSITION_NED')
pos_ned = rotmat.Vector3(m_pos.x, m_pos.y, m_pos.z)
delta_ef = dest_ned - pos_ned
dist = math.sqrt(delta_ef.x * delta_ef.x + delta_ef.y * delta_ef.y)
dist_max = 1
self.progress("dist=%f want <%f" % (dist, dist_max))
if dist < dist_max:
if success_start == -1:
success_start = now
elif now - success_start > 10:
self.progress("Yay!")
break
else:
success_start = -1
mav_frame = None
if send_bf:
delta_bf = self.earth_to_body(delta_ef)
print("delta_bf=%s" % str(delta_bf))
angle_x = math.atan2(delta_bf.y, delta_bf.z)
angle_y = -math.atan2(delta_bf.x, delta_bf.z)
distance = math.sqrt(delta_bf.x * delta_bf.x +
delta_bf.y * delta_bf.y +
delta_bf.z * delta_bf.z)
mav_frame = mavutil.mavlink.MAV_FRAME_BODY_FRD
else:
rotmat_yaw = Matrix3()
rotmat_yaw.rotate_yaw(self.mav.messages["ATTITUDE"].yaw)
delta_local_frd = rotmat_yaw * delta_ef
angle_x = math.atan2(delta_local_frd.y, delta_local_frd.z)
angle_y = -math.atan2(delta_local_frd.x, delta_local_frd.z)
distance = math.sqrt(delta_local_frd.x * delta_local_frd.x +
delta_local_frd.y * delta_local_frd.y +
delta_local_frd.z * delta_local_frd.z)
mav_frame = mavutil.mavlink.MAV_FRAME_LOCAL_FRD
self.mav.mav.landing_target_send(
0,
1,
mav_frame,
angle_x,
angle_y,
distance,
0.01,
0.01
)
def set_servo_gripper_parameters(self):
self.set_parameters({
"GRIP_ENABLE": 1,
"GRIP_TYPE": 1,
"SIM_GRPS_ENABLE": 1,
"SIM_GRPS_PIN": 8,
"SERVO8_FUNCTION": 28,
})
def PayloadPlaceMission(self):
"""Test payload placing in auto."""
self.set_analog_rangefinder_parameters()
self.set_servo_gripper_parameters()
self.reboot_sitl()
num_wp = self.load_and_start_mission("copter_payload_place.txt")
self.wait_text("Gripper load releas(ed|ing)", timeout=90, regex=True)
dist_limit = 1
target_loc = mavutil.location(-35.363106,
149.165436,
0,
0)
dist = self.get_distance(target_loc, self.mav.location())
self.progress("dist=%f" % (dist,))
if dist > dist_limit:
raise NotAchievedException("Did not honour target lat/lng (dist=%f want <%f" %
(dist, dist_limit))
self.wait_waypoint(num_wp-1, num_wp-1)
self.do_RTL()
def PayloadPlaceMissionOpenGripper(self):
'''test running the mission when the gripper is open'''
self.set_analog_rangefinder_parameters()
self.set_servo_gripper_parameters()
self.reboot_sitl()
self.load_mission("copter_payload_place.txt")
self.set_parameter("AUTO_OPTIONS", 3)
self.change_mode('AUTO')
self.wait_ready_to_arm()
self.arm_vehicle()
self.progress("Opening the gripper before time")
self.run_auxfunc(19, 0)
self.wait_text("Abort: Gripper Open", timeout=90)
self.wait_disarmed()
def Weathervane(self):
'''Test copter weathervaning'''
self.set_parameters({
"SIM_WIND_SPD": 10,
"SIM_WIND_DIR": 100,
"GUID_OPTIONS": 129,
"AUTO_OPTIONS": 131,
"WVANE_ENABLE": 1,
"WVANE_GAIN": 3,
"WVANE_VELZ_MAX": 1,
"WVANE_SPD_MAX": 2
})
self.progress("Test weathervaning in auto")
self.load_mission("weathervane_mission.txt", strict=False)
self.change_mode("AUTO")
self.wait_ready_to_arm()
self.arm_vehicle()
self.wait_statustext("Weathervane Active", timeout=60)
self.do_RTL()
self.wait_disarmed()
self.change_mode("GUIDED")
self.progress("Test weathervaning in guided vel-accel")
self.set_rc(3, 1000)
self.wait_ready_to_arm()
self.arm_vehicle()
self.user_takeoff(alt_min=15)
self.wait_heading(100, accuracy=8, timeout=100)
self.progress("Test weathervaning in guided pos only")
self.fly_guided_move_local(x=40, y=0, z_up=15)
self.wait_heading(100, accuracy=8, timeout=100)
self.do_RTL()
def _DO_WINCH(self, command):
self.context_push()
self.load_default_params_file("copter-winch.parm")
self.reboot_sitl()
self.wait_ready_to_arm()
self.start_subtest("starts relaxed")
self.wait_servo_channel_value(9, 0)
self.start_subtest("rate control")
command(
mavutil.mavlink.MAV_CMD_DO_WINCH,
p1=1,
p2=mavutil.mavlink.WINCH_RATE_CONTROL,
p3=0,
p4=1,
)
self.wait_servo_channel_value(9, 1900)
self.start_subtest("relax")
command(
mavutil.mavlink.MAV_CMD_DO_WINCH,
p1=1,
p2=mavutil.mavlink.WINCH_RELAXED,
p3=0,
p4=1,
)
self.wait_servo_channel_value(9, 0)
self.start_subtest("hold but zero output")
command(
mavutil.mavlink.MAV_CMD_DO_WINCH,
p1=1,
p2=mavutil.mavlink.WINCH_RATE_CONTROL,
p3=0,
p4=0,
)
self.wait_servo_channel_value(9, 1500)
self.start_subtest("relax")
command(
mavutil.mavlink.MAV_CMD_DO_WINCH,
p1=1,
p2=mavutil.mavlink.WINCH_RELAXED,
p3=0,
p4=1,
)
self.wait_servo_channel_value(9, 0)
self.start_subtest("position")
command(
mavutil.mavlink.MAV_CMD_DO_WINCH,
p1=1,
p2=mavutil.mavlink.WINCH_RELATIVE_LENGTH_CONTROL,
p3=2,
p4=1,
)
self.wait_servo_channel_value(9, 1900)
self.wait_servo_channel_value(9, 1500, timeout=60)
self.context_pop()
self.reboot_sitl()
def DO_WINCH(self):
'''test mavlink DO_WINCH command'''
self._DO_WINCH(self.run_cmd_int)
self._DO_WINCH(self.run_cmd)
def GuidedSubModeChange(self):
""""Ensure we can move around in guided after a takeoff command."""
'''start by disabling GCS failsafe, otherwise we immediately disarm
due to (apparently) not receiving traffic from the GCS for
too long. This is probably a function of --speedup'''
self.set_parameters({
"FS_GCS_ENABLE": 0,
"DISARM_DELAY": 0,
})
self.change_mode("GUIDED")
self.wait_ready_to_arm()
self.arm_vehicle()
self.user_takeoff(alt_min=10)
self.start_subtest("yaw through absolute angles using MAV_CMD_CONDITION_YAW")
self.guided_achieve_heading(45)
self.guided_achieve_heading(135)
self.start_subtest("move the vehicle using set_position_target_global_int")
self.fly_guided_move_global_relative_alt(5, 5, 10)
self.start_subtest("move the vehicle using MAVLINK_MSG_ID_SET_POSITION_TARGET_LOCAL_NED")
self.fly_guided_stop(groundspeed_tolerance=0.1)
self.fly_guided_move_local(5, 5, 10)
self.start_subtest("Checking that WP_YAW_BEHAVIOUR 0 works")
self.set_parameter('WP_YAW_BEHAVIOR', 0)
self.delay_sim_time(2)
orig_heading = self.get_heading()
self.fly_guided_move_local(5, 0, 10)
self.assert_heading(orig_heading, accuracy=2)
self.fly_guided_move_local(0, 5, 10)
self.assert_heading(orig_heading, accuracy=2)
self.start_subtest("Check target position received by vehicle using SET_MESSAGE_INTERVAL")
self.test_guided_local_position_target(5, 5, 10)
self.test_guided_local_velocity_target(2, 2, 1)
self.test_position_target_message_mode()
self.do_RTL()
def WPYawBehaviour1RTL(self):
'''ensure behaviour 1 (face home) works in RTL'''
self.start_subtest("moving off in guided mode and checking return yaw")
self.change_mode('GUIDED')
self.wait_ready_to_arm()
self.wait_heading(272, timeout=1)
self.takeoff(2, mode='GUIDED')
self.set_parameter('WP_YAW_BEHAVIOR', 1)
self.fly_guided_move_local(100, 100, z_up=20)
self.wait_heading(45, timeout=1)
self.change_mode('RTL')
self.wait_heading(225, minimum_duration=10, timeout=20)
self.wait_disarmed()
self.reboot_sitl()
self.start_subtest("now the same but in auto mode")
self.upload_simple_relhome_mission([
(mavutil.mavlink.MAV_CMD_NAV_TAKEOFF, 0, 0, 2),
(mavutil.mavlink.MAV_CMD_NAV_WAYPOINT, 100, 100, 20),
(mavutil.mavlink.MAV_CMD_NAV_RETURN_TO_LAUNCH, 0, 0, 0),
])
self.set_parameter('AUTO_OPTIONS', 3)
self.change_mode('AUTO')
self.set_rc(3, 1000)
self.wait_ready_to_arm()
self.wait_heading(273, timeout=1)
self.arm_vehicle()
self.wait_heading(45, minimum_duration=10, timeout=20)
self.wait_current_waypoint(3)
self.wait_heading(225, minimum_duration=10, timeout=20)
self.wait_disarmed()
def TestGripperMission(self):
'''Test Gripper mission items'''
num_wp = self.load_mission("copter-gripper-mission.txt")
self.change_mode('LOITER')
self.wait_ready_to_arm()
self.assert_vehicle_location_is_at_startup_location()
self.arm_vehicle()
self.change_mode('AUTO')
self.set_rc(3, 1500)
self.wait_statustext("Gripper Grabbed", timeout=60)
self.wait_statustext("Gripper Released", timeout=60)
self.wait_waypoint(num_wp-1, num_wp-1)
self.wait_disarmed()
def SplineLastWaypoint(self):
'''Test Spline as last waypoint'''
self.load_mission("copter-spline-last-waypoint.txt")
self.change_mode('LOITER')
self.wait_ready_to_arm()
self.arm_vehicle()
self.change_mode('AUTO')
self.set_rc(3, 1500)
self.wait_altitude(10, 3000, relative=True)
self.do_RTL()
def ManualThrottleModeChange(self):
'''Check manual throttle mode changes denied on high throttle'''
self.set_parameter("FS_GCS_ENABLE", 0)
self.change_mode("STABILIZE")
self.wait_ready_to_arm()
self.arm_vehicle()
self.change_mode("ACRO")
self.change_mode("STABILIZE")
self.change_mode("GUIDED")
self.set_rc(3, 1700)
self.watch_altitude_maintained(altitude_min=-1, altitude_max=0.2)
self.run_cmd_do_set_mode(
"ACRO",
want_result=mavutil.mavlink.MAV_RESULT_FAILED)
self.run_cmd_do_set_mode(
"STABILIZE",
want_result=mavutil.mavlink.MAV_RESULT_FAILED)
self.run_cmd_do_set_mode(
"DRIFT",
want_result=mavutil.mavlink.MAV_RESULT_FAILED)
self.progress("Check setting an invalid mode")
self.run_cmd(
mavutil.mavlink.MAV_CMD_DO_SET_MODE,
p1=mavutil.mavlink.MAV_MODE_FLAG_CUSTOM_MODE_ENABLED,
p2=126,
want_result=mavutil.mavlink.MAV_RESULT_FAILED,
timeout=1,
)
self.set_rc(3, 1000)
self.run_cmd_do_set_mode("ACRO")
self.wait_disarmed()
def constrained_mount_pitch(self, pitch_angle_deg, mount_instance=1):
PITCH_MIN = self.get_parameter("MNT%u_PITCH_MIN" % mount_instance)
PITCH_MAX = self.get_parameter("MNT%u_PITCH_MAX" % mount_instance)
return min(max(pitch_angle_deg, PITCH_MIN), PITCH_MAX)
def test_mount_pitch(self, despitch, despitch_tolerance, mount_mode, timeout=10, hold=0, constrained=True):
self.set_mount_mode(mount_mode)
tstart = self.get_sim_time()
success_start = 0
while True:
now = self.get_sim_time_cached()
if now - tstart > timeout:
raise NotAchievedException("Mount pitch not achieved")
if constrained:
despitch = self.constrained_mount_pitch(despitch)
'''retrieve latest angles from GIMBAL_DEVICE_ATTITUDE_STATUS'''
mount_roll, mount_pitch, mount_yaw, mount_yaw_is_absolute = self.get_mount_roll_pitch_yaw_deg()
if abs(despitch - mount_pitch) > despitch_tolerance:
self.progress("Mount pitch incorrect: got=%f want=%f (+/- %f)" %
(mount_pitch, despitch, despitch_tolerance))
success_start = 0
continue
self.progress("Mount pitch correct: %f degrees == %f" %
(mount_pitch, despitch))
if success_start == 0:
success_start = now
if now - success_start >= hold:
self.progress("Mount pitch achieved")
return
def do_pitch(self, pitch):
'''pitch aircraft in guided/angle mode'''
self.mav.mav.set_attitude_target_send(
0,
1,
1,
0,
mavextra.euler_to_quat([0, math.radians(pitch), 0]),
0,
0,
0,
0.5)
def do_yaw_rate(self, yaw_rate):
'''yaw aircraft in guided/rate mode'''
self.run_cmd(
mavutil.mavlink.MAV_CMD_CONDITION_YAW,
p1=60,
p2=0,
p3=1,
p4=1,
quiet=True,
)
def get_mount_roll_pitch_yaw_deg(self):
'''return mount (aka gimbal) roll, pitch and yaw angles in degrees'''
m = self.assert_receive_message('GIMBAL_DEVICE_ATTITUDE_STATUS', timeout=5)
yaw_is_absolute = m.flags & mavutil.mavlink.GIMBAL_DEVICE_FLAGS_YAW_LOCK
q = quaternion.Quaternion(m.q)
euler = q.euler
return math.degrees(euler[0]), math.degrees(euler[1]), math.degrees(euler[2]), yaw_is_absolute
def set_mount_mode(self, mount_mode):
'''set mount mode'''
self.run_cmd_int(
mavutil.mavlink.MAV_CMD_DO_MOUNT_CONFIGURE,
p1=mount_mode,
p2=0,
p3=0,
)
self.run_cmd(
mavutil.mavlink.MAV_CMD_DO_MOUNT_CONFIGURE,
p1=mount_mode,
p2=0,
p3=0,
)
def test_mount_rc_targetting(self, pitch_rc_neutral=1500, do_rate_tests=True):
'''called in multipleplaces to make sure that mount RC targeting works'''
if True:
self.context_push()
self.set_parameters({
'RC6_OPTION': 0,
'RC11_OPTION': 212,
'RC12_OPTION': 213,
'RC13_OPTION': 214,
'RC12_MIN': 1100,
'RC12_MAX': 1900,
'RC12_TRIM': 1500,
'MNT1_PITCH_MIN': -45,
'MNT1_PITCH_MAX': 45,
})
self.progress("Testing RC angular control")
self.set_rc_from_map({
11: 1500,
12: 1500,
13: 1500,
})
self.test_mount_pitch(0, 1, mavutil.mavlink.MAV_MOUNT_MODE_RC_TARGETING)
self.progress("Testing RC input down 1/4 of its range in the output, should be down 1/4 range in output")
rc12_in = 1400
rc12_min = 1100
rc12_max = 1900
mpitch_min = -45.0
mpitch_max = 45.0
expected_pitch = (float(rc12_in-rc12_min)/float(rc12_max-rc12_min) * (mpitch_max-mpitch_min)) + mpitch_min
self.progress("expected mount pitch: %f" % expected_pitch)
if expected_pitch != -11.25:
raise NotAchievedException("Calculation wrong - defaults changed?!")
self.set_rc(12, rc12_in)
self.test_mount_pitch(-11.25, 0.1, mavutil.mavlink.MAV_MOUNT_MODE_RC_TARGETING)
self.set_rc(12, 1800)
self.test_mount_pitch(33.75, 0.1, mavutil.mavlink.MAV_MOUNT_MODE_RC_TARGETING)
self.set_rc_from_map({
11: 1500,
12: 1500,
13: 1500,
})
try:
self.context_push()
self.set_parameters({
"RC12_MIN": 1000,
"RC12_MAX": 2000,
"MNT1_PITCH_MIN": -90,
"MNT1_PITCH_MAX": 10,
})
self.set_rc(12, 1000)
self.test_mount_pitch(-90.00, 0.1, mavutil.mavlink.MAV_MOUNT_MODE_RC_TARGETING)
self.set_rc(12, 2000)
self.test_mount_pitch(10.00, 0.1, mavutil.mavlink.MAV_MOUNT_MODE_RC_TARGETING)
self.set_rc(12, 1500)
self.test_mount_pitch(-40.00, 0.1, mavutil.mavlink.MAV_MOUNT_MODE_RC_TARGETING)
finally:
self.context_pop()
self.set_rc(12, 1500)
if do_rate_tests:
self.test_mount_rc_targetting_rate_control()
self.context_pop()
def test_mount_rc_targetting_rate_control(self, pitch_rc_neutral=1500):
if True:
self.progress("Testing RC rate control")
self.set_parameter('MNT1_RC_RATE', 10)
self.test_mount_pitch(0, 1, mavutil.mavlink.MAV_MOUNT_MODE_RC_TARGETING)
self.set_rc(12, 1300)
self.test_mount_pitch(-5, 1, mavutil.mavlink.MAV_MOUNT_MODE_RC_TARGETING, constrained=False)
self.test_mount_pitch(-10, 1, mavutil.mavlink.MAV_MOUNT_MODE_RC_TARGETING, constrained=False)
self.test_mount_pitch(-15, 1, mavutil.mavlink.MAV_MOUNT_MODE_RC_TARGETING, constrained=False)
self.test_mount_pitch(-20, 1, mavutil.mavlink.MAV_MOUNT_MODE_RC_TARGETING, constrained=False)
self.set_rc(12, 1700)
self.test_mount_pitch(-15, 1, mavutil.mavlink.MAV_MOUNT_MODE_RC_TARGETING, constrained=False)
self.test_mount_pitch(-10, 1, mavutil.mavlink.MAV_MOUNT_MODE_RC_TARGETING, constrained=False)
self.test_mount_pitch(-5, 1, mavutil.mavlink.MAV_MOUNT_MODE_RC_TARGETING, constrained=False)
self.test_mount_pitch(0, 1, mavutil.mavlink.MAV_MOUNT_MODE_RC_TARGETING, constrained=False)
self.test_mount_pitch(5, 1, mavutil.mavlink.MAV_MOUNT_MODE_RC_TARGETING, constrained=False)
self.progress("Reverting to angle mode")
self.set_parameter('MNT1_RC_RATE', 0)
self.set_rc(12, 1500)
self.test_mount_pitch(0, 0.1, mavutil.mavlink.MAV_MOUNT_MODE_RC_TARGETING)
def mount_test_body(self, pitch_rc_neutral=1500, do_rate_tests=True, constrain_sysid_target=True):
'''Test Camera/Antenna Mount - assumes a camera is set up and ready to go'''
if True:
self.assert_receive_message('GIMBAL_DEVICE_ATTITUDE_STATUS', timeout=5, very_verbose=True)
self.set_mount_mode(mavutil.mavlink.MAV_MOUNT_MODE_NEUTRAL)
mount_roll_deg, mount_pitch_deg, mount_yaw_deg, mount_yaw_is_absolute = self.get_mount_roll_pitch_yaw_deg()
if mount_roll_deg != 0 or mount_pitch_deg != 0 or mount_yaw_deg != 0:
raise NotAchievedException("Mount not neutral")
self.takeoff(30, mode='GUIDED')
despitch = 10
despitch_tolerance = 3
self.progress("Pitching vehicle")
self.do_pitch(despitch)
self.wait_pitch(despitch, despitch_tolerance)
mount_roll_deg, mount_pitch_deg, mount_yaw_deg, mount_yaw_is_absolute = self.get_mount_roll_pitch_yaw_deg()
if mount_roll_deg != 0 or mount_pitch_deg != 0 or mount_yaw_deg != 0:
raise NotAchievedException("Mount stabilising when not requested")
self.progress("Gimbal to RC Targeting mode")
self.set_rc(6, pitch_rc_neutral)
self.set_mount_mode(mavutil.mavlink.MAV_MOUNT_MODE_RC_TARGETING)
self.progress("Pitching vehicle")
self.do_pitch(despitch)
self.wait_pitch(despitch, despitch_tolerance)
self.test_mount_pitch(0, 1, mavutil.mavlink.MAV_MOUNT_MODE_RC_TARGETING)
self.progress("Point gimbal using GIMBAL_MANAGER_PITCHYAW (ANGLE)")
self.do_pitch(0)
self.wait_pitch(0, despitch_tolerance)
self.set_mount_mode(mavutil.mavlink.MAV_MOUNT_MODE_MAVLINK_TARGETING)
for (method, angle) in (self.run_cmd, -20), (self.run_cmd_int, -30):
method(
mavutil.mavlink.MAV_CMD_DO_GIMBAL_MANAGER_PITCHYAW,
p1=angle,
p2=0,
p3=0,
p4=0,
p5=0,
p6=0,
p7=0,
)
self.test_mount_pitch(angle, 1, mavutil.mavlink.MAV_MOUNT_MODE_MAVLINK_TARGETING)
self.progress("Levelling aircraft")
self.mav.mav.set_attitude_target_send(
0,
1,
1,
0,
mavextra.euler_to_quat([0, 0, 0]),
0,
0,
0,
0.5)
self.wait_groundspeed(0, 1)
self.progress("Testing mount RC targeting")
self.set_mount_mode(mavutil.mavlink.MAV_MOUNT_MODE_RC_TARGETING)
self.test_mount_rc_targetting(
pitch_rc_neutral=pitch_rc_neutral,
do_rate_tests=do_rate_tests,
)
self.progress("Testing mount ROI behaviour")
self.test_mount_pitch(0, 0.1, mavutil.mavlink.MAV_MOUNT_MODE_RC_TARGETING)
start = self.mav.location()
self.progress("start=%s" % str(start))
(roi_lat, roi_lon) = mavextra.gps_offset(start.lat,
start.lng,
10,
20)
roi_alt = 0
self.progress("Using MAV_CMD_DO_SET_ROI_LOCATION")
self.run_cmd(
mavutil.mavlink.MAV_CMD_DO_SET_ROI_LOCATION,
p5=roi_lat,
p6=roi_lon,
p7=roi_alt,
)
self.test_mount_pitch(-52, 5, mavutil.mavlink.MAV_MOUNT_MODE_GPS_POINT)
self.progress("Using MAV_CMD_DO_SET_ROI_LOCATION")
self.run_cmd(
mavutil.mavlink.MAV_CMD_DO_SET_ROI_LOCATION,
p5=roi_lat + 1,
p6=roi_lon + 1,
p7=roi_alt,
)
self.run_cmd_int(
mavutil.mavlink.MAV_CMD_DO_SET_ROI_LOCATION,
p5=int(roi_lat * 1e7),
p6=int(roi_lon * 1e7),
p7=roi_alt,
frame=mavutil.mavlink.MAV_FRAME_GLOBAL_RELATIVE_ALT,
)
self.test_mount_pitch(-52, 5, mavutil.mavlink.MAV_MOUNT_MODE_GPS_POINT)
self.progress("Using MAV_CMD_DO_SET_ROI_NONE")
self.run_cmd(mavutil.mavlink.MAV_CMD_DO_SET_ROI_NONE)
self.run_cmd_int(mavutil.mavlink.MAV_CMD_DO_SET_ROI_NONE)
self.test_mount_pitch(0, 1, mavutil.mavlink.MAV_MOUNT_MODE_RC_TARGETING)
start = self.mav.location()
(roi_lat, roi_lon) = mavextra.gps_offset(start.lat,
start.lng,
-100,
-200)
roi_alt = 0
self.progress("Using MAV_CMD_DO_SET_ROI")
self.run_cmd(
mavutil.mavlink.MAV_CMD_DO_SET_ROI,
p5=roi_lat,
p6=roi_lon,
p7=roi_alt,
)
self.test_mount_pitch(-7.5, 1, mavutil.mavlink.MAV_MOUNT_MODE_GPS_POINT)
start = self.mav.location()
(roi_lat, roi_lon) = mavextra.gps_offset(start.lat,
start.lng,
-100,
-200)
roi_alt = 0
self.progress("Using MAV_CMD_DO_SET_ROI (COMMAND_INT)")
self.run_cmd_int(
mavutil.mavlink.MAV_CMD_DO_SET_ROI,
0,
0,
0,
0,
int(roi_lat*1e7),
int(roi_lon*1e7),
roi_alt,
frame=mavutil.mavlink.MAV_FRAME_GLOBAL_RELATIVE_ALT_INT,
)
self.test_mount_pitch(-7.5, 1, mavutil.mavlink.MAV_MOUNT_MODE_GPS_POINT)
self.progress("Using MAV_CMD_DO_SET_ROI (COMMAND_INT), absolute-alt-frame")
self.run_cmd_int(
mavutil.mavlink.MAV_CMD_DO_SET_ROI,
0,
0,
0,
0,
int(roi_lat*1e7),
int(roi_lon*1e7),
roi_alt,
frame=mavutil.mavlink.MAV_FRAME_GLOBAL,
)
self.test_mount_pitch(-70, 1, mavutil.mavlink.MAV_MOUNT_MODE_GPS_POINT, hold=2)
self.set_mount_mode(mavutil.mavlink.MAV_MOUNT_MODE_NEUTRAL)
self.test_mount_pitch(0, 0.1, mavutil.mavlink.MAV_MOUNT_MODE_NEUTRAL)
self.progress("Testing mount roi-sysid behaviour")
self.test_mount_pitch(0, 0.1, mavutil.mavlink.MAV_MOUNT_MODE_NEUTRAL)
start = self.mav.location()
self.progress("start=%s" % str(start))
(roi_lat, roi_lon) = mavextra.gps_offset(start.lat,
start.lng,
10,
20)
roi_alt = 0
self.progress("Using MAV_CMD_DO_SET_ROI_SYSID")
self.run_cmd(
mavutil.mavlink.MAV_CMD_DO_SET_ROI_SYSID,
p1=self.mav.source_system,
)
self.mav.mav.global_position_int_send(
0,
int(roi_lat * 1e7),
int(roi_lon * 1e7),
0 * 1000,
0 * 1000,
0,
0,
0,
0
)
self.test_mount_pitch(-89, 5, mavutil.mavlink.MAV_MOUNT_MODE_SYSID_TARGET, hold=2)
self.run_cmd(mavutil.mavlink.MAV_CMD_DO_SET_ROI_NONE)
self.run_cmd_int(
mavutil.mavlink.MAV_CMD_DO_SET_ROI_SYSID,
p1=self.mav.source_system,
)
self.mav.mav.global_position_int_send(
0,
int(roi_lat * 1e7),
int(roi_lon * 1e7),
670 * 1000,
100 * 1000,
0,
0,
0,
0
)
self.test_mount_pitch(
68,
5,
mavutil.mavlink.MAV_MOUNT_MODE_SYSID_TARGET,
hold=2,
constrained=constrain_sysid_target,
)
self.set_mount_mode(mavutil.mavlink.MAV_MOUNT_MODE_NEUTRAL)
self.test_mount_pitch(0, 0.1, mavutil.mavlink.MAV_MOUNT_MODE_NEUTRAL)
self.disarm_vehicle(force=True)
self.test_mount_body_yaw()
def test_mount_body_yaw(self):
'''check reporting of yaw'''
self.takeoff(10, mode='GUIDED')
self.set_mount_mode(mavutil.mavlink.MAV_MOUNT_MODE_NEUTRAL)
for heading in 30, 45, 150:
self.guided_achieve_heading(heading)
r, p , y, yaw_is_absolute = self.get_mount_roll_pitch_yaw_deg()
if yaw_is_absolute:
raise NotAchievedException("Expected a relative yaw")
if y > 1:
raise NotAchievedException("Bad yaw (y=%f)")
self.do_RTL()
def Mount(self):
'''test servo mount'''
self.setup_servo_mount()
self.reboot_sitl()
self.mount_test_body()
def MountPOIFromAuxFunction(self):
'''test we can lock onto a lat/lng/alt with the flick of a switch'''
self.install_terrain_handlers_context()
self.MountPOIFromAuxFunction_Main(enable=1)
self.MountPOIFromAuxFunction_Main(enable=0)
def MountPOIFromAuxFunction_Main(self, enable):
self.setup_servo_mount()
self.progress(f">>>>>>> Testing with TERRAIN_ENABLE = {enable} >>>>>>>>>>>>")
self.set_parameters({
"RC10_OPTION": 186,
"MNT1_RC_RATE": 0,
"TERRAIN_ENABLE": enable,
"SIM_TERRAIN": enable,
})
self.reboot_sitl()
self.wait_ready_to_arm()
self.takeoff(10, mode='GUIDED')
self.set_rc(6, 1000)
self.delay_sim_time(.1)
self.progress("checking mount angles")
mount_roll, mount_pitch, mount_yaw, mount_yaw_is_absolute = self.get_mount_roll_pitch_yaw_deg()
assert -46 <= mount_pitch <= -44, f"Initial Mount Pitch is out of range: {mount_pitch}"
self.set_rc(10, 1900)
self.delay_sim_time(5)
self.set_rc(10, 1500)
self.fly_guided_move_local(100, 100, 70)
WaitAndMaintainAttitude(self, 0, 0, epsilon=1, minimum_duration=1, timeout=5).run()
self.set_rc(10, 1900)
self.delay_sim_time(.1)
mount_roll, mount_pitch, mount_yaw, mount_yaw_is_absolute = self.get_mount_roll_pitch_yaw_deg()
assert (-178 <= mount_yaw <= -176), f"Mount Yaw2 is out of range: {mount_yaw}"
assert -26 <= mount_pitch <= -24, f"Mount Pitch2 is out of range: {mount_pitch}"
self.progress(f"Mount Pitch2 = {mount_pitch}. Mount Yaw2 = {mount_yaw}")
self.set_rc(10, 1500)
self.delay_sim_time(.1)
mount_roll, mount_pitch, mount_yaw, mount_yaw_is_absolute = self.get_mount_roll_pitch_yaw_deg()
assert -46 <= mount_pitch <= -44, f"Mount Pitch3 is out of range: {mount_pitch}"
assert -1 <= mount_yaw <= 1, f"Mount Yaw3 is out of range: {mount_yaw}"
self.progress(f"Mount Pitch3 = {mount_pitch}. Mount Yaw3 = {mount_yaw}")
self.change_mode('RTL')
self.wait_disarmed()
self.assert_at_home()
def MountSolo(self):
'''test type=2, a "Solo" mount'''
self.set_parameters({
"MNT1_TYPE": 2,
"RC6_OPTION": 213,
})
self.customise_SITL_commandline([
"--gimbal"
])
self.mount_test_body(
pitch_rc_neutral=1818,
do_rate_tests=False,
constrain_sysid_target=False,
)
def assert_mount_rpy(self, r, p, y, tolerance=1):
'''assert mount atttiude in degrees'''
got_r, got_p, got_y, yaw_is_absolute = self.get_mount_roll_pitch_yaw_deg()
for (want, got, name) in (r, got_r, "roll"), (p, got_p, "pitch"), (y, got_y, "yaw"):
if abs(want - got) > tolerance:
raise NotAchievedException("%s incorrect; want=%f got=%f" %
(name, want, got))
def neutralise_gimbal(self):
'''put mount into neutralise mode, assert it is at zero angles'''
self.run_cmd(
mavutil.mavlink.MAV_CMD_DO_MOUNT_CONTROL,
p7=mavutil.mavlink.MAV_MOUNT_MODE_NEUTRAL,
)
self.test_mount_pitch(0, 0, mavutil.mavlink.MAV_MOUNT_MODE_RETRACT)
def MAV_CMD_DO_MOUNT_CONTROL(self):
'''test MAV_CMD_DO_MOUNT_CONTROL mavlink command'''
self.context_push()
self.setup_servo_mount()
self.reboot_sitl()
takeoff_loc = self.mav.location()
self.takeoff(20, mode='GUIDED')
self.guided_achieve_heading(315)
self.run_cmd(
mavutil.mavlink.MAV_CMD_DO_MOUNT_CONTROL,
p7=mavutil.mavlink.MAV_MOUNT_MODE_RETRACT,
)
self.run_cmd_int(
mavutil.mavlink.MAV_CMD_DO_MOUNT_CONTROL,
p7=mavutil.mavlink.MAV_MOUNT_MODE_RETRACT,
)
for method in self.run_cmd, self.run_cmd_int:
self.start_subtest("MAV_MOUNT_MODE_GPS_POINT")
self.progress("start=%s" % str(takeoff_loc))
t = self.offset_location_ne(takeoff_loc, 20, 0)
self.progress("targeting=%s" % str(t))
x = int(t.lat * 1e7)
y = int(t.lng * 1e7)
method(
mavutil.mavlink.MAV_CMD_DO_MOUNT_CONTROL,
p4=0,
p5=x,
p6=y,
p7=mavutil.mavlink.MAV_MOUNT_MODE_GPS_POINT,
)
self.test_mount_pitch(-45, 5, mavutil.mavlink.MAV_MOUNT_MODE_GPS_POINT)
self.neutralise_gimbal()
self.start_subtest("MAV_MOUNT_MODE_HOME_LOCATION")
method(
mavutil.mavlink.MAV_CMD_DO_MOUNT_CONTROL,
p7=mavutil.mavlink.MAV_MOUNT_MODE_HOME_LOCATION,
)
self.test_mount_pitch(-90, 5, mavutil.mavlink.MAV_MOUNT_MODE_HOME_LOCATION)
self.neutralise_gimbal()
self.start_subtest("Invalid mode")
method(
mavutil.mavlink.MAV_CMD_DO_MOUNT_CONTROL,
p7=87,
want_result=mavutil.mavlink.MAV_RESULT_FAILED,
)
self.start_subtest("MAV_MOUNT_MODE_MAVLINK_TARGETING")
r = 15
p = 20
y = 30
method(
mavutil.mavlink.MAV_CMD_DO_MOUNT_CONTROL,
p1=p,
p2=r,
p3=y,
p7=mavutil.mavlink.MAV_MOUNT_MODE_MAVLINK_TARGETING,
)
self.delay_sim_time(2)
self.assert_mount_rpy(r, p, y)
self.neutralise_gimbal()
self.start_subtest("MAV_MOUNT_MODE_RC_TARGETING")
method(
mavutil.mavlink.MAV_CMD_DO_MOUNT_CONTROL,
p7=mavutil.mavlink.MAV_MOUNT_MODE_RC_TARGETING,
)
self.test_mount_rc_targetting()
self.start_subtest("MAV_MOUNT_MODE_RETRACT")
self.context_push()
retract_r = 13
retract_p = 23
retract_y = 33
self.set_parameters({
"MNT1_RETRACT_X": retract_r,
"MNT1_RETRACT_Y": retract_p,
"MNT1_RETRACT_Z": retract_y,
})
method(
mavutil.mavlink.MAV_CMD_DO_MOUNT_CONTROL,
p7=mavutil.mavlink.MAV_MOUNT_MODE_RETRACT,
)
self.delay_sim_time(3)
self.assert_mount_rpy(retract_r, retract_p, retract_y)
self.context_pop()
self.do_RTL()
self.context_pop()
self.reboot_sitl()
def AutoYawDO_MOUNT_CONTROL(self):
'''test AutoYaw behaviour when MAV_CMD_DO_MOUNT_CONTROL sent to Mount without Yaw control'''
self.context_push()
yaw_servo = 7
self.setup_servo_mount(roll_servo=5, pitch_servo=6, yaw_servo=yaw_servo)
self.set_parameters({
"SERVO%u_FUNCTION" % yaw_servo: 0,
})
self.reboot_sitl()
self.takeoff(20, mode='GUIDED')
for mount_yaw in [-45, 0, 45]:
heading = 330
self.guided_achieve_heading(heading)
self.assert_heading(heading)
self.neutralise_gimbal()
r = 15
p = 20
self.run_cmd_int(
mavutil.mavlink.MAV_CMD_DO_MOUNT_CONTROL,
p1=p,
p2=r,
p3=mount_yaw,
p7=mavutil.mavlink.MAV_MOUNT_MODE_MAVLINK_TARGETING,
)
self.delay_sim_time(5)
self.assert_mount_rpy(r, p, 0)
self.assert_heading(heading + mount_yaw)
self.do_RTL()
self.context_pop()
self.reboot_sitl()
def MAV_CMD_DO_GIMBAL_MANAGER_CONFIGURE(self):
'''test MAV_CMD_DO_GIMBAL_MANAGER_CONFIGURE mavlink command'''
self.context_push()
self.setup_servo_mount()
self.reboot_sitl()
self.context_set_message_rate_hz('GIMBAL_MANAGER_STATUS', 10)
self.assert_received_message_field_values('GIMBAL_MANAGER_STATUS', {
"gimbal_device_id": 1,
"primary_control_sysid": 0,
"primary_control_compid": 0,
})
for method in self.run_cmd, self.run_cmd_int:
self.start_subtest("set_sysid-compid")
method(
mavutil.mavlink.MAV_CMD_DO_GIMBAL_MANAGER_CONFIGURE,
p1=37,
p2=38,
)
self.assert_received_message_field_values('GIMBAL_MANAGER_STATUS', {
"gimbal_device_id": 1,
"primary_control_sysid": 37,
"primary_control_compid": 38,
})
self.start_subtest("leave unchanged")
method(mavutil.mavlink.MAV_CMD_DO_GIMBAL_MANAGER_CONFIGURE, p1=-1)
self.assert_received_message_field_values('GIMBAL_MANAGER_STATUS', {
"gimbal_device_id": 1,
"primary_control_sysid": 37,
"primary_control_compid": 38,
})
self.start_subtest("release control")
method(
mavutil.mavlink.MAV_CMD_DO_GIMBAL_MANAGER_CONFIGURE,
p1=self.mav.source_system,
p2=self.mav.source_component,
)
self.assert_received_message_field_values('GIMBAL_MANAGER_STATUS', {
"gimbal_device_id": 1,
"primary_control_sysid": self.mav.source_system,
"primary_control_compid": self.mav.source_component,
})
method(mavutil.mavlink.MAV_CMD_DO_GIMBAL_MANAGER_CONFIGURE, p1=-3)
self.assert_received_message_field_values('GIMBAL_MANAGER_STATUS', {
"gimbal_device_id": 1,
"primary_control_sysid": 0,
"primary_control_compid": 0,
})
self.context_pop()
self.reboot_sitl()
def MountYawVehicleForMountROI(self):
'''Test Camera/Antenna Mount vehicle yawing for ROI'''
self.set_parameter("MAV_GCS_SYSID", self.mav.source_system)
yaw_servo = 7
self.setup_servo_mount(yaw_servo=yaw_servo)
self.reboot_sitl()
self.progress("checking ArduCopter yaw-aircraft-for-roi")
self.takeoff(20, mode='GUIDED')
m = self.assert_receive_message('VFR_HUD')
self.progress("current heading %u" % m.heading)
self.set_parameter("SERVO%u_FUNCTION" % yaw_servo, 0)
self.progress("Waiting for check_servo_map to do its job")
self.delay_sim_time(5)
self.progress("Pointing North")
self.guided_achieve_heading(0)
self.delay_sim_time(5)
start = self.mav.location()
(roi_lat, roi_lon) = mavextra.gps_offset(start.lat,
start.lng,
-100,
-100)
roi_alt = 0
self.progress("Using MAV_CMD_DO_SET_ROI")
self.run_cmd(
mavutil.mavlink.MAV_CMD_DO_SET_ROI,
p5=roi_lat,
p6=roi_lon,
p7=roi_alt,
)
self.progress("Waiting for vehicle to point towards ROI")
self.wait_heading(225, timeout=600, minimum_duration=2)
there = mavutil.location(1, 0, 0, 0)
self.progress("Starting to move")
self.mav.mav.set_position_target_global_int_send(
0,
1,
1,
mavutil.mavlink.MAV_FRAME_GLOBAL_RELATIVE_ALT_INT,
MAV_POS_TARGET_TYPE_MASK.POS_ONLY | MAV_POS_TARGET_TYPE_MASK.LAST_BYTE,
there.lat,
there.lng,
there.alt,
0,
0,
0,
0,
0,
0,
0,
0,
)
self.progress("Starting to move changes the target")
bearing = self.bearing_to(there)
self.wait_heading(bearing, timeout=600, minimum_duration=2)
self.run_cmd(
mavutil.mavlink.MAV_CMD_DO_SET_ROI,
p5=roi_lat,
p6=roi_lon,
p7=roi_alt,
)
self.progress("Wait for vehicle to point sssse due to moving")
self.wait_heading(170, timeout=600, minimum_duration=1)
self.do_RTL()
def ThrowMode(self):
'''Fly Throw Mode'''
self.progress("Throwing vehicle away")
self.set_parameters({
"THROW_NEXTMODE": 6,
"SIM_SHOVE_Z": -30,
"SIM_SHOVE_X": -20,
})
self.change_mode('THROW')
self.wait_ready_to_arm()
self.arm_vehicle()
try:
self.set_parameter("SIM_SHOVE_TIME", 500)
except ValueError:
pass
tstart = self.get_sim_time()
self.wait_mode('RTL')
max_good_tdelta = 15
tdelta = self.get_sim_time() - tstart
self.progress("Vehicle in RTL")
self.wait_rtl_complete()
self.progress("Vehicle disarmed")
if tdelta > max_good_tdelta:
raise NotAchievedException("Took too long to enter RTL: %fs > %fs" %
(tdelta, max_good_tdelta))
self.progress("Vehicle returned")
def hover_and_check_matched_frequency_with_fft_and_psd(self, dblevel=-15, minhz=200, maxhz=300, peakhz=None,
reverse=None, takeoff=True, instance=0):
'''Takeoff and hover, checking the noise against the provided db level and returning psd'''
if takeoff:
self.takeoff(10, mode="ALT_HOLD")
tstart, tend, hover_throttle = self.hover_for_interval(15)
self.do_RTL()
psd = self.mavfft_fttd(1, instance, tstart * 1.0e6, tend * 1.0e6)
freq = psd["F"][numpy.argmax(psd["X"][minhz:maxhz]) + minhz] * (1000. / 1024.)
peakdb = numpy.amax(psd["X"][minhz:maxhz])
if peakdb < dblevel or (peakhz is not None and abs(freq - peakhz) / peakhz > 0.05):
if reverse is not None:
self.progress("Did not detect a motor peak, found %fHz at %fdB" % (freq, peakdb))
else:
raise NotAchievedException("Did not detect a motor peak, found %fHz at %fdB" % (freq, peakdb))
else:
if reverse is not None:
raise NotAchievedException(
"Detected motor peak at %fHz, throttle %f%%, %fdB" %
(freq, hover_throttle, peakdb))
else:
self.progress("Detected motor peak at %fHz, throttle %f%%, %fdB" %
(freq, hover_throttle, peakdb))
return freq, hover_throttle, peakdb, psd
def hover_and_check_matched_frequency_with_fft(self, dblevel=-15, minhz=200, maxhz=300, peakhz=None,
reverse=None, takeoff=True, instance=0):
'''Takeoff and hover, checking the noise against the provided db level and returning peak db'''
freq, hover_throttle, peakdb, psd = \
self.hover_and_check_matched_frequency_with_fft_and_psd(dblevel, minhz,
maxhz, peakhz, reverse, takeoff, instance)
return freq, hover_throttle, peakdb
def get_average_esc_frequency(self):
mlog = self.dfreader_for_current_onboard_log()
rpm_total = 0
rpm_count = 0
tho = 0
while True:
m = mlog.recv_match()
if m is None:
break
msg_type = m.get_type()
if msg_type == "CTUN":
tho = m.ThO
elif msg_type == "ESC" and tho > 0.1:
rpm_total += m.RPM
rpm_count += 1
esc_hz = rpm_total / (rpm_count * 60)
return esc_hz
def DynamicNotches(self):
"""Use dynamic harmonic notch to control motor noise."""
self.progress("Flying with dynamic notches")
self.set_parameters({
"AHRS_EKF_TYPE": 10,
"INS_LOG_BAT_MASK": 3,
"INS_LOG_BAT_OPT": 0,
"INS_GYRO_FILTER": 100,
"LOG_BITMASK": 958,
"LOG_DISARMED": 0,
"SIM_VIB_MOT_MAX": 350,
"SIM_GYR1_RND": 20,
})
self.reboot_sitl()
self.takeoff(10, mode="ALT_HOLD")
freq, hover_throttle, peakdb = self.hover_and_check_matched_frequency_with_fft(-15, 200, 300)
self.set_parameters({
"INS_LOG_BAT_OPT": 2,
"INS_HNTCH_ENABLE": 1,
"INS_HNTCH_FREQ": freq,
"INS_HNTCH_REF": hover_throttle/100.,
"INS_HNTCH_HMNCS": 5,
"INS_HNTCH_ATT": 50,
"INS_HNTCH_BW": freq/2,
})
self.reboot_sitl()
freq, hover_throttle, peakdb1 = \
self.hover_and_check_matched_frequency_with_fft(-10, 20, 350, reverse=True)
self.set_parameter("INS_HNTCH_OPTS", 1)
self.reboot_sitl()
freq, hover_throttle, peakdb2 = \
self.hover_and_check_matched_frequency_with_fft(-15, 20, 350, reverse=True)
if peakdb2 * 1.05 > peakdb1:
raise NotAchievedException(
"Double-notch peak was higher than single-notch peak %fdB > %fdB" %
(peakdb2, peakdb1))
self.set_parameter("INS_HNTCH_OPTS", 16)
self.reboot_sitl()
freq, hover_throttle, peakdb2 = \
self.hover_and_check_matched_frequency_with_fft(-15, 20, 350, reverse=True)
if peakdb2 * 1.05 > peakdb1:
raise NotAchievedException(
"Triple-notch peak was higher than single-notch peak %fdB > %fdB" %
(peakdb2, peakdb1))
self.set_parameter("INS_HNTCH_OPTS", 64)
self.reboot_sitl()
freq, hover_throttle, peakdb2 = \
self.hover_and_check_matched_frequency_with_fft(-15, 20, 350, reverse=True)
if peakdb2 * 1.05 > peakdb1:
raise NotAchievedException(
"Quintuple-notch peak was higher than single-notch peak %fdB > %fdB" %
(peakdb2, peakdb1))
def DynamicRpmNotches(self):
"""Use dynamic harmonic notch to control motor noise via ESC telemetry."""
self.progress("Flying with ESC telemetry driven dynamic notches")
self.set_rc_default()
self.set_parameters({
"AHRS_EKF_TYPE": 10,
"INS_LOG_BAT_MASK": 3,
"INS_LOG_BAT_OPT": 0,
"INS_GYRO_FILTER": 300,
"LOG_BITMASK": 958,
"LOG_DISARMED": 0,
"SIM_VIB_MOT_MAX": 350,
"SIM_GYR1_RND": 20,
"SIM_ESC_TELEM": 1
})
self.reboot_sitl()
self.takeoff(10, mode="ALT_HOLD")
freq, hover_throttle, peakdb = self.hover_and_check_matched_frequency_with_fft(-5, 50, 320)
self.set_parameters({
"INS_LOG_BAT_OPT": 4,
"INS_HNTCH_ENABLE": 1,
"INS_HNTCH_FREQ": 80,
"INS_HNTCH_REF": 1.0,
"INS_HNTCH_HMNCS": 5,
"INS_HNTCH_ATT": 50,
"INS_HNTCH_BW": 40,
"INS_HNTCH_MODE": 3,
})
self.reboot_sitl()
freq, hover_throttle, peakdb1, psd = \
self.hover_and_check_matched_frequency_with_fft_and_psd(-10, 50, 320, reverse=True, instance=2)
esc_hz = self.get_average_esc_frequency()
esc_peakdb1 = psd["X"][int(esc_hz)]
self.set_parameter("INS_HNTCH_OPTS", 2)
self.reboot_sitl()
freq, hover_throttle, peakdb2, psd = \
self.hover_and_check_matched_frequency_with_fft_and_psd(-10, 50, 320, reverse=True, instance=2)
esc_hz = self.get_average_esc_frequency()
esc_peakdb2 = psd["X"][int(esc_hz)]
if esc_peakdb2 > esc_peakdb1:
raise NotAchievedException(
"Notch-per-motor peak was higher than single-notch peak %fdB > %fdB" %
(esc_peakdb2, esc_peakdb1))
if esc_peakdb2 > -25:
raise NotAchievedException(
"Notch-per-motor had a peak of %fdB there should be none" % esc_peakdb2)
self.progress("Flying Octacopter with ESC telemetry driven dynamic notches")
self.set_parameter("INS_HNTCH_OPTS", 0)
self.customise_SITL_commandline(
[],
defaults_filepath=','.join(self.model_defaults_filepath("octa")),
model="octa"
)
freq, hover_throttle, peakdb1, psd = \
self.hover_and_check_matched_frequency_with_fft_and_psd(-10, 50, 320, reverse=True, instance=2)
esc_hz = self.get_average_esc_frequency()
esc_peakdb1 = psd["X"][int(esc_hz)]
self.set_parameter("INS_HNTCH_HMNCS", 1)
self.set_parameter("INS_HNTCH_OPTS", 2)
self.reboot_sitl()
freq, hover_throttle, peakdb2, psd = \
self.hover_and_check_matched_frequency_with_fft_and_psd(-15, 50, 320, reverse=True, instance=2)
esc_hz = self.get_average_esc_frequency()
esc_peakdb2 = psd["X"][int(esc_hz)]
if esc_peakdb2 > esc_peakdb1:
raise NotAchievedException(
"Notch-per-motor peak was higher than single-notch peak %fdB > %fdB" %
(esc_peakdb2, esc_peakdb1))
def DynamicRpmNotchesRateThread(self):
"""Use dynamic harmonic notch to control motor noise via ESC telemetry."""
self.progress("Flying with ESC telemetry driven dynamic notches")
self.context_push()
self.set_rc_default()
self.set_parameters({
"AHRS_EKF_TYPE": 10,
"INS_LOG_BAT_MASK": 3,
"INS_LOG_BAT_OPT": 0,
"INS_GYRO_FILTER": 300,
"LOG_BITMASK": 959,
"LOG_DISARMED": 0,
"SIM_VIB_MOT_MAX": 350,
"SIM_GYR1_RND": 20,
"SIM_ESC_TELEM": 1,
"FSTRATE_ENABLE": 1
})
self.reboot_sitl()
self.takeoff(10, mode="ALT_HOLD")
freq, hover_throttle, peakdb = self.hover_and_check_matched_frequency_with_fft(-5, 50, 320)
self.set_parameters({
"INS_LOG_BAT_OPT": 4,
"INS_HNTCH_ENABLE": 1,
"INS_HNTCH_FREQ": 80,
"INS_HNTCH_REF": 1.0,
"INS_HNTCH_HMNCS": 5,
"INS_HNTCH_ATT": 50,
"INS_HNTCH_BW": 40,
"INS_HNTCH_MODE": 3,
"FSTRATE_ENABLE": 1
})
self.reboot_sitl()
freq, hover_throttle, peakdb1, psd = \
self.hover_and_check_matched_frequency_with_fft_and_psd(-10, 50, 320, reverse=True, instance=2)
esc_hz = self.get_average_esc_frequency()
esc_peakdb1 = psd["X"][int(esc_hz)]
self.set_parameter("INS_HNTCH_OPTS", 2)
self.reboot_sitl()
freq, hover_throttle, peakdb2, psd = \
self.hover_and_check_matched_frequency_with_fft_and_psd(-10, 50, 320, reverse=True, instance=2)
esc_hz = self.get_average_esc_frequency()
esc_peakdb2 = psd["X"][int(esc_hz)]
if esc_peakdb2 > esc_peakdb1:
raise NotAchievedException(
"Notch-per-motor peak was higher than single-notch peak %fdB > %fdB" %
(esc_peakdb2, esc_peakdb1))
if esc_peakdb2 > -25:
raise NotAchievedException(
"Notch-per-motor had a peak of %fdB there should be none" % esc_peakdb2)
self.context_pop()
self.reboot_sitl()
def hover_and_check_matched_frequency(self, dblevel=-15, minhz=200, maxhz=300, fftLength=32, peakhz=None):
'''do a simple up-and-down test flight with current vehicle state.
Check that the onboard filter comes up with the same peak-frequency that
post-processing does.'''
self.takeoff(10, mode="ALT_HOLD")
tstart, tend, hover_throttle = self.hover_for_interval(15)
self.do_RTL()
psd = self.mavfft_fttd(1, 0, tstart * 1.0e6, tend * 1.0e6)
scale = 1000. / 1024.
sminhz = int(minhz * scale)
smaxhz = int(maxhz * scale)
freq = psd["F"][numpy.argmax(psd["X"][sminhz:smaxhz]) + sminhz]
peakdb = numpy.amax(psd["X"][sminhz:smaxhz])
self.progress("Post-processing FFT detected motor peak at %fHz/%fdB, throttle %f%%" %
(freq, peakdb, hover_throttle))
if peakdb < dblevel:
raise NotAchievedException(
"Detected motor peak not strong enough; want=%fdB got=%fdB" %
(peakdb, dblevel))
if peakhz is not None and abs(freq - peakhz) / peakhz > 0.05:
raise NotAchievedException(
"Post-processing detected motor peak at wrong frequency; want=%fHz got=%fHz" %
(peakhz, freq))
pkAvg, nmessages = self.extract_median_FTN1_PkAvg_from_current_onboard_log(tstart, tend)
self.progress("Onboard-FFT detected motor peak at %fHz (processed %d FTN1 messages)" % (pkAvg, nmessages))
freqDelta = 1000. / fftLength
if abs(pkAvg - freq) > freqDelta:
raise NotAchievedException(
"post-processed FFT does not agree with onboard filter on peak frequency; onboard=%fHz post-processed=%fHz/%fdB" %
(pkAvg, freq, dblevel)
)
return freq
def extract_median_FTN1_PkAvg_from_current_onboard_log(self, tstart, tend):
'''extracts FTN1 messages from log, returns median of pkAvg values and
the number of samples'''
mlog = self.dfreader_for_current_onboard_log()
freqs = []
while True:
m = mlog.recv_match(
type='FTN1',
blocking=False,
condition="FTN1.TimeUS>%u and FTN1.TimeUS<%u" % (tstart * 1.0e6, tend * 1.0e6))
if m is None:
break
freqs.append(m.PkAvg)
return numpy.median(numpy.asarray(freqs)), len(freqs)
def PIDNotches(self):
"""Use dynamic harmonic notch to control motor noise."""
self.progress("Flying with PID notches")
self.set_parameters({
"FILT1_TYPE": 1,
"AHRS_EKF_TYPE": 10,
"INS_LOG_BAT_MASK": 3,
"INS_LOG_BAT_OPT": 0,
"INS_GYRO_FILTER": 100,
"LOG_BITMASK": 65535,
"LOG_DISARMED": 0,
"SIM_VIB_FREQ_X": 120,
"SIM_VIB_FREQ_Y": 120,
"SIM_VIB_FREQ_Z": 180,
"FILT1_NOTCH_FREQ": 120,
"ATC_RAT_RLL_NEF": 1,
"ATC_RAT_PIT_NEF": 1,
"ATC_RAT_YAW_NEF": 1,
"SIM_GYR1_RND": 5,
})
self.reboot_sitl()
self.hover_and_check_matched_frequency_with_fft(dblevel=5, minhz=20, maxhz=350, reverse=True)
def StaticNotches(self):
"""Use static harmonic notch to control motor noise."""
self.progress("Flying with Static notches")
self.set_parameters({
"AHRS_EKF_TYPE": 10,
"INS_LOG_BAT_MASK": 3,
"INS_LOG_BAT_OPT": 4,
"INS_GYRO_FILTER": 100,
"LOG_BITMASK": 65535,
"LOG_DISARMED": 0,
"SIM_VIB_FREQ_X": 120,
"SIM_VIB_FREQ_Y": 120,
"SIM_VIB_FREQ_Z": 120,
"SIM_VIB_MOT_MULT": 0,
"SIM_GYR1_RND": 5,
"INS_HNTCH_ENABLE": 1,
"INS_HNTCH_FREQ": 120,
"INS_HNTCH_REF": 1.0,
"INS_HNTCH_HMNCS": 3,
"INS_HNTCH_ATT": 50,
"INS_HNTCH_BW": 40,
"INS_HNTCH_MODE": 0,
})
self.reboot_sitl()
self.hover_and_check_matched_frequency_with_fft(dblevel=-15, minhz=20, maxhz=350, reverse=True, instance=2)
def ThrottleGainBoost(self):
"""Use PD and Angle P boost for anti-gravity."""
self.progress("Flying with Throttle-Gain Boost")
self.set_parameters({
"AHRS_EKF_TYPE": 10,
"EK2_ENABLE": 0,
"EK3_ENABLE": 0,
"INS_FAST_SAMPLE": 0,
"LOG_BITMASK": 959,
"LOG_DISARMED": 0,
"ATC_THR_G_BOOST": 5.0,
})
self.reboot_sitl()
self.takeoff(10, mode="ALT_HOLD")
hover_time = 15
self.progress("Hovering for %u seconds" % hover_time)
tstart = self.get_sim_time()
while self.get_sim_time_cached() < tstart + hover_time:
self.assert_receive_message('ATTITUDE')
self.set_rc(3, 1900)
self.set_rc(2, 1200)
self.wait_groundspeed(5, 1000)
self.set_rc(3, 1500)
self.set_rc(2, 1500)
self.do_RTL()
def test_gyro_fft_harmonic(self, averaging):
"""Use dynamic harmonic notch to control motor noise with harmonic matching of the first harmonic."""
self.progress("Flying with gyro FFT harmonic - Gyro sample rate")
self.start_subtest("Hover to calculate approximate hover frequency")
self.set_parameters({
"AHRS_EKF_TYPE": 10,
"EK2_ENABLE": 0,
"EK3_ENABLE": 0,
"INS_LOG_BAT_MASK": 3,
"INS_LOG_BAT_OPT": 0,
"INS_GYRO_FILTER": 100,
"INS_FAST_SAMPLE": 0,
"LOG_BITMASK": 958,
"LOG_DISARMED": 0,
"SIM_DRIFT_SPEED": 0,
"SIM_DRIFT_TIME": 0,
"FFT_THR_REF": self.get_parameter("MOT_THST_HOVER"),
"SIM_GYR1_RND": 20,
})
self.set_parameters({
"FFT_ENABLE": 1,
"FFT_MINHZ": 50,
"FFT_MAXHZ": 450,
"FFT_SNR_REF": 10,
})
if averaging:
self.set_parameter("FFT_NUM_FRAMES", 8)
self.set_parameters({
"SIM_VIB_MOT_MAX": 250,
"FFT_WINDOW_SIZE": 64,
"FFT_WINDOW_OLAP": 0.75,
})
self.reboot_sitl()
freq = self.hover_and_check_matched_frequency(-15, 100, 250, 64)
self.start_subtest("Add a fixed frequency harmonic at twice the hover frequency "
"and check the right harmonic is found")
self.set_parameters({
"SIM_VIB_FREQ_X": freq * 2,
"SIM_VIB_FREQ_Y": freq * 2,
"SIM_VIB_FREQ_Z": freq * 2,
"SIM_VIB_MOT_MULT": 0.25,
})
self.reboot_sitl()
self.hover_and_check_matched_frequency(-15, 100, 250, 64, None)
self.start_subtest("Switch harmonics mid flight and check the right harmonic is found")
self.set_parameter("FFT_HMNC_PEAK", 0)
self.reboot_sitl()
self.takeoff(10, mode="ALT_HOLD")
hover_time = 10
tstart, tend_unused, hover_throttle = self.hover_for_interval(hover_time)
self.progress("Switching motor vibration multiplier")
self.set_parameter("SIM_VIB_MOT_MULT", 5.0)
tstart_unused, tend, hover_throttle = self.hover_for_interval(hover_time)
self.do_RTL()
pkAvg, nmessages = self.extract_median_FTN1_PkAvg_from_current_onboard_log(tstart, tend)
freqDelta = 1000. / self.get_parameter("FFT_WINDOW_SIZE")
if abs(pkAvg - freq) > freqDelta:
raise NotAchievedException("FFT did not detect a harmonic motor peak, found %f, wanted %f" % (pkAvg, freq))
self.start_subtest("Enable dynamic harmonics and make sure both frequency peaks are attenuated")
freq, hover_throttle, peakdb = self.hover_and_check_matched_frequency_with_fft(-15, 100, 350)
self.set_parameters({
"INS_LOG_BAT_OPT": 2,
"INS_HNTCH_ENABLE": 1,
"INS_HNTCH_HMNCS": 1,
"INS_HNTCH_MODE": 4,
"INS_HNTCH_FREQ": freq,
"INS_HNTCH_REF": hover_throttle/100.0,
"INS_HNTCH_ATT": 100,
"INS_HNTCH_BW": freq/2,
"INS_HNTCH_OPTS": 3,
})
self.reboot_sitl()
self.hover_and_check_matched_frequency_with_fft(5, 100, 350, reverse=True)
self.set_parameters({
"SIM_VIB_FREQ_X": 0,
"SIM_VIB_FREQ_Y": 0,
"SIM_VIB_FREQ_Z": 0,
"SIM_VIB_MOT_MULT": 1.0,
})
self.set_parameter("FFT_ENABLE", 0)
def GyroFFTHarmonic(self):
"""Use dynamic harmonic notch to control motor noise with harmonic matching of the first harmonic."""
self.test_gyro_fft_harmonic(False)
def GyroFFTContinuousAveraging(self):
"""Use dynamic harmonic notch with FFT averaging to control motor noise
with harmonic matching of the first harmonic."""
self.test_gyro_fft_harmonic(True)
def GyroFFT(self):
"""Use dynamic harmonic notch to control motor noise."""
self.progress("Flying with gyro FFT - Gyro sample rate")
self.set_parameters({
"AHRS_EKF_TYPE": 10,
"EK2_ENABLE": 0,
"EK3_ENABLE": 0,
"INS_LOG_BAT_MASK": 3,
"INS_LOG_BAT_OPT": 4,
"INS_GYRO_FILTER": 100,
"INS_FAST_SAMPLE": 0,
"LOG_BITMASK": 958,
"LOG_DISARMED": 0,
"SIM_DRIFT_SPEED": 0,
"SIM_DRIFT_TIME": 0,
"SIM_GYR1_RND": 20,
})
self.set_parameters({
"FFT_ENABLE": 1,
"FFT_MINHZ": 50,
"FFT_MAXHZ": 450,
"FFT_SNR_REF": 10,
"FFT_WINDOW_SIZE": 128,
"FFT_WINDOW_OLAP": 0.75,
"FFT_SAMPLE_MODE": 0,
})
self.start_subtest("Inject noise at 250Hz and check the FFT can find the noise")
self.set_parameters({
"SIM_VIB_FREQ_X": 250,
"SIM_VIB_FREQ_Y": 250,
"SIM_VIB_FREQ_Z": 250,
})
self.reboot_sitl()
self.hover_and_check_matched_frequency(-15, 100, 350, 128, 250)
self.set_parameter("FFT_WINDOW_SIZE", 256)
self.start_subtest("Inject noise at 250Hz and check the FFT can find the noise")
self.reboot_sitl()
self.hover_and_check_matched_frequency(-15, 100, 350, 256, 250)
self.set_parameter("FFT_WINDOW_SIZE", 128)
self.start_subtest("Hover and check that the FFT can find the motor noise")
self.set_parameters({
"SIM_VIB_FREQ_X": 0,
"SIM_VIB_FREQ_Y": 0,
"SIM_VIB_FREQ_Z": 0,
"SIM_VIB_MOT_MAX": 250,
"FFT_WINDOW_SIZE": 32,
"FFT_WINDOW_OLAP": 0.5,
})
self.reboot_sitl()
freq = self.hover_and_check_matched_frequency(-15, 100, 250, 32)
self.set_parameter("SIM_VIB_MOT_MULT", 1.)
self.start_subtest("Add a dynamic notch, hover and check that the noise peak is now gone")
self.set_parameters({
"INS_LOG_BAT_OPT": 2,
"INS_HNTCH_ENABLE": 1,
"INS_HNTCH_FREQ": freq,
"INS_HNTCH_REF": 1.0,
"INS_HNTCH_ATT": 50,
"INS_HNTCH_BW": freq/2,
"INS_HNTCH_MODE": 4,
})
self.reboot_sitl()
self.takeoff(10, mode="ALT_HOLD")
tstart, tend, hover_throttle = self.hover_for_interval(15)
self.set_rc(3, 1900)
self.set_rc(2, 1200)
self.wait_groundspeed(5, 1000)
self.set_rc(3, 1500)
self.set_rc(2, 1500)
self.do_RTL()
psd = self.mavfft_fttd(1, 0, tstart * 1.0e6, tend * 1.0e6)
scale = 1000. / 1024.
sminhz = int(100 * scale)
smaxhz = int(350 * scale)
freq = psd["F"][numpy.argmax(psd["X"][sminhz:smaxhz]) + sminhz]
peakdb = numpy.amax(psd["X"][sminhz:smaxhz])
if peakdb < 0:
self.progress("Did not detect a motor peak, found %fHz at %fdB" % (freq, peakdb))
else:
raise NotAchievedException("Detected %fHz motor peak at %fdB" % (freq, peakdb))
self.start_subtest("Hover and check that the FFT can find the motor noise when running at fast loop rate")
self.set_parameters({
"FFT_MAXHZ": 185,
"INS_LOG_BAT_OPT": 4,
"SIM_VIB_MOT_MAX": 220,
"FFT_WINDOW_SIZE": 64,
"FFT_WINDOW_OLAP": 0.75,
"FFT_SAMPLE_MODE": 1,
})
self.reboot_sitl()
self.takeoff(10, mode="ALT_HOLD")
tstart, tend, hover_throttle = self.hover_for_interval(15)
self.do_RTL()
self.set_parameter("FFT_ENABLE", 0)
def GyroFFTAverage(self):
"""Use dynamic harmonic notch to control motor noise setup via FFT averaging."""
self.progress("Flying with gyro FFT harmonic - Gyro sample rate")
self.start_subtest("Hover to calculate approximate hover frequency and see that it is tracked")
self.set_parameters({
"INS_HNTCH_ATT": 100,
"AHRS_EKF_TYPE": 10,
"EK2_ENABLE": 0,
"EK3_ENABLE": 0,
"INS_LOG_BAT_MASK": 3,
"INS_LOG_BAT_OPT": 2,
"INS_GYRO_FILTER": 100,
"INS_FAST_SAMPLE": 0,
"LOG_BITMASK": 958,
"LOG_DISARMED": 0,
"SIM_DRIFT_SPEED": 0,
"SIM_DRIFT_TIME": 0,
"SIM_GYR1_RND": 20,
})
self.set_parameters({
"FFT_ENABLE": 1,
"FFT_WINDOW_SIZE": 64,
"FFT_SNR_REF": 10,
"FFT_MINHZ": 80,
"FFT_MAXHZ": 450,
})
self.set_parameters({
"SIM_VIB_MOT_MAX": 250,
"RC7_OPTION" : 162,
})
self.reboot_sitl()
self.takeoff(10, mode="ALT_HOLD")
hover_time = 60
self.set_rc(7, 2000)
tstart, tend, hover_throttle = self.hover_for_interval(hover_time)
self.set_rc(7, 1000)
psd = self.mavfft_fttd(1, 0, tstart * 1.0e6, tend * 1.0e6)
freq = psd["F"][numpy.argmax(psd["X"][50:450]) + 50] * (1000. / 1024.)
detected_ref = self.get_parameter("INS_HNTCH_REF")
detected_freq = self.get_parameter("INS_HNTCH_FREQ")
self.progress("FFT detected parameters were %fHz, ref %f" % (detected_freq, detected_ref))
scaled_freq_at_hover = math.sqrt((hover_throttle / 100.) / detected_ref) * detected_freq
if abs(scaled_freq_at_hover - freq) / scaled_freq_at_hover > 0.05:
raise NotAchievedException("Detected frequency %fHz did not match required %fHz" %
(scaled_freq_at_hover, freq))
if self.get_parameter("INS_HNTCH_ENABLE") != 1:
raise NotAchievedException("Harmonic notch was not enabled")
self.start_subtest("Verify that noise is suppressed by the harmonic notch")
self.hover_and_check_matched_frequency_with_fft(0, 100, 350, reverse=True, takeoff=False)
self.set_parameters({
"INS_HNTCH_HMNCS": 3.0,
"INS_HNTCH_ENABLE": 0.0,
"INS_HNTCH_REF": 0.0,
"INS_HNTCH_FREQ": 80,
"INS_HNTCH_BW": 40,
"INS_HNTCH_FM_RAT": 1.0
})
self.start_subtest("Add a fixed frequency harmonic at twice the hover frequency "
"and check the right harmonic is found")
self.set_parameters({
"SIM_VIB_FREQ_X": detected_freq * 2,
"SIM_VIB_FREQ_Y": detected_freq * 2,
"SIM_VIB_FREQ_Z": detected_freq * 2,
"SIM_VIB_MOT_MULT": 0.25,
})
self.reboot_sitl()
self.takeoff(10, mode="ALT_HOLD")
hover_time = 60
self.set_rc(7, 2000)
tstart, tend, hover_throttle = self.hover_for_interval(hover_time)
self.set_rc(7, 1000)
self.do_RTL()
detected_ref = self.get_parameter("INS_HNTCH_REF")
detected_freq = self.get_parameter("INS_HNTCH_FREQ")
self.progress("FFT detected parameters were %fHz, ref %f" % (detected_freq, detected_ref))
scaled_freq_at_hover = math.sqrt((hover_throttle / 100.) / detected_ref) * detected_freq
if abs(scaled_freq_at_hover - freq) / scaled_freq_at_hover > 0.05:
raise NotAchievedException("Detected frequency %fHz did not match required %fHz" %
(scaled_freq_at_hover, freq))
if self.get_parameter("INS_HNTCH_ENABLE") != 1:
raise NotAchievedException("Harmonic notch was not enabled")
self.set_parameters({
"SIM_VIB_FREQ_X": 0,
"SIM_VIB_FREQ_Y": 0,
"SIM_VIB_FREQ_Z": 0,
"SIM_VIB_MOT_MULT": 1.0,
"INS_HNTCH_HMNCS": 3.0,
"INS_HNTCH_ENABLE": 0.0,
"INS_HNTCH_REF": 0.0,
"INS_HNTCH_FREQ": 80,
"INS_HNTCH_BW": 40,
"INS_HNTCH_FM_RAT": 1.0
})
self.set_parameter("FFT_ENABLE", 0)
def GyroFFTPostFilter(self):
"""Use FFT-driven dynamic harmonic notch to control post-RPM filter motor noise."""
self.progress("Flying with gyro FFT post-filter suppression - Gyro sample rate")
self.set_parameters({
"AHRS_EKF_TYPE": 10,
"EK2_ENABLE": 0,
"EK3_ENABLE": 0,
"INS_LOG_BAT_MASK": 3,
"INS_LOG_BAT_OPT": 4,
"INS_GYRO_FILTER": 100,
"INS_FAST_SAMPLE": 3,
"LOG_BITMASK": 958,
"LOG_DISARMED": 0,
"SIM_DRIFT_SPEED": 0,
"SIM_DRIFT_TIME": 0,
"SIM_GYR1_RND": 20,
"INS_HNTCH_ENABLE": 1,
"INS_HNTCH_FREQ": 80,
"INS_HNTCH_REF": 1.0,
"INS_HNTCH_HMNCS": 1,
"INS_HNTCH_ATT": 50,
"INS_HNTCH_BW": 30,
"INS_HNTCH_MODE": 3,
"INS_HNTCH_OPTS": 2,
"INS_HNTC2_ENABLE": 1,
"INS_HNTC2_FREQ": 80,
"INS_HNTC2_REF": 1.0,
"INS_HNTC2_HMNCS": 1,
"INS_HNTC2_ATT": 50,
"INS_HNTC2_BW": 40,
"INS_HNTC2_MODE": 4,
"INS_HNTC2_OPTS": 18,
"FFT_ENABLE": 1,
"FFT_WINDOW_SIZE": 64,
"FFT_OPTIONS": 1,
"FFT_MINHZ": 50,
"FFT_MAXHZ": 450,
"SIM_VIB_MOT_MAX": 250,
"SIM_VIB_FREQ_X": 250,
"SIM_VIB_FREQ_Y": 250,
"SIM_VIB_FREQ_Z": 250,
"SIM_GYR_FILE_RW": 2,
})
self.reboot_sitl()
self.takeoff(10, mode="ALT_HOLD")
tstart, tend, hover_throttle = self.hover_for_interval(60)
self.set_rc(3, 1900)
self.set_rc(2, 1200)
self.wait_groundspeed(5, 1000)
self.set_rc(3, 1500)
self.set_rc(2, 1500)
self.do_RTL()
psd = self.mavfft_fttd(1, 2, tstart * 1.0e6, tend * 1.0e6)
scale = 1000. / 1024.
sminhz = int(100 * scale)
smaxhz = int(350 * scale)
freq = psd["F"][numpy.argmax(psd["X"][sminhz:smaxhz]) + sminhz]
peakdb = numpy.amax(psd["X"][sminhz:smaxhz])
if peakdb < -5:
self.progress("Did not detect a motor peak, found %fHz at %fdB" % (freq, peakdb))
else:
raise NotAchievedException("Detected %fHz motor peak at %fdB" % (freq, peakdb))
self.set_parameters({
"SIM_VIB_FREQ_X": 0,
"SIM_VIB_FREQ_Y": 0,
"SIM_VIB_FREQ_Z": 0,
"SIM_VIB_MOT_MULT": 1.0,
"SIM_GYR_FILE_RW": 0,
"FFT_ENABLE": 0,
})
def GyroFFTMotorNoiseCheck(self):
"""Use FFT to detect post-filter motor noise."""
self.progress("Flying with FFT motor-noise detection - Gyro sample rate")
self.set_parameters({
"AHRS_EKF_TYPE": 10,
"EK2_ENABLE": 0,
"EK3_ENABLE": 0,
"INS_LOG_BAT_MASK": 3,
"INS_LOG_BAT_OPT": 4,
"INS_GYRO_FILTER": 100,
"INS_FAST_SAMPLE": 3,
"LOG_BITMASK": 958,
"LOG_DISARMED": 0,
"SIM_DRIFT_SPEED": 0,
"SIM_DRIFT_TIME": 0,
"SIM_GYR1_RND": 200,
"INS_HNTCH_ENABLE": 1,
"INS_HNTCH_FREQ": 80,
"INS_HNTCH_REF": 1.0,
"INS_HNTCH_HMNCS": 1,
"INS_HNTCH_ATT": 50,
"INS_HNTCH_BW": 30,
"INS_HNTCH_MODE": 3,
"INS_HNTCH_OPTS": 2,
"INS_HNTC2_ENABLE": 1,
"INS_HNTC2_FREQ": 80,
"INS_HNTC2_REF": 1.0,
"INS_HNTC2_HMNCS": 1,
"INS_HNTC2_ATT": 50,
"INS_HNTC2_BW": 40,
"INS_HNTC2_MODE": 0,
"INS_HNTC2_OPTS": 16,
"FFT_ENABLE": 1,
"FFT_WINDOW_SIZE": 64,
"FFT_OPTIONS": 3,
"FFT_MINHZ": 50,
"FFT_MAXHZ": 450,
"SIM_VIB_MOT_MAX": 250,
"SIM_VIB_FREQ_X": 250,
"SIM_VIB_FREQ_Y": 250,
"SIM_VIB_FREQ_Z": 250,
"SIM_GYR_FILE_RW": 2,
})
self.reboot_sitl()
self.takeoff(10, mode="ALT_HOLD")
tstart, tend, hover_throttle = self.hover_for_interval(10)
self.wait_statustext("Noise ", timeout=20)
self.set_parameter("SIM_GYR1_RND", 0)
self.do_RTL()
self.set_parameters({
"SIM_VIB_FREQ_X": 0,
"SIM_VIB_FREQ_Y": 0,
"SIM_VIB_FREQ_Z": 0,
"SIM_VIB_MOT_MULT": 1.0,
"SIM_GYR_FILE_RW": 0,
"FFT_ENABLE": 0,
})
def BrakeMode(self):
'''Fly Brake Mode'''
self.progress("Testing brake mode")
self.takeoff(10, mode="LOITER")
self.progress("Ensuring RC inputs have no effect in brake mode")
self.change_mode("STABILIZE")
self.set_rc(3, 1500)
self.set_rc(2, 1200)
self.wait_groundspeed(5, 1000)
self.change_mode("BRAKE")
self.wait_groundspeed(0, 1)
self.set_rc(2, 1500)
self.do_RTL()
self.progress("Ran brake mode")
def fly_guided_move_to(self, destination, timeout=30):
'''move to mavutil.location location; absolute altitude'''
tstart = self.get_sim_time()
self.mav.mav.set_position_target_global_int_send(
0,
1,
1,
mavutil.mavlink.MAV_FRAME_GLOBAL_INT,
MAV_POS_TARGET_TYPE_MASK.POS_ONLY | MAV_POS_TARGET_TYPE_MASK.LAST_BYTE,
int(destination.lat * 1e7),
int(destination.lng * 1e7),
destination.alt,
0,
0,
0,
0,
0,
0,
0,
0,
)
while True:
if self.get_sim_time() - tstart > timeout:
raise NotAchievedException()
delta = self.get_distance(self.mav.location(), destination)
self.progress("delta=%f (want <1)" % delta)
if delta < 1:
break
def AltTypes(self):
'''Test Different Altitude Types'''
'''start by disabling GCS failsafe, otherwise we immediately disarm
due to (apparently) not receiving traffic from the GCS for
too long. This is probably a function of --speedup'''
'''this test flies the vehicle somewhere lower than were it started.
It then disarms. It then arms, which should reset home to the
new, lower altitude. This delta should be outside 1m but
within a few metres of the old one.
'''
self.install_terrain_handlers_context()
self.set_parameter("FS_GCS_ENABLE", 0)
self.change_mode('GUIDED')
self.wait_ready_to_arm()
self.arm_vehicle()
m = self.assert_receive_message('GLOBAL_POSITION_INT')
max_initial_home_alt_m = 500
if m.relative_alt > max_initial_home_alt_m:
raise NotAchievedException("Initial home alt too high (%fm > %fm)" %
(m.relative_alt*1000, max_initial_home_alt_m*1000))
orig_home_offset_mm = m.alt - m.relative_alt
self.user_takeoff(5)
self.progress("Flying to low position")
current_alt = self.mav.location().alt
low_position = mavutil.location(-35.36200016, 149.16415599, current_alt, 0)
self.fly_guided_move_to(low_position, timeout=240)
self.change_mode('LAND')
self.wait_landed_and_disarmed()
self.delay_sim_time(10)
m = self.assert_receive_message('GLOBAL_POSITION_INT')
new_home_offset_mm = m.alt - m.relative_alt
home_offset_delta_mm = orig_home_offset_mm - new_home_offset_mm
self.progress("new home offset: %f delta=%f" %
(new_home_offset_mm, home_offset_delta_mm))
self.progress("gpi=%s" % str(m))
max_home_offset_delta_mm = 10
if home_offset_delta_mm > max_home_offset_delta_mm:
raise NotAchievedException("Large home offset delta: want<%f got=%f" %
(max_home_offset_delta_mm, home_offset_delta_mm))
self.progress("Ensuring home moves when we arm")
self.change_mode('GUIDED')
self.wait_ready_to_arm()
self.arm_vehicle()
m = self.assert_receive_message('GLOBAL_POSITION_INT')
post_arming_home_offset_mm = m.alt - m.relative_alt
self.progress("post-arming home offset: %f" % (post_arming_home_offset_mm))
self.progress("gpi=%s" % str(m))
min_post_arming_home_offset_delta_mm = -2500
max_post_arming_home_offset_delta_mm = -4000
delta_between_original_home_alt_offset_and_new_home_alt_offset_mm = post_arming_home_offset_mm - orig_home_offset_mm
self.progress("delta=%f-%f=%f" % (
post_arming_home_offset_mm,
orig_home_offset_mm,
delta_between_original_home_alt_offset_and_new_home_alt_offset_mm))
self.progress("Home moved %fm vertically" % (delta_between_original_home_alt_offset_and_new_home_alt_offset_mm/1000.0))
if delta_between_original_home_alt_offset_and_new_home_alt_offset_mm > min_post_arming_home_offset_delta_mm:
raise NotAchievedException(
"Home did not move vertically on arming: want<=%f got=%f" %
(min_post_arming_home_offset_delta_mm, delta_between_original_home_alt_offset_and_new_home_alt_offset_mm))
if delta_between_original_home_alt_offset_and_new_home_alt_offset_mm < max_post_arming_home_offset_delta_mm:
raise NotAchievedException(
"Home moved too far vertically on arming: want>=%f got=%f" %
(max_post_arming_home_offset_delta_mm, delta_between_original_home_alt_offset_and_new_home_alt_offset_mm))
self.wait_disarmed()
def PrecisionLoiterCompanion(self):
"""Use Companion PrecLand backend precision messages to loiter."""
self.set_parameters({
"PLND_ENABLED": 1,
"PLND_TYPE": 1,
"RC7_OPTION": 39,
})
self.set_analog_rangefinder_parameters()
self.reboot_sitl()
self.progress("Waiting for location")
self.change_mode('LOITER')
self.wait_ready_to_arm()
start = self.assert_receive_message('LOCAL_POSITION_NED')
self.takeoff(20, mode='ALT_HOLD')
self.set_rc(2, 1550)
self.wait_distance(5, accuracy=1)
self.set_rc(2, 1500)
self.change_mode('LOITER')
self.context_collect('STATUSTEXT')
self.set_rc(7, 2000)
self.precision_loiter_to_pos(start.x + 5, start.y + 10, start.z + 10, True)
self.wait_statustext("PrecLand: Target Found", check_context=True, timeout=10)
self.wait_statustext("PrecLand: Init Complete", check_context=True, timeout=10)
self.precision_loiter_to_pos(start.x + 5, start.y - 10, start.z + 10, True)
self.precision_loiter_to_pos(start.x + 5, start.y - 10, start.z + 10, False)
self.disarm_vehicle(force=True)
def loiter_requires_position(self):
self.progress("Ensure we can't enter LOITER without position")
self.context_push()
self.set_parameters({
"GPS1_TYPE": 2,
"SIM_GPS1_ENABLE": 0,
})
self.set_parameters({
"EK3_SRC1_POSXY": 0,
"EK3_SRC1_VELZ": 0,
"EK3_SRC1_VELXY": 0,
})
self.reboot_sitl()
self.delay_sim_time(30)
ahrs_ekf_type = self.get_parameter("AHRS_EKF_TYPE")
expected_ekf_flags = (mavutil.mavlink.ESTIMATOR_ATTITUDE |
mavutil.mavlink.ESTIMATOR_VELOCITY_VERT |
mavutil.mavlink.ESTIMATOR_POS_VERT_ABS |
mavutil.mavlink.ESTIMATOR_CONST_POS_MODE)
if ahrs_ekf_type == 2:
expected_ekf_flags = expected_ekf_flags | mavutil.mavlink.ESTIMATOR_PRED_POS_HORIZ_REL
self.wait_ekf_flags(expected_ekf_flags, 0, timeout=120)
self.change_mode('STABILIZE')
self.arm_vehicle()
self.context_collect('STATUSTEXT')
self.run_cmd_do_set_mode(
"LOITER",
want_result=mavutil.mavlink.MAV_RESULT_FAILED)
self.wait_statustext("requires position", check_context=True)
self.disarm_vehicle()
self.context_pop()
self.reboot_sitl()
def ArmFeatures(self):
'''Arm features'''
self.loiter_requires_position()
super(AutoTestCopter, self).ArmFeatures()
def ParameterChecks(self):
'''Test Arming Parameter Checks'''
self.test_parameter_checks_poscontrol("PSC")
def PosHoldTakeOff(self):
"""ensure vehicle stays put until it is ready to fly"""
self.context_push()
self.set_parameter("PILOT_TKOFF_ALT", 700)
self.change_mode('POSHOLD')
self.set_rc(3, 1000)
self.wait_ready_to_arm()
self.arm_vehicle()
self.delay_sim_time(2)
relative_alt = self.get_altitude(relative=True)
if relative_alt > 0.1:
raise NotAchievedException("Took off prematurely")
self.progress("Pushing throttle up")
self.set_rc(3, 1710)
self.delay_sim_time(0.5)
self.progress("Bringing back to hover throttle")
self.set_rc(3, 1500)
relative_alt = self.get_altitude(relative=True)
max_initial_alt = 2.0
if abs(relative_alt) > max_initial_alt:
raise NotAchievedException("Took off too fast (%f > %f" %
(relative_alt, max_initial_alt))
self.progress("Monitoring takeoff-to-alt")
self.wait_altitude(6.9, 8, relative=True, minimum_duration=10)
self.progress("takeoff OK")
self.land_and_disarm()
self.set_rc(8, 1000)
self.context_pop()
def initial_mode(self):
return "STABILIZE"
def initial_mode_switch_mode(self):
return "STABILIZE"
def default_mode(self):
return "STABILIZE"
def rc_defaults(self):
ret = super(AutoTestCopter, self).rc_defaults()
ret[3] = 1000
ret[5] = 1800
return ret
def MANUAL_CONTROL(self):
'''test MANUAL_CONTROL mavlink message'''
self.set_parameter("MAV_GCS_SYSID", self.mav.source_system)
self.change_mode('STABILIZE')
self.takeoff(10)
tstart = self.get_sim_time_cached()
want_pitch_degrees = -12
while True:
if self.get_sim_time_cached() - tstart > 10:
raise AutoTestTimeoutException("Did not reach pitch")
self.progress("Sending pitch-forward")
self.mav.mav.manual_control_send(
1,
500,
32767,
32767,
32767,
0)
m = self.assert_receive_message('ATTITUDE', verbose=True)
p = math.degrees(m.pitch)
self.progress("pitch=%f want<=%f" % (p, want_pitch_degrees))
if p <= want_pitch_degrees:
break
self.mav.mav.manual_control_send(
1,
32767,
32767,
32767,
32767,
0)
self.do_RTL()
def check_avoidance_corners(self):
self.takeoff(10, mode="LOITER")
here = self.mav.location()
self.set_rc(2, 1400)
west_loc = mavutil.location(-35.363007,
149.164911,
here.alt,
0)
self.wait_location(west_loc, accuracy=6)
north_loc = mavutil.location(-35.362908,
149.165051,
here.alt,
0)
self.reach_heading_manual(0)
self.wait_location(north_loc, accuracy=6, timeout=200)
self.reach_heading_manual(90)
east_loc = mavutil.location(-35.363013,
149.165194,
here.alt,
0)
self.wait_location(east_loc, accuracy=6)
self.reach_heading_manual(225)
self.wait_location(west_loc, accuracy=6, timeout=200)
self.set_rc(2, 1500)
self.do_RTL()
def OBSTACLE_DISTANCE_3D_test_angle(self, angle):
now = self.get_sim_time_cached()
distance = 15
right = distance * math.sin(math.radians(angle))
front = distance * math.cos(math.radians(angle))
down = 0
expected_distance_cm = distance * 100
expected_orientation = int((angle+22.5)/45) % 8
self.progress("Angle %f expected orient %u" %
(angle, expected_orientation))
tstart = self.get_sim_time()
last_send = 0
m = None
while True:
now = self.get_sim_time_cached()
if now - tstart > 100:
raise NotAchievedException("Did not get correct angle back (last-message=%s)" % str(m))
if now - last_send > 0.1:
self.progress("ang=%f sending front=%f right=%f" %
(angle, front, right))
self.mav.mav.obstacle_distance_3d_send(
int(now*1000),
mavutil.mavlink.MAV_DISTANCE_SENSOR_LASER,
mavutil.mavlink.MAV_FRAME_BODY_FRD,
65535,
front,
right,
down,
0,
20
)
last_send = now
m = self.mav.recv_match(type="DISTANCE_SENSOR",
blocking=True,
timeout=1)
if m is None:
continue
if m.orientation != expected_orientation:
continue
if abs(m.current_distance - expected_distance_cm) > 1:
continue
self.progress("distance-at-angle good")
break
def OBSTACLE_DISTANCE_3D(self):
'''Check round-trip behaviour of distance sensors'''
self.context_push()
self.set_parameters({
"SERIAL5_PROTOCOL": 1,
"PRX1_TYPE": 2,
"SIM_SPEEDUP": 8,
})
self.reboot_sitl()
self.wait_ekf_happy(require_absolute=True)
for angle in range(0, 360):
self.OBSTACLE_DISTANCE_3D_test_angle(angle)
self.context_pop()
self.reboot_sitl()
def AC_Avoidance_Proximity(self):
'''Test proximity avoidance slide behaviour'''
self.context_push()
self.load_fence("copter-avoidance-fence.txt")
self.set_parameters({
"FENCE_ENABLE": 1,
"PRX1_TYPE": 10,
"PRX_LOG_RAW": 1,
"RC10_OPTION": 40,
})
self.reboot_sitl()
self.progress("Enabling proximity")
self.set_rc(10, 2000)
self.check_avoidance_corners()
self.assert_current_onboard_log_contains_message("PRX")
self.assert_current_onboard_log_contains_message("PRXR")
self.disarm_vehicle(force=True)
self.context_pop()
self.reboot_sitl()
def ProximitySensors(self):
'''ensure proximity sensors return appropriate data'''
self.set_parameters({
"SERIAL5_PROTOCOL": 11,
"OA_DB_OUTPUT": 3,
"OA_TYPE": 2,
})
sensors = [
('ld06', 16, {
mavutil.mavlink.MAV_SENSOR_ROTATION_NONE: 275,
mavutil.mavlink.MAV_SENSOR_ROTATION_YAW_45: 256,
mavutil.mavlink.MAV_SENSOR_ROTATION_YAW_90: 1130,
mavutil.mavlink.MAV_SENSOR_ROTATION_YAW_135: 1200,
mavutil.mavlink.MAV_SENSOR_ROTATION_YAW_180: 625,
mavutil.mavlink.MAV_SENSOR_ROTATION_YAW_225: 967,
mavutil.mavlink.MAV_SENSOR_ROTATION_YAW_270: 760,
mavutil.mavlink.MAV_SENSOR_ROTATION_YAW_315: 765,
}),
('sf45b', 8, {
mavutil.mavlink.MAV_SENSOR_ROTATION_NONE: 270,
mavutil.mavlink.MAV_SENSOR_ROTATION_YAW_45: 258,
mavutil.mavlink.MAV_SENSOR_ROTATION_YAW_90: 1146,
mavutil.mavlink.MAV_SENSOR_ROTATION_YAW_135: 632,
mavutil.mavlink.MAV_SENSOR_ROTATION_YAW_180: 629,
mavutil.mavlink.MAV_SENSOR_ROTATION_YAW_225: 972,
mavutil.mavlink.MAV_SENSOR_ROTATION_YAW_270: 774,
mavutil.mavlink.MAV_SENSOR_ROTATION_YAW_315: 774,
}),
('rplidara2', 5, {
mavutil.mavlink.MAV_SENSOR_ROTATION_NONE: 277,
mavutil.mavlink.MAV_SENSOR_ROTATION_YAW_45: 256,
mavutil.mavlink.MAV_SENSOR_ROTATION_YAW_90: 1130,
mavutil.mavlink.MAV_SENSOR_ROTATION_YAW_135: 1288,
mavutil.mavlink.MAV_SENSOR_ROTATION_YAW_180: 626,
mavutil.mavlink.MAV_SENSOR_ROTATION_YAW_225: 970,
mavutil.mavlink.MAV_SENSOR_ROTATION_YAW_270: 762,
mavutil.mavlink.MAV_SENSOR_ROTATION_YAW_315: 790,
}),
('terarangertower', 3, {
mavutil.mavlink.MAV_SENSOR_ROTATION_NONE: 450,
mavutil.mavlink.MAV_SENSOR_ROTATION_YAW_45: 282,
mavutil.mavlink.MAV_SENSOR_ROTATION_YAW_90: 450,
mavutil.mavlink.MAV_SENSOR_ROTATION_YAW_135: 450,
mavutil.mavlink.MAV_SENSOR_ROTATION_YAW_180: 450,
mavutil.mavlink.MAV_SENSOR_ROTATION_YAW_225: 450,
mavutil.mavlink.MAV_SENSOR_ROTATION_YAW_270: 450,
mavutil.mavlink.MAV_SENSOR_ROTATION_YAW_315: 450,
}),
]
home_string = "%s,%s,%s,%s" % (51.8752066, 14.6487840, 54.15, 0)
for (name, prx_type, expected_distances) in sensors:
self.start_subtest("Testing %s" % name)
self.set_parameter("PRX1_TYPE", prx_type)
self.customise_SITL_commandline([
"--serial5=sim:%s:" % name,
"--home", home_string,
])
self.wait_ready_to_arm()
expected_distances_copy = copy.copy(expected_distances)
tstart = self.get_sim_time()
failed = False
wants = []
gots = []
epsilon = 20
while True:
if self.get_sim_time_cached() - tstart > 30:
raise AutoTestTimeoutException("Failed to get distances")
if len(expected_distances_copy.keys()) == 0:
break
m = self.assert_receive_message("DISTANCE_SENSOR")
if m.orientation not in expected_distances_copy:
continue
got = m.current_distance
want = expected_distances_copy[m.orientation]
wants.append(want)
gots.append(got)
if abs(want - got) > epsilon:
failed = True
del expected_distances_copy[m.orientation]
if failed:
raise NotAchievedException(
"Distance too great (%s) (want=%s != got=%s)" %
(name, wants, gots))
def AC_Avoidance_Proximity_AVOID_ALT_MIN(self):
'''Test proximity avoidance with AVOID_ALT_MIN'''
self.set_parameters({
"PRX1_TYPE": 2,
"AVOID_ALT_MIN": 10,
})
self.set_analog_rangefinder_parameters()
self.reboot_sitl()
self.change_mode('LOITER')
self.wait_ekf_happy()
self.set_parameter("SIM_SPEEDUP", 20)
tstart = self.get_sim_time()
while True:
if self.armed(cached=True):
break
if self.get_sim_time_cached() - tstart > 60:
raise AutoTestTimeoutException("Did not arm")
self.mav.mav.distance_sensor_send(
0,
10,
500,
400,
mavutil.mavlink.MAV_DISTANCE_SENSOR_LASER,
26,
mavutil.mavlink.MAV_SENSOR_ROTATION_NONE,
255
)
self.send_mavlink_arm_command()
self.do_timesync_roundtrip()
self.takeoff(15, mode='LOITER')
self.progress("Poking vehicle; should avoid")
def shove(a, b):
self.mav.mav.distance_sensor_send(
0,
10,
500,
20,
mavutil.mavlink.MAV_DISTANCE_SENSOR_LASER,
21,
mavutil.mavlink.MAV_SENSOR_ROTATION_NONE,
255
)
self.wait_speed_vector_bf(
Vector3(-0.4, 0.0, 0.0),
timeout=10,
called_function=shove,
)
self.change_alt(5)
tstart = self.get_sim_time()
while True:
if self.get_sim_time_cached() - tstart > 10:
break
vel = self.get_body_frame_velocity()
if vel.length() > 0.5:
raise NotAchievedException("Moved too much (%s)" %
(str(vel),))
shove(None, None)
self.disarm_vehicle(force=True)
def AC_Avoidance_Fence(self):
'''Test fence avoidance slide behaviour'''
self.load_fence("copter-avoidance-fence.txt")
self.set_parameter("FENCE_ENABLE", 1)
self.check_avoidance_corners()
def AvoidanceAltFence(self):
'''Test fence avoidance at minimum and maximum altitude'''
self.context_push()
self.set_parameters({
"FENCE_ENABLE": 1,
"FENCE_TYPE": 9,
"FENCE_ALT_MIN": 10,
"FENCE_ALT_MAX": 30,
})
self.change_mode('LOITER')
self.wait_ekf_happy()
tstart = self.get_sim_time()
self.takeoff(15, mode='LOITER')
self.progress("Increasing throttle, vehicle should stay below 30m")
self.set_rc(3, 1920)
tstart = self.get_sim_time()
while True:
if self.get_sim_time_cached() - tstart > 20:
break
alt = self.get_altitude(relative=True)
self.progress("Altitude %s" % alt)
if alt > 30:
raise NotAchievedException("Breached maximum altitude (%s)" % (str(alt),))
self.progress("Decreasing, vehicle should stay above 10m")
self.set_rc(3, 1080)
tstart = self.get_sim_time()
while True:
if self.get_sim_time_cached() - tstart > 20:
break
alt = self.get_altitude(relative=True)
self.progress("Altitude %s" % alt)
if alt < 10:
raise NotAchievedException("Breached minimum altitude (%s)" % (str(alt),))
self.context_pop()
self.do_RTL()
def mav_location_from_message_cache(self) -> mavutil.location:
return mavutil.location(
self.mav.messages['GPS_RAW_INT'].lat*1.0e-7,
self.mav.messages['GPS_RAW_INT'].lon*1.0e-7,
self.mav.messages['VFR_HUD'].alt,
self.mav.messages['VFR_HUD'].heading
)
def ModeFollow(self):
'''Fly follow mode'''
foll_ofs_x = 30
self.set_parameters({
"FOLL_ENABLE": 1,
"FOLL_SYSID": self.mav.source_system,
"FOLL_OFS_X": -foll_ofs_x,
"FOLL_OFS_TYPE": 1,
})
self.takeoff(10, mode="LOITER")
self.context_push()
self.set_parameter("SIM_SPEEDUP", 1)
self.change_mode("FOLLOW")
new_loc = self.mav.location()
new_loc_offset_n = 20
new_loc_offset_e = 30
self.location_offset_ne(new_loc, new_loc_offset_n, new_loc_offset_e)
self.progress("new_loc: %s" % str(new_loc))
heading = 0
if self.mavproxy is not None:
self.mavproxy.send("map icon %f %f greenplane %f\n" %
(new_loc.lat, new_loc.lng, heading))
expected_loc = copy.copy(new_loc)
self.location_offset_ne(expected_loc, -foll_ofs_x, 0)
if self.mavproxy is not None:
self.mavproxy.send("map icon %f %f hoop\n" %
(expected_loc.lat, expected_loc.lng))
self.progress("expected_loc: %s" % str(expected_loc))
origin = self.poll_message('GPS_GLOBAL_ORIGIN')
last_sent = 0
tstart = self.get_sim_time()
while True:
now = self.get_sim_time_cached()
if now - tstart > 60:
raise NotAchievedException("Did not FOLLOW")
if now - last_sent > 0.5:
gpi = self.mav.mav.global_position_int_encode(
int(now * 1000),
int(new_loc.lat * 1e7),
int(new_loc.lng * 1e7),
int(new_loc.alt * 1000),
int(new_loc.alt * 1000 - origin.altitude),
vx=0,
vy=0,
vz=0,
hdg=heading
)
gpi.pack(self.mav.mav)
self.mav.mav.send(gpi)
self.assert_receive_message('GLOBAL_POSITION_INT')
pos = self.mav_location_from_message_cache()
delta = self.get_distance(expected_loc, pos)
max_delta = 3
self.progress("position delta=%f (want <%f)" % (delta, max_delta))
if delta < max_delta:
break
self.start_subtest("Trying relative-follow mode")
self.change_mode('LOITER')
self.set_parameter('FOLL_ALT_TYPE', 1)
new_loc = self.mav.location()
new_loc_offset_n = -40
new_loc_offset_e = 60
new_loc.alt += 1
self.location_offset_ne(new_loc, new_loc_offset_n, new_loc_offset_e)
expected_loc = copy.copy(new_loc)
self.location_offset_ne(expected_loc, -foll_ofs_x, 0)
if self.mavproxy is not None:
self.mavproxy.send("map icon %f %f hoop\n" %
(expected_loc.lat, expected_loc.lng))
self.progress("expected_loc: %s" % str(expected_loc))
self.progress("new_loc: %s" % str(new_loc))
self.change_mode('FOLLOW')
last_sent = 0
tstart = self.get_sim_time()
while True:
now = self.get_sim_time_cached()
if now - tstart > 60:
raise NotAchievedException("Did not FOLLOW")
if now - last_sent > 0.5:
gpi = self.mav.mav.global_position_int_encode(
int(now * 1000),
int(new_loc.lat * 1e7),
int(new_loc.lng * 1e7),
666,
int(new_loc.alt * 1000 - origin.altitude),
vx=0,
vy=0,
vz=0,
hdg=heading
)
gpi.pack(self.mav.mav)
self.mav.mav.send(gpi)
self.assert_receive_message('GLOBAL_POSITION_INT')
pos = self.mav_location_from_message_cache()
delta = self.get_distance(expected_loc, pos)
max_delta = 3
self.progress("position delta=%f (want <%f)" % (delta, max_delta))
if delta < max_delta:
break
self.start_subtest("Trying follow-with-velocity mode")
self.change_mode('LOITER')
self.set_parameter('FOLL_ALT_TYPE', 1)
new_loc = self.mav.location()
vel_n = 3
vel_e = 2
vel_d = -1
tstart = self.get_sim_time()
first_loop = True
last_sent = 0
last_loop_s = tstart
heading = math.atan2(vel_n, vel_e)
while True:
now = self.get_sim_time_cached()
dt = now - last_loop_s
last_loop_s = now
new_loc_offset_n = vel_n * dt
new_loc_offset_e = vel_e * dt
new_loc_offset_u = vel_d * dt * -1
self.location_offset_ne(new_loc, new_loc_offset_n, new_loc_offset_e)
new_loc.alt += new_loc_offset_u
expected_loc = copy.copy(new_loc)
self.location_offset_ne(expected_loc, -foll_ofs_x, 0)
self.progress("expected_loc: %s" % str(expected_loc))
self.progress("new_loc: %s" % str(new_loc))
if first_loop:
self.change_mode('FOLLOW')
first_loop = False
if now - tstart > 60:
raise NotAchievedException("Did not FOLLOW")
if now - last_sent > 0.5:
gpi = self.mav.mav.global_position_int_encode(
int(now * 1000),
int(new_loc.lat * 1e7),
int(new_loc.lng * 1e7),
666,
int(new_loc.alt * 1000 - origin.altitude),
vx=int(vel_n*100),
vy=int(vel_e*100),
vz=int(vel_d*100),
hdg=int(math.degrees(heading)*100)
)
gpi.pack(self.mav.mav)
self.mav.mav.send(gpi)
self.assert_receive_message('GLOBAL_POSITION_INT')
pos = self.mav.location()
delta = self.get_distance(expected_loc, pos)
want_delta = foll_ofs_x
self.progress("position delta=%f (want <%f)" % (delta, max_delta))
if abs(want_delta - delta) < 3:
break
self.context_pop()
self.do_RTL()
def ModeFollow_with_FOLLOW_TARGET(self):
'''test ModeFollow passing in FOLLOW_TARGET messages'''
foll_ofs_x = 30
self.set_parameters({
"FOLL_ENABLE": 1,
"FOLL_SYSID": self.mav.source_system,
"FOLL_OFS_X": -foll_ofs_x,
"FOLL_OFS_TYPE": 1,
})
self.takeoff(10, mode="LOITER")
self.context_push()
self.set_parameter("SIM_SPEEDUP", 1)
self.change_mode("FOLLOW")
new_loc = self.mav.location()
new_loc_offset_n = 40
new_loc_offset_e = 60
self.location_offset_ne(new_loc, new_loc_offset_n, new_loc_offset_e)
self.progress("new_loc: %s" % str(new_loc))
heading = 0
if self.mavproxy is not None:
self.mavproxy.send("map icon %f %f greenplane %f\n" %
(new_loc.lat, new_loc.lng, heading))
expected_loc = copy.copy(new_loc)
self.location_offset_ne(expected_loc, -foll_ofs_x, 0)
if self.mavproxy is not None:
self.mavproxy.send("map icon %f %f hoop\n" %
(expected_loc.lat, expected_loc.lng))
self.progress("expected_loc: %s" % str(expected_loc))
def send_target():
vel_n = 3
vel_e = 2
heading = math.atan2(vel_n, vel_e)
attitude = quaternion.Quaternion([
math.radians(0),
math.radians(0),
math.radians(heading)
])
now = self.get_sim_time_cached()
self.mav.mav.follow_target_send(
int(now * 1000),
1 << 0 | 1 << 1 | 1 << 3,
int(new_loc.lat * 1e7),
int(new_loc.lng * 1e7),
new_loc.alt,
[0, 0, 0],
[0, 0, 0],
attitude,
[0, 0, 0],
[0, 0, 0],
0
)
self.wait_location(
expected_loc,
fn=send_target,
fn_interval=0.5,
accuracy=3,
timeout=60,
)
self.start_subtest("Trying follow-with-velocity mode")
self.change_mode('LOITER')
self.set_parameter('FOLL_ALT_TYPE', 1)
new_loc = self.mav.location()
self.change_mode('FOLLOW')
last_loop_s = self.get_sim_time_cached()
vel_n = 3
vel_e = 2
vel_d = 0
mul = 40
self.location_offset_ne(expected_loc, mul*vel_n, mul*vel_e)
def send_target_vel():
nonlocal last_loop_s
now = self.get_sim_time_cached()
dt = now - last_loop_s
last_loop_s = now
new_loc_offset_n = vel_n * dt
new_loc_offset_e = vel_e * dt
new_loc_offset_u = vel_d * dt * -1
self.location_offset_ne(new_loc, new_loc_offset_n, new_loc_offset_e)
new_loc.alt += new_loc_offset_u
heading = math.atan2(vel_n, vel_e)
attitude = quaternion.Quaternion([
math.radians(0),
math.radians(0),
math.radians(heading)
])
now = self.get_sim_time_cached()
self.mav.mav.follow_target_send(
int(now * 1000),
1 << 0 | 1 << 1 | 1 << 3,
int(new_loc.lat * 1e7),
int(new_loc.lng * 1e7),
new_loc.alt,
[vel_n, vel_e, vel_d],
[0, 0, 0],
attitude,
[0, 0, 0],
[0, 0, 0],
0
)
self.wait_speed_vector(
Vector3(vel_n, vel_e, 0),
timeout=100,
called_function=lambda plop, empty: send_target_vel(),
minimum_duration=2,
)
self.context_pop()
self.do_RTL()
def get_global_position_int(self, timeout=30):
tstart = self.get_sim_time()
while True:
if self.get_sim_time_cached() - tstart > timeout:
raise NotAchievedException("Did not get good global_position_int")
m = self.assert_receive_message('GLOBAL_POSITION_INT')
self.progress("GPI: %s" % str(m))
if m is None:
continue
if m.lat != 0 or m.lon != 0:
return m
def BeaconPosition(self):
'''Fly Beacon Position'''
self.reboot_sitl()
self.wait_ready_to_arm(require_absolute=True)
old_pos = self.get_global_position_int()
print("old_pos=%s" % str(old_pos))
self.set_parameters({
"BCN_TYPE": 10,
"BCN_LATITUDE": SITL_START_LOCATION.lat,
"BCN_LONGITUDE": SITL_START_LOCATION.lng,
"BCN_ALT": SITL_START_LOCATION.alt,
"BCN_ORIENT_YAW": 0,
"AVOID_ENABLE": 4,
"GPS1_TYPE": 0,
"EK3_ENABLE": 1,
"EK3_SRC1_POSXY": 4,
"EK3_SRC1_POSZ": 1,
"EK3_SRC1_VELXY": 0,
"EK3_SRC1_VELZ": 0,
"EK2_ENABLE": 0,
"AHRS_EKF_TYPE": 3,
})
self.reboot_sitl()
self.wait_ready_to_arm(require_absolute=False)
tstart = self.get_sim_time()
timeout = 20
while True:
if self.get_sim_time_cached() - tstart > timeout:
raise NotAchievedException("Did not get new position like old position")
self.progress("Fetching location")
new_pos = self.get_global_position_int()
pos_delta = self.get_distance_int(old_pos, new_pos)
max_delta = 1
self.progress("delta=%u want <= %u" % (pos_delta, max_delta))
if pos_delta <= max_delta:
break
self.progress("Moving to ensure location is tracked")
self.takeoff(10, mode="STABILIZE")
self.change_mode("CIRCLE")
self.context_push()
validator = vehicle_test_suite.TestSuite.ValidateGlobalPositionIntAgainstSimState(self, max_allowed_divergence=10)
self.install_message_hook_context(validator)
self.delay_sim_time(20)
self.progress("Tracked location just fine")
self.context_pop()
self.change_mode("LOITER")
self.wait_groundspeed(0, 0.3, timeout=120)
self.land_and_disarm()
self.assert_current_onboard_log_contains_message("BCN")
self.disarm_vehicle(force=True)
def AC_Avoidance_Beacon(self):
'''Test beacon avoidance slide behaviour'''
self.context_push()
ex = None
try:
self.set_parameters({
"BCN_TYPE": 10,
"BCN_LATITUDE": int(SITL_START_LOCATION.lat),
"BCN_LONGITUDE": int(SITL_START_LOCATION.lng),
"BCN_ORIENT_YAW": 45,
"AVOID_ENABLE": 4,
})
self.reboot_sitl()
self.takeoff(10, mode="LOITER")
self.set_rc(2, 1400)
here = self.mav.location()
west_loc = mavutil.location(-35.362919, 149.165055, here.alt, 0)
self.wait_location(west_loc, accuracy=1)
self.reach_heading_manual(0)
north_loc = mavutil.location(-35.362881, 149.165103, here.alt, 0)
self.wait_location(north_loc, accuracy=1)
self.set_rc(2, 1500)
self.set_rc(1, 1600)
east_loc = mavutil.location(-35.362986, 149.165227, here.alt, 0)
self.wait_location(east_loc, accuracy=1)
self.set_rc(1, 1500)
self.set_rc(2, 1600)
south_loc = mavutil.location(-35.363025, 149.165182, here.alt, 0)
self.wait_location(south_loc, accuracy=1)
self.set_rc(2, 1500)
self.do_RTL()
except Exception as e:
self.print_exception_caught(e)
ex = e
self.context_pop()
self.clear_fence()
self.disarm_vehicle(force=True)
self.reboot_sitl()
if ex is not None:
raise ex
def BaroWindCorrection(self):
'''Test wind estimation and baro position error compensation'''
self.customise_SITL_commandline(
[],
defaults_filepath=self.model_defaults_filepath('Callisto'),
model="octa-quad:@ROMFS/models/Callisto.json",
wipe=True,
)
wind_spd_truth = 8.0
wind_dir_truth = 90.0
self.set_parameters({
"EK3_ENABLE": 1,
"EK2_ENABLE": 0,
"AHRS_EKF_TYPE": 3,
"BARO1_WCF_ENABLE": 1.000000,
})
self.reboot_sitl()
self.set_parameters({
"BARO1_WCF_FWD": -0.300000,
"BARO1_WCF_BCK": -0.300000,
"BARO1_WCF_RGT": 0.300000,
"BARO1_WCF_LFT": 0.300000,
"BARO1_WCF_UP": 0.300000,
"BARO1_WCF_DN": 0.300000,
"SIM_BARO_WCF_FWD": -0.300000,
"SIM_BARO_WCF_BAK": -0.300000,
"SIM_BARO_WCF_RGT": 0.300000,
"SIM_BARO_WCF_LFT": 0.300000,
"SIM_BARO_WCF_UP": 0.300000,
"SIM_BARO_WCF_DN": 0.300000,
"SIM_WIND_DIR": wind_dir_truth,
"SIM_WIND_SPD": wind_spd_truth,
"SIM_WIND_T": 1.000000,
})
self.reboot_sitl()
self.wait_ready_to_arm(require_absolute=False)
self.progress("Climb to 20m in LOITER and yaw spin for 30 seconds")
self.takeoff(10, mode="LOITER")
self.set_rc(4, 1400)
self.delay_sim_time(30)
m = self.assert_receive_message('WIND')
speed_error = abs(m.speed - wind_spd_truth)
angle_error = abs(m.direction - wind_dir_truth)
if (speed_error > 1.0):
raise NotAchievedException("Wind speed incorrect - want %f +-1 got %f m/s" % (wind_spd_truth, m.speed))
if (angle_error > 15.0):
raise NotAchievedException(
"Wind direction incorrect - want %f +-15 got %f deg" %
(wind_dir_truth, m.direction))
self.progress("Wind estimate is good, now check height variation for 30 seconds")
z_min = 1E6
z_max = -1E6
tstart = self.get_sim_time()
while (self.get_sim_time() < tstart + 30):
m = self.assert_receive_message('LOCAL_POSITION_NED')
if (m.z > z_max):
z_max = m.z
if (m.z < z_min):
z_min = m.z
if (z_max-z_min > 0.5):
raise NotAchievedException("Height variation is excessive")
self.progress("Height variation is good")
self.set_rc(4, 1500)
self.land_and_disarm()
def wait_generator_speed_and_state(self, rpm_min, rpm_max, want_state, timeout=240):
tstart = self.get_sim_time()
while True:
if self.get_sim_time_cached() - tstart > timeout:
raise NotAchievedException("Did not move to state/speed")
m = self.assert_receive_message("GENERATOR_STATUS", timeout=10)
if m.generator_speed < rpm_min:
self.progress("Too slow (%u<%u)" % (m.generator_speed, rpm_min))
continue
if m.generator_speed > rpm_max:
self.progress("Too fast (%u>%u)" % (m.generator_speed, rpm_max))
continue
if m.status != want_state:
self.progress("Wrong state (got=%u want=%u)" % (m.status, want_state))
break
self.progress("Got generator speed and state")
def RichenPower(self):
'''Test RichenPower generator'''
self.set_parameters({
"SERIAL5_PROTOCOL": 30,
"SIM_RICH_ENABLE": 1,
"SERVO8_FUNCTION": 42,
"SIM_RICH_CTRL": 8,
"RC9_OPTION": 85,
"LOG_DISARMED": 1,
"BATT2_MONITOR": 17,
"GEN_TYPE": 3,
})
self.reboot_sitl()
self.set_rc(9, 1000)
self.customise_SITL_commandline(["--serial5=sim:richenpower"])
self.wait_statustext("requested state is not RUN", timeout=60)
self.set_message_rate_hz("GENERATOR_STATUS", 10)
self.wait_generator_speed_and_state(0, 0, mavutil.mavlink.MAV_GENERATOR_STATUS_FLAG_OFF)
self.context_collect('STATUSTEXT')
self.set_rc(9, 2000)
self.wait_statustext("Generator HIGH", check_context=True)
self.set_rc(9, 1000)
self.wait_statustext("requested state is not RUN", timeout=200)
self.set_rc(9, 1500)
self.wait_generator_speed_and_state(3000, 8000, mavutil.mavlink.MAV_GENERATOR_STATUS_FLAG_IDLE)
self.set_rc(9, 2000)
self.wait_generator_speed_and_state(8000, 30000, mavutil.mavlink.MAV_GENERATOR_STATUS_FLAG_GENERATING)
bs = self.assert_receive_message(
"BATTERY_STATUS",
condition="BATTERY_STATUS.id==1",
)
self.progress("Received battery status: %s" % str(bs))
want_bs_volt = 50000
if bs.voltages[0] != want_bs_volt:
raise NotAchievedException("Battery voltage not as expected (want=%f) got=(%f)" % (want_bs_volt, bs.voltages[0],))
self.progress("Moving *back* to idle")
self.set_rc(9, 1500)
self.wait_generator_speed_and_state(3000, 10000, mavutil.mavlink.MAV_GENERATOR_STATUS_FLAG_IDLE)
self.progress("Moving *back* to run")
self.set_rc(9, 2000)
self.wait_generator_speed_and_state(8000, 30000, mavutil.mavlink.MAV_GENERATOR_STATUS_FLAG_GENERATING)
self.set_message_rate_hz("GENERATOR_STATUS", -1)
self.set_parameter("LOG_DISARMED", 0)
if not self.current_onboard_log_contains_message("RICH"):
raise NotAchievedException("Did not find expected RICH message")
def IE24(self):
'''Test IntelligentEnergy 2.4kWh generator with V1 and V2 telemetry protocols'''
protocol_ver = (1, 2)
for ver in protocol_ver:
self.start_subtest(f"IE24 general tests for protocol {ver}")
self.context_push()
self.run_IE24(ver)
self.context_pop()
self.reboot_sitl()
self.start_subtest(f"IE24 low capacity failsafe test for protocol {ver}")
self.context_push()
self.test_IE24_low_capacity_failsafe(ver)
self.context_pop()
self.reboot_sitl()
def run_IE24(self, proto_ver):
'''Test IntelligentEnergy 2.4kWh generator'''
elec_battery_instance = 2
fuel_battery_instance = 1
self.set_parameters({
"SERIAL5_PROTOCOL": 30,
"SERIAL5_BAUD": 115200,
"GEN_TYPE": 2,
"BATT%u_MONITOR" % (fuel_battery_instance + 1): 18,
"BATT%u_MONITOR" % (elec_battery_instance + 1): 17,
"SIM_IE24_ENABLE": proto_ver,
"LOG_DISARMED": 1,
})
self.customise_SITL_commandline(["--serial5=sim:ie24"])
self.start_subtest("Protocol %i: ensure that BATTERY_STATUS for electrical generator message looks right" % proto_ver)
self.start_subsubtest("Protocol %i: Checking original voltage (electrical)" % proto_ver)
self.delay_sim_time(30)
original_elec_m = self.wait_message_field_values('BATTERY_STATUS', {
"charge_state": mavutil.mavlink.MAV_BATTERY_CHARGE_STATE_OK
}, instance=elec_battery_instance)
original_fuel_m = self.wait_message_field_values('BATTERY_STATUS', {
"charge_state": mavutil.mavlink.MAV_BATTERY_CHARGE_STATE_OK
}, instance=fuel_battery_instance)
if original_elec_m.battery_remaining < 90:
raise NotAchievedException("Bad original percentage")
self.start_subsubtest("Ensure percentage is counting down")
self.wait_message_field_values('BATTERY_STATUS', {
"battery_remaining": original_elec_m.battery_remaining - 1,
}, instance=elec_battery_instance)
self.start_subtest("Protocol %i: ensure that BATTERY_STATUS for fuel generator message looks right" % proto_ver)
self.start_subsubtest("Protocol %i: Checking original voltage (fuel)" % proto_ver)
if original_fuel_m.battery_remaining <= 90:
raise NotAchievedException("Bad original percentage (want=>%f got %f" % (90, original_fuel_m.battery_remaining))
self.start_subsubtest("Protocol %i: Ensure percentage is counting down" % proto_ver)
self.wait_message_field_values('BATTERY_STATUS', {
"battery_remaining": original_fuel_m.battery_remaining - 1,
}, instance=fuel_battery_instance)
self.wait_ready_to_arm()
self.arm_vehicle()
self.disarm_vehicle()
self.start_subtest("Protocol %i: Without takeoff generator error should cause failsafe and disarm" % proto_ver)
self.set_parameter("SIM_IE24_STATE", 8)
self.wait_statustext("Status not running", timeout=40)
self.try_arm(result=False,
expect_msg="Status not running")
self.set_parameter("SIM_IE24_STATE", 2)
self.start_subtest("Protocol %i: Without taken off generator error should cause failsafe and disarm" % proto_ver)
self.change_mode("STABILIZE")
self.set_parameter("DISARM_DELAY", 0)
self.arm_vehicle()
self.set_parameter("SIM_IE24_ERROR", 30)
self.disarm_wait(timeout=1)
self.set_parameter("SIM_IE24_ERROR", 0)
self.set_parameter("DISARM_DELAY", 10)
def test_IE24_low_capacity_failsafe(self, proto_ver):
elec_battery_instance = 2
fuel_battery_instance = 1
self.set_parameters({
"SERIAL5_PROTOCOL": 30,
"SERIAL5_BAUD": 115200,
"GEN_TYPE": 2,
"BATT%u_MONITOR" % (fuel_battery_instance + 1): 18,
"BATT%u_MONITOR" % (elec_battery_instance + 1): 17,
"SIM_IE24_ENABLE": proto_ver,
"LOG_DISARMED": 1,
})
self.customise_SITL_commandline(["--serial5=sim:ie24"])
self.change_mode('GUIDED')
self.wait_ready_to_arm()
original_fuel_m = self.wait_message_field_values('BATTERY_STATUS', {
"charge_state": mavutil.mavlink.MAV_BATTERY_CHARGE_STATE_OK
}, instance=fuel_battery_instance)
self.set_parameters({
"BATT%u_CAPACITY" % (fuel_battery_instance + 1): 100,
"BATT%u_LOW_MAH" % (fuel_battery_instance + 1): original_fuel_m.battery_remaining - 5,
"BATT%u_FS_LOW_ACT" % (fuel_battery_instance + 1): 1,
})
self.takeoff(mode='GUIDED')
self.wait_mode('LAND')
self.wait_disarmed()
self.set_rc(3, 1000)
def LoweheiserAuto(self):
'''Ensure the Loweheiser generator works as expected in auto-starter mode.'''
gen_ctrl_ch = 9
self.set_parameters({
"SERIAL5_PROTOCOL": 2,
"MAV3_OPTIONS": 2,
"GEN_TYPE": 4,
"EFI_TYPE": 4,
"SIM_EFI_TYPE": 2,
"BATT2_MONITOR": 17,
"BATT3_MONITOR": 18,
"BATT3_CAPACITY": 10000,
f"RC{gen_ctrl_ch}_OPTION": 85,
"LOG_DISARMED": 1
})
self.set_rc_from_map({
gen_ctrl_ch: 1000,
})
self.customise_SITL_commandline(["--serial5=sim:loweheiser"])
self.set_parameters({
"GEN_L_IDLE_TH": 25,
"GEN_L_IDLE_TH_H": 40,
"GEN_L_RUN_TEMP": 60,
"GEN_L_IDLE_TEMP": 80,
})
self.reboot_sitl()
self.assert_parameter_value("GEN_L_IDLE_TH", 25)
self.delay_sim_time(10)
self.start_subtest("Checking GENERATOR_STATUS while OFF.")
self.set_message_rate_hz("GENERATOR_STATUS", 10)
self.delay_sim_time(1)
self.wait_generator_speed_and_state(0, 0, mavutil.mavlink.MAV_GENERATOR_STATUS_FLAG_OFF)
self.start_subtest("Checking EFI_STATUS while OFF.")
self.set_message_rate_hz("EFI_STATUS", 10)
self.delay_sim_time(1)
self.assert_received_message_field_values('EFI_STATUS', {
"health": 1,
"ecu_index": 1.0,
"rpm": 0.0,
"fuel_consumed": 0,
"fuel_flow": float("nan"),
"engine_load": 0.0,
"throttle_position": 0.0,
"spark_dwell_time": 0.0,
"barometric_pressure": 0.0,
"intake_manifold_pressure": float("nan"),
"intake_manifold_temperature": float("nan"),
"cylinder_head_temperature": float("nan"),
"ignition_timing": 0.0,
"injection_time": float("nan"),
"exhaust_gas_temperature": float("nan"),
"throttle_out": 0.0,
"pt_compensation": 0.0,
"ignition_voltage": 0,
}, epsilon=1.0)
self.assert_received_message_field_values('GENERATOR_STATUS', {
"status": 1,
"generator_speed": 0,
"battery_current": -0.30000001192092896,
"load_current": 10.119999885559082,
"power_generated": 521.0,
"bus_voltage": 50,
"rectifier_temperature": 32767,
"bat_current_setpoint": float("nan"),
"generator_temperature": 32767,
"runtime": 0,
"time_until_maintenance": 300*60*60,
})
self.context_collect('STATUSTEXT')
self.try_arm(result=False, expect_msg="requested state is not RUN")
self.start_subtest("Setting generator to IDLE state.")
self.set_rc(gen_ctrl_ch, 1500)
self.wait_statustext("Generator MIDDLE", check_context=True)
self.delay_sim_time(2)
self.drain_mav()
self.assert_received_message_field_values('EFI_STATUS', {
"intake_manifold_pressure": 94,
"exhaust_gas_temperature": float("nan"),
"ignition_voltage": 12,
"throttle_position": 40,
}, epsilon=1.0)
self.wait_message_field_values('EFI_STATUS', {
"cylinder_head_temperature": 20,
}, epsilon=5.0, timeout=10)
self.assert_received_message_field_values('GENERATOR_STATUS', {
"battery_current": -0.30000001192092896,
"load_current": 10.119999885559082,
"power_generated": 521.0,
"bus_voltage": 50,
"rectifier_temperature": 32767,
"bat_current_setpoint": float("nan"),
"generator_temperature": 32767,
"runtime": 2,
"time_until_maintenance": 300*60*60 - 2,
})
self.wait_generator_speed_and_state(2000, 3000, mavutil.mavlink.MAV_GENERATOR_STATUS_FLAG_IDLE)
self.start_subtest("Setting generator to RUN state.")
self.set_rc(gen_ctrl_ch, 2000)
self.wait_statustext("Generator HIGH", check_context=True)
self.try_arm(result=False, expect_msg="Generator warming up")
self.set_rc(gen_ctrl_ch, 1000)
self.wait_statustext("requested state is not RUN", timeout=200)
self.set_rc(gen_ctrl_ch, 1500)
self.wait_generator_speed_and_state(2000, 3000, mavutil.mavlink.MAV_GENERATOR_STATUS_FLAG_IDLE)
self.set_rc(gen_ctrl_ch, 2000)
self.wait_generator_speed_and_state(8000, 30000, mavutil.mavlink.MAV_GENERATOR_STATUS_FLAG_GENERATING)
bs = self.assert_receive_message(
"BATTERY_STATUS",
condition="BATTERY_STATUS.id==1",
timeout=1,
very_verbose=True,
)
if bs is None:
raise NotAchievedException("Did not receive BATTERY_STATUS")
self.progress("Received battery status: %s" % str(bs))
want_bs_volt = 50000
if bs.voltages[0] != want_bs_volt:
raise NotAchievedException("Battery voltage not as expected (want=%f) got=(%f)" % (want_bs_volt, bs.voltages[0],))
self.progress("Checking battery remaining")
bs = self.assert_receive_message(
"BATTERY_STATUS",
condition="BATTERY_STATUS.id==2",
timeout=1,
very_verbose=True,
)
self.progress("Waiting for some fuel to be consumed...")
self.wait_message_field_values("BATTERY_STATUS", {
"id": 2,
"battery_remaining": bs.battery_remaining-1,
}, timeout=100)
bs2 = self.assert_receive_message(
"BATTERY_STATUS",
condition="BATTERY_STATUS.id==2",
timeout=1,
very_verbose=True,
)
if bs2.battery_remaining >= bs.battery_remaining:
raise NotAchievedException("Expected battery remaining to drop")
if bs2.current_consumed <= bs.current_consumed:
raise NotAchievedException("Expected energy consumed to rise")
self.progress("Checking battery reset")
batt_reset_values = [(25, 24),
(50, 49),
(63, 62),
(87, 86),
(100, 99)]
for (reset_val, return_val) in batt_reset_values:
self.run_cmd(mavutil.mavlink.MAV_CMD_BATTERY_RESET,
(1 << 2),
reset_val,
0,
0,
0,
0,
0
)
self.wait_message_field_values("BATTERY_STATUS", {
"id": 2,
"battery_remaining": return_val,
}, timeout=5)
self.progress("Moving *back* to idle")
self.set_rc(gen_ctrl_ch, 1500)
self.wait_generator_speed_and_state(3000, 10000, mavutil.mavlink.MAV_GENERATOR_STATUS_FLAG_IDLE)
self.progress("Moving *back* to run")
self.set_rc(gen_ctrl_ch, 2000)
self.wait_generator_speed_and_state(8000, 30000, mavutil.mavlink.MAV_GENERATOR_STATUS_FLAG_GENERATING)
self.set_parameter("LOG_DISARMED", 0)
if not self.current_onboard_log_contains_message("LOEG"):
raise NotAchievedException("Did not find expected LOEG message in .bin log")
if not self.current_onboard_log_contains_message("LOEC"):
raise NotAchievedException("Did not find expected LOEC message in .bin log")
self.set_parameter("LOG_DISARMED", 1)
self.progress("Stopping generator")
self.set_rc(gen_ctrl_ch, 1000)
self.wait_statustext("Cooling down")
self.wait_generator_speed_and_state(0, 0, mavutil.mavlink.MAV_GENERATOR_STATUS_FLAG_OFF)
self.start_subtest("Checking safety switch estop")
self.set_rc(gen_ctrl_ch, 2000)
self.wait_generator_speed_and_state(8000, 30000, mavutil.mavlink.MAV_GENERATOR_STATUS_FLAG_GENERATING)
self.set_safetyswitch_on()
self.wait_generator_speed_and_state(0, 0, mavutil.mavlink.MAV_GENERATOR_STATUS_FLAG_OFF)
self.wait_message_field_values('EFI_STATUS', {
"throttle_position": 0,
"rpm": 0,
})
self.set_safetyswitch_off()
self.wait_message_field_values('EFI_STATUS', {
"throttle_position": 80,
"rpm": 8000,
}, timeout=20)
self.set_rc(gen_ctrl_ch, 1000)
self.wait_generator_speed_and_state(0, 0, mavutil.mavlink.MAV_GENERATOR_STATUS_FLAG_OFF)
self.start_subtest("Battery Failsafes")
self.context_push()
batt3_capacity = 500
batt3_low_mah = 100
batt3_low_consumed_mah = batt3_capacity - batt3_low_mah
self.set_parameters({
"BATT3_CAPACITY": batt3_capacity,
"BATT3_LOW_MAH": batt3_low_mah,
"BATT3_CRT_MAH": 50,
"BATT3_FS_LOW_ACT": 2,
"BATT3_FS_CRT_ACT": 1,
"BATT3_LOW_VOLT": 0,
})
self.reboot_sitl()
self.set_rc(gen_ctrl_ch, 2000)
self.takeoff(10, mode='GUIDED')
first_efi_status = self.assert_receive_message('EFI_STATUS', verbose=True)
if first_efi_status.fuel_consumed < 100:
raise NotAchievedException("Unexpected fuel consumed value after takeoff (%f)" % first_efi_status.fuel_consumed)
self.fly_guided_move_local(100, 100, 20)
self.wait_mode('RTL', timeout=300)
second_efi_status = self.assert_receive_message('EFI_STATUS', verbose=True)
if second_efi_status.fuel_consumed < batt3_low_consumed_mah:
raise NotAchievedException("Unexpected fuel consumed value after failsafe (%f)" % second_efi_status.fuel_consumed)
self.wait_mode('LAND', timeout=300)
self.wait_disarmed()
self.context_pop()
self.reboot_sitl()
self.set_message_rate_hz("EFI_STATUS", -1)
self.set_message_rate_hz("GENERATOR_STATUS", -1)
def LoweheiserManual(self):
'''Ensure the Loweheiser generator works as expected in manual starter mode.'''
gen_ctrl_ch = 9
loweheiser_man_throt_ch = 10
loweheiser_man_start_ch = 11
self.set_parameters({
"SERIAL5_PROTOCOL": 2,
"MAV3_OPTIONS": 2,
"GEN_TYPE": 4,
"EFI_TYPE": 4,
"SIM_EFI_TYPE": 2,
"BATT2_MONITOR": 17,
"BATT3_MONITOR": 18,
"BATT3_CAPACITY": 10000,
f"RC{gen_ctrl_ch}_OPTION": 85,
"LOG_DISARMED": 1,
"RC%u_OPTION" % loweheiser_man_throt_ch: 218,
"RC%u_DZ" % loweheiser_man_throt_ch: 20,
"RC%u_TRIM" % loweheiser_man_throt_ch: 1000,
"RC%u_MIN" % loweheiser_man_throt_ch: 1000,
"RC%u_MAX" % loweheiser_man_throt_ch: 2000,
"RC%u_OPTION" % loweheiser_man_start_ch: 111,
})
self.set_rc_from_map({
gen_ctrl_ch: 1000,
loweheiser_man_throt_ch: 1000,
loweheiser_man_start_ch: 1000,
})
self.customise_SITL_commandline(["--serial5=sim:loweheiser"])
self.set_parameters({
"GEN_L_IDLE_TH": 25,
"GEN_L_IDLE_TH_H": 40,
"GEN_L_RUN_TEMP": 60,
"GEN_L_IDLE_TEMP": 80,
})
self.reboot_sitl()
self.assert_parameter_value("GEN_L_IDLE_TH", 25)
self.delay_sim_time(10)
self.start_subtest("Checking GENERATOR_STATUS while OFF.")
self.set_message_rate_hz("GENERATOR_STATUS", 10)
self.delay_sim_time(1)
self.wait_generator_speed_and_state(0, 0, mavutil.mavlink.MAV_GENERATOR_STATUS_FLAG_OFF)
self.start_subtest("Checking EFI_STATUS while OFF.")
self.set_message_rate_hz("EFI_STATUS", 10)
self.delay_sim_time(1)
self.assert_received_message_field_values('EFI_STATUS', {
"health": 1,
"ecu_index": 1.0,
"rpm": 0.0,
"fuel_consumed": 0,
"fuel_flow": float("nan"),
"engine_load": 0.0,
"throttle_position": 0.0,
"spark_dwell_time": 0.0,
"barometric_pressure": 0.0,
"intake_manifold_pressure": float("nan"),
"intake_manifold_temperature": float("nan"),
"cylinder_head_temperature": float("nan"),
"ignition_timing": 0.0,
"injection_time": float("nan"),
"exhaust_gas_temperature": float("nan"),
"throttle_out": 0.0,
"pt_compensation": 0.0,
"ignition_voltage": 0,
}, epsilon=1.0)
self.assert_received_message_field_values('GENERATOR_STATUS', {
"status": 1,
"generator_speed": 0,
"battery_current": -0.30000001192092896,
"load_current": 10.119999885559082,
"power_generated": 521.0,
"bus_voltage": 50,
"rectifier_temperature": 32767,
"bat_current_setpoint": float("nan"),
"generator_temperature": 32767,
"runtime": 0,
"time_until_maintenance": 300*60*60,
})
rc_dz = self.get_parameter('RC%u_DZ' % loweheiser_man_throt_ch)
rc_trim = int(self.get_parameter('RC%u_TRIM' % loweheiser_man_throt_ch))
rc_min = self.get_parameter('RC%u_MIN' % loweheiser_man_throt_ch)
rc_max = int(self.get_parameter('RC%u_MAX' % loweheiser_man_throt_ch))
self.progress("Ensuring 1000 and rc_trim all leave throttle out 0")
for i in 1000, rc_trim:
self.progress("Checking %u pwm" % i)
self.set_rc(loweheiser_man_throt_ch, i)
self.delay_sim_time(1)
self.assert_received_message_field_values('EFI_STATUS', {
"throttle_position": 0,
"rpm": 0,
})
self.context_collect('STATUSTEXT')
self.try_arm(result=False, expect_msg="requested state is not RUN")
self.start_subtest("Setting generator to IDLE state.")
self.set_rc(gen_ctrl_ch, 1500)
self.wait_statustext("Generator MIDDLE", check_context=True)
self.delay_sim_time(5)
self.assert_received_message_field_values('EFI_STATUS', {
"throttle_out": 0,
"rpm": 0,
})
pwm_for_fifty_percent_throttle = int(rc_min + rc_dz + int((rc_max-rc_min-rc_dz)/2))
self.progress("Using PWM of %u for 50 percent throttle" % pwm_for_fifty_percent_throttle)
self.set_rc(loweheiser_man_throt_ch, pwm_for_fifty_percent_throttle)
self.wait_message_field_values('EFI_STATUS', {
"throttle_position": 51,
"rpm": 0,
}, timeout=20)
self.set_rc(loweheiser_man_start_ch, 2000)
self.drain_mav()
self.assert_received_message_field_values('EFI_STATUS', {
"intake_manifold_pressure": 94,
"exhaust_gas_temperature": float("nan"),
"ignition_voltage": 12,
}, epsilon=1.0)
self.wait_message_field_values('EFI_STATUS', {
"cylinder_head_temperature": 20,
}, epsilon=5.0, timeout=10)
self.set_rc(loweheiser_man_start_ch, 1000)
self.assert_received_message_field_values('GENERATOR_STATUS', {
"battery_current": -0.30000001192092896,
"load_current": 10.119999885559082,
"power_generated": 521.0,
"bus_voltage": 50,
"rectifier_temperature": 32767,
"bat_current_setpoint": float("nan"),
"generator_temperature": 32767,
"runtime": 2,
"time_until_maintenance": 300*60*60 - 2,
})
self.progress("Generator at idle should not run governor and use throttle input")
self.wait_message_field_values('EFI_STATUS', {
"throttle_position": 51,
"rpm": 4080,
}, timeout=20)
self.start_subtest("Setting generator to RUN state.")
self.set_rc(gen_ctrl_ch, 2000)
self.wait_statustext("Generator HIGH", check_context=True)
self.try_arm(result=False, expect_msg="Generator warming up")
self.set_rc(gen_ctrl_ch, 1000)
self.wait_statustext("requested state is not RUN", timeout=200)
self.set_rc(gen_ctrl_ch, 1500)
self.set_rc(loweheiser_man_start_ch, 2000)
self.wait_generator_speed_and_state(2000, 3000, mavutil.mavlink.MAV_GENERATOR_STATUS_FLAG_IDLE)
self.set_rc(loweheiser_man_start_ch, 1000)
self.set_rc(gen_ctrl_ch, 2000)
self.wait_generator_speed_and_state(8000, 30000, mavutil.mavlink.MAV_GENERATOR_STATUS_FLAG_GENERATING)
bs = self.assert_receive_message(
"BATTERY_STATUS",
condition="BATTERY_STATUS.id==1",
timeout=1,
very_verbose=True,
)
if bs is None:
raise NotAchievedException("Did not receive BATTERY_STATUS")
self.progress("Received battery status: %s" % str(bs))
want_bs_volt = 50000
if bs.voltages[0] != want_bs_volt:
raise NotAchievedException("Battery voltage not as expected (want=%f) got=(%f)" % (want_bs_volt, bs.voltages[0],))
self.progress("Checking battery remaining")
bs = self.assert_receive_message(
"BATTERY_STATUS",
condition="BATTERY_STATUS.id==2",
timeout=1,
very_verbose=True,
)
self.progress("Waiting for some fuel to be consumed...")
self.wait_message_field_values("BATTERY_STATUS", {
"id": 2,
"battery_remaining": bs.battery_remaining-1,
}, timeout=100)
bs2 = self.assert_receive_message(
"BATTERY_STATUS",
condition="BATTERY_STATUS.id==2",
timeout=1,
very_verbose=True,
)
if bs2.battery_remaining >= bs.battery_remaining:
raise NotAchievedException("Expected battery remaining to drop")
if bs2.current_consumed <= bs.current_consumed:
raise NotAchievedException("Expected energy consumed to rise")
self.progress("Checking battery reset")
batt_reset_values = [(25, 24),
(50, 49),
(63, 62),
(87, 86),
(100, 99)]
for (reset_val, return_val) in batt_reset_values:
self.run_cmd(mavutil.mavlink.MAV_CMD_BATTERY_RESET,
(1 << 2),
reset_val,
0,
0,
0,
0,
0
)
self.wait_message_field_values("BATTERY_STATUS", {
"id": 2,
"battery_remaining": return_val,
}, timeout=5)
self.progress("Moving *back* to idle")
self.set_rc(gen_ctrl_ch, 1500)
self.wait_generator_speed_and_state(3000, 10000, mavutil.mavlink.MAV_GENERATOR_STATUS_FLAG_IDLE)
self.progress("Moving *back* to run")
self.set_rc(gen_ctrl_ch, 2000)
self.wait_generator_speed_and_state(8000, 30000, mavutil.mavlink.MAV_GENERATOR_STATUS_FLAG_GENERATING)
self.set_parameter("LOG_DISARMED", 0)
if not self.current_onboard_log_contains_message("LOEG"):
raise NotAchievedException("Did not find expected LOEG message in .bin log")
if not self.current_onboard_log_contains_message("LOEC"):
raise NotAchievedException("Did not find expected LOEC message in .bin log")
self.set_parameter("LOG_DISARMED", 1)
self.progress("Stopping generator")
self.set_rc(gen_ctrl_ch, 1000)
self.wait_statustext("Cooling down")
self.wait_generator_speed_and_state(0, 0, mavutil.mavlink.MAV_GENERATOR_STATUS_FLAG_OFF)
self.start_subtest("Checking safety switch estop")
self.set_rc(gen_ctrl_ch, 1500)
self.set_rc(loweheiser_man_start_ch, 2000)
self.wait_message_field_values('EFI_STATUS', {
"rpm": 4000,
}, timeout=20)
self.set_rc(loweheiser_man_start_ch, 1000)
self.set_safetyswitch_on()
self.wait_generator_speed_and_state(0, 0, mavutil.mavlink.MAV_GENERATOR_STATUS_FLAG_OFF)
self.wait_message_field_values('EFI_STATUS', {
"throttle_position": 0,
"rpm": 0,
})
self.set_safetyswitch_off()
self.set_rc(gen_ctrl_ch, 1500)
self.set_rc(loweheiser_man_start_ch, 2000)
self.wait_message_field_values('EFI_STATUS', {
"throttle_position": 51,
"rpm": 4000,
}, timeout=20)
self.set_rc(loweheiser_man_start_ch, 1000)
self.set_rc(gen_ctrl_ch, 1000)
self.wait_generator_speed_and_state(0, 0, mavutil.mavlink.MAV_GENERATOR_STATUS_FLAG_OFF)
self.start_subtest("Battery Failsafes")
self.context_push()
batt3_capacity = 500
batt3_low_mah = 100
batt3_low_consumed_mah = batt3_capacity - batt3_low_mah
self.set_parameters({
"BATT3_CAPACITY": batt3_capacity,
"BATT3_LOW_MAH": batt3_low_mah,
"BATT3_CRT_MAH": 50,
"BATT3_FS_LOW_ACT": 2,
"BATT3_FS_CRT_ACT": 1,
"BATT3_LOW_VOLT": 0,
})
self.reboot_sitl()
self.set_rc(gen_ctrl_ch, 1500)
self.set_rc(loweheiser_man_start_ch, 2000)
self.delay_sim_time(5)
self.set_rc(loweheiser_man_start_ch, 1000)
self.set_rc(gen_ctrl_ch, 2000)
self.wait_generator_speed_and_state(8000, 30000, mavutil.mavlink.MAV_GENERATOR_STATUS_FLAG_GENERATING)
self.takeoff(10, mode='GUIDED')
first_efi_status = self.assert_receive_message('EFI_STATUS', verbose=True)
if first_efi_status.fuel_consumed < 100:
raise NotAchievedException("Unexpected fuel consumed value after takeoff (%f)" % first_efi_status.fuel_consumed)
self.fly_guided_move_local(100, 100, 20)
self.wait_mode('RTL', timeout=300)
second_efi_status = self.assert_receive_message('EFI_STATUS', verbose=True)
if second_efi_status.fuel_consumed < batt3_low_consumed_mah:
raise NotAchievedException("Unexpected fuel consumed value after failsafe (%f)" % second_efi_status.fuel_consumed)
self.wait_mode('LAND', timeout=300)
self.wait_disarmed()
self.context_pop()
self.reboot_sitl()
self.set_message_rate_hz("EFI_STATUS", -1)
self.set_message_rate_hz("GENERATOR_STATUS", -1)
def AuxSwitchOptions(self):
'''Test random aux mode options'''
self.set_parameter("RC7_OPTION", 58)
self.load_mission("copter_loiter_to_alt.txt")
self.set_rc(7, 1000)
self.assert_mission_count(5)
self.progress("Clear mission")
self.set_rc(7, 2000)
self.delay_sim_time(1)
self.assert_mission_count(0)
self.set_rc(7, 1000)
self.set_parameter("RC7_OPTION", 24)
self.delay_sim_time(2)
self.load_mission("copter_loiter_to_alt.txt")
set_wp = 4
self.set_current_waypoint(set_wp)
self.wait_current_waypoint(set_wp, timeout=10)
self.progress("Reset mission")
self.set_rc(7, 2000)
self.delay_sim_time(1)
self.wait_current_waypoint(0, timeout=10)
self.set_rc(7, 1000)
def AuxFunctionsInMission(self):
'''Test use of auxiliary functions in missions'''
self.load_mission("aux_functions.txt")
self.change_mode('LOITER')
self.wait_ready_to_arm()
self.arm_vehicle()
self.change_mode('AUTO')
self.set_rc(3, 1500)
self.wait_mode('ALT_HOLD')
self.change_mode('AUTO')
self.wait_rtl_complete()
def MAV_CMD_AIRFRAME_CONFIGURATION(self):
'''deploy/retract landing gear using mavlink command'''
self.context_push()
self.set_parameters({
"LGR_ENABLE": 1,
"SERVO10_FUNCTION": 29,
"SERVO10_MIN": 1001,
"SERVO10_MAX": 1999,
})
self.reboot_sitl()
self.wait_servo_channel_value(10, 0)
self.start_subtest("Put gear down")
self.run_cmd(mavutil.mavlink.MAV_CMD_AIRFRAME_CONFIGURATION, p2=0)
self.wait_servo_channel_value(10, 1999)
self.start_subtest("Put gear up")
self.run_cmd_int(mavutil.mavlink.MAV_CMD_AIRFRAME_CONFIGURATION, p2=1)
self.wait_servo_channel_value(10, 1001)
self.start_subtest("Put gear down")
self.run_cmd(mavutil.mavlink.MAV_CMD_AIRFRAME_CONFIGURATION, p2=0)
self.wait_servo_channel_value(10, 1999)
self.context_pop()
self.reboot_sitl()
def WatchAlts(self):
'''Ensure we can monitor different altitudes'''
self.takeoff(30, mode='GUIDED')
self.delay_sim_time(5, reason='let altitude settle')
self.progress("Testing absolute altitudes")
absolute_alt = self.get_altitude(altitude_source='SIM_STATE.alt')
self.progress("absolute_alt=%f" % absolute_alt)
epsilon = 4
for source in ['GLOBAL_POSITION_INT.alt', 'SIM_STATE.alt', 'GPS_RAW_INT.alt']:
self.watch_altitude_maintained(
absolute_alt-epsilon,
absolute_alt+epsilon,
altitude_source=source
)
self.progress("Testing absolute altitudes")
relative_alt = self.get_altitude(relative=True)
for source in ['GLOBAL_POSITION_INT.relative_alt']:
self.watch_altitude_maintained(
relative_alt-epsilon,
relative_alt+epsilon,
altitude_source=source
)
self.do_RTL()
def TestTetherStuck(self):
"""Test tethered vehicle stuck because of tether"""
self.set_parameters({
"SIM_TETH_ENABLE": 1,
})
self.delay_sim_time(2)
self.reboot_sitl()
self.set_parameters({
"SIM_TETH_LINELEN": 10,
})
self.delay_sim_time(2)
self.wait_ready_to_arm()
self.arm_vehicle()
self.takeoff(10, mode='LOITER')
self.set_rc(3, 1900)
self.delay_sim_time(10, reason='let tether get stuck')
tstart = self.get_sim_time()
initial_alt = self.get_altitude(altitude_source='SIM_STATE.alt')
self.progress(f"initial_alt={initial_alt}")
stuck = True
while self.get_sim_time() - tstart < 10:
current_alt = self.get_altitude(altitude_source='SIM_STATE.alt')
self.progress(f"current_alt={current_alt}")
battery_status = self.assert_receive_message('BATTERY_STATUS')
if battery_status:
self.progress(f"Battery: {battery_status}")
if battery_status and (battery_status.current_battery < 6500 or abs(current_alt - initial_alt) > 3):
stuck = False
break
if not stuck:
raise NotAchievedException("Vehicle did not get stuck as expected")
self.land_and_disarm()
def fly_rangefinder_drivers_fly(self, rangefinders):
'''ensure rangefinder gives height-above-ground'''
expected_alt = 5
self.takeoff(expected_alt, mode='GUIDED', alt_minimum_duration=5)
ds = self.assert_receive_message("DISTANCE_SENSOR")
gpi = self.assert_receive_message('GLOBAL_POSITION_INT')
delta = abs(ds.current_distance*0.01 - gpi.relative_alt/1000.0)
alt_msg = f"{ds.current_distance*0.01=} disagrees with global-position-int.relative_alt ({gpi.relative_alt/1000.0}) by {delta}m"
self.progress(alt_msg)
self.progress(f"Terrain report: {self.mav.messages['TERRAIN_REPORT']}")
if delta > 1:
raise NotAchievedException(alt_msg)
for i in range(0, len(rangefinders)):
name = rangefinders[i]
self.progress("i=%u (%s)" % (i, name))
ds = self.assert_receive_message(
"DISTANCE_SENSOR",
timeout=2,
condition=f"DISTANCE_SENSOR.id=={i}",
verbose=True,
)
if abs(ds.current_distance/100.0 - gpi.relative_alt/1000.0) > 1:
raise NotAchievedException(
"distance sensor.current_distance (%f) (%s) disagrees with global-position-int.relative_alt (%s)" %
(ds.current_distance/100.0, name, gpi.relative_alt/1000.0))
self.land_and_disarm()
self.progress("Ensure RFND messages in log")
if not self.current_onboard_log_contains_message("RFND"):
raise NotAchievedException("No RFND messages in log")
def MAVProximity(self):
'''Test MAVLink proximity driver'''
self.start_subtest("Test mavlink proximity sensor using DISTANCE_SENSOR messages")
self.set_parameter("SERIAL5_PROTOCOL", 1)
self.set_parameter("PRX1_TYPE", 2)
self.reboot_sitl()
self.progress("Should be unhealthy while we don't send messages")
self.assert_sensor_state(mavutil.mavlink.MAV_SYS_STATUS_SENSOR_PROXIMITY, True, True, False)
self.progress("Should be healthy while we're sending good messages")
tstart = self.get_sim_time()
while True:
if self.get_sim_time() - tstart > 5:
raise NotAchievedException("Sensor did not come good")
self.mav.mav.distance_sensor_send(
0,
10,
50,
20,
mavutil.mavlink.MAV_DISTANCE_SENSOR_LASER,
21,
mavutil.mavlink.MAV_SENSOR_ROTATION_NONE,
255
)
if self.sensor_has_state(mavutil.mavlink.MAV_SYS_STATUS_SENSOR_PROXIMITY, True, True, True):
self.progress("Sensor has good state")
break
self.delay_sim_time(0.1)
self.progress("Should be unhealthy again if we stop sending messages")
self.delay_sim_time(1)
self.assert_sensor_state(mavutil.mavlink.MAV_SYS_STATUS_SENSOR_PROXIMITY, True, True, False)
distance_map = {
mavutil.mavlink.MAV_SENSOR_ROTATION_NONE: 30,
mavutil.mavlink.MAV_SENSOR_ROTATION_YAW_45: 35,
mavutil.mavlink.MAV_SENSOR_ROTATION_YAW_90: 20,
mavutil.mavlink.MAV_SENSOR_ROTATION_YAW_135: 15,
mavutil.mavlink.MAV_SENSOR_ROTATION_YAW_180: 70,
mavutil.mavlink.MAV_SENSOR_ROTATION_YAW_225: 80,
mavutil.mavlink.MAV_SENSOR_ROTATION_YAW_270: 10,
mavutil.mavlink.MAV_SENSOR_ROTATION_YAW_315: 90,
}
wanted_distances = copy.copy(distance_map)
sensor_enum = mavutil.mavlink.enums["MAV_SENSOR_ORIENTATION"]
def my_message_hook(mav, m):
if m.get_type() != 'DISTANCE_SENSOR':
return
self.progress("Got (%s)" % str(m))
want = distance_map[m.orientation]
got = m.current_distance
delta = abs(want-got)
if delta > 1:
self.progress(
"Wrong distance (%s): want=%f got=%f" %
(sensor_enum[m.orientation].name, want, got))
return
if m.orientation not in wanted_distances:
return
self.progress(
"Correct distance (%s): want=%f got=%f" %
(sensor_enum[m.orientation].name, want, got))
del wanted_distances[m.orientation]
self.install_message_hook_context(my_message_hook)
tstart = self.get_sim_time()
while True:
if self.get_sim_time() - tstart > 5:
raise NotAchievedException("Sensor did not give right distances")
for (orient, dist) in distance_map.items():
self.mav.mav.distance_sensor_send(
0,
10,
90,
dist,
mavutil.mavlink.MAV_DISTANCE_SENSOR_LASER,
21,
orient,
255
)
self.wait_heartbeat()
if len(wanted_distances.keys()) == 0:
break
def fly_rangefinder_mavlink_distance_sensor(self):
self.start_subtest("Test mavlink rangefinder using DISTANCE_SENSOR messages")
self.context_push()
self.set_parameters({
"RTL_ALT_TYPE": 0,
"LGR_ENABLE": 1,
"LGR_DEPLOY_ALT": 1,
"LGR_RETRACT_ALT": 10,
"SERVO10_FUNCTION": 29
})
ex = None
try:
self.set_parameter("SERIAL5_PROTOCOL", 1)
self.set_parameter("RNGFND1_TYPE", 10)
self.reboot_sitl()
self.set_parameter("RNGFND1_MAX", 327.67)
self.progress("Should be unhealthy while we don't send messages")
self.assert_sensor_state(mavutil.mavlink.MAV_SYS_STATUS_SENSOR_LASER_POSITION, True, True, False)
self.progress("Should be healthy while we're sending good messages")
tstart = self.get_sim_time()
while True:
if self.get_sim_time() - tstart > 5:
raise NotAchievedException("Sensor did not come good")
self.mav.mav.distance_sensor_send(
0,
10,
50,
20,
mavutil.mavlink.MAV_DISTANCE_SENSOR_LASER,
21,
mavutil.mavlink.MAV_SENSOR_ROTATION_PITCH_270,
255
)
if self.sensor_has_state(mavutil.mavlink.MAV_SYS_STATUS_SENSOR_LASER_POSITION, True, True, True):
self.progress("Sensor has good state")
break
self.delay_sim_time(0.1)
self.progress("Should be unhealthy again if we stop sending messages")
self.delay_sim_time(1)
self.assert_sensor_state(mavutil.mavlink.MAV_SYS_STATUS_SENSOR_LASER_POSITION, True, True, False)
self.progress("Landing gear should deploy with current_distance below min_distance")
self.change_mode('STABILIZE')
timeout = 60
tstart = self.get_sim_time()
while not self.armed():
if self.get_sim_time() - tstart > timeout:
raise NotAchievedException("Failed to become armable after %f seconds" % timeout)
self.mav.mav.distance_sensor_send(
0,
100,
2500,
200,
mavutil.mavlink.MAV_DISTANCE_SENSOR_LASER,
21,
mavutil.mavlink.MAV_SENSOR_ROTATION_PITCH_270,
255
)
try:
self.arm_vehicle()
except Exception:
pass
self.delay_sim_time(1)
self.run_cmd(
mavutil.mavlink.MAV_CMD_AIRFRAME_CONFIGURATION,
p2=0,
)
self.context_collect("STATUSTEXT")
tstart = self.get_sim_time()
while True:
if self.get_sim_time_cached() - tstart > 5:
raise NotAchievedException("Retraction did not happen")
self.mav.mav.distance_sensor_send(
0,
100,
6000,
1500,
mavutil.mavlink.MAV_DISTANCE_SENSOR_LASER,
21,
mavutil.mavlink.MAV_SENSOR_ROTATION_PITCH_270,
255
)
self.delay_sim_time(0.1)
try:
self.wait_text("LandingGear: RETRACT", check_context=True, timeout=0.1)
except Exception:
continue
self.progress("Retracted")
break
while True:
if self.get_sim_time_cached() - tstart > 5:
raise NotAchievedException("Deployment did not happen")
self.progress("Sending distance-sensor message")
self.mav.mav.distance_sensor_send(
0,
300,
500,
250,
mavutil.mavlink.MAV_DISTANCE_SENSOR_LASER,
21,
mavutil.mavlink.MAV_SENSOR_ROTATION_PITCH_270,
255
)
try:
self.wait_text("LandingGear: DEPLOY", check_context=True, timeout=0.1)
except Exception:
continue
self.progress("Deployed")
break
self.disarm_vehicle()
except Exception as e:
self.print_exception_caught(e)
ex = e
self.context_pop()
self.reboot_sitl()
if ex is not None:
raise ex
def GSF(self):
'''test the Gaussian Sum filter'''
self.context_push()
self.set_parameter("EK2_ENABLE", 1)
self.reboot_sitl()
self.takeoff(20, mode='LOITER')
self.set_rc(2, 1400)
self.delay_sim_time(5)
self.set_rc(2, 1500)
self.progress("Path: %s" % self.current_onboard_log_filepath())
dfreader = self.dfreader_for_current_onboard_log()
self.do_RTL()
self.context_pop()
self.reboot_sitl()
want = set(["XKY0", "XKY1", "NKY0", "NKY1"])
still_want = want
while len(still_want):
m = dfreader.recv_match(type=want)
if m is None:
raise NotAchievedException("Did not get %s" % want)
still_want.remove(m.get_type())
def GSF_reset(self):
'''test the Gaussian Sum filter based Emergency reset'''
self.context_push()
self.set_parameters({
"COMPASS_ORIENT": 4,
"COMPASS_USE2": 0,
"COMPASS_USE3": 0,
})
self.reboot_sitl()
self.change_mode('GUIDED')
self.wait_ready_to_arm()
startpos = self.assert_receive_message('LOCAL_POSITION_NED')
self.progress("startpos=%s" % str(startpos))
self.arm_vehicle()
expected_alt = 5
self.user_takeoff(alt_min=expected_alt)
self.wait_text("EKF3 IMU. emergency yaw reset", timeout=5, regex=True)
endpos = self.assert_receive_message('LOCAL_POSITION_NED')
delta_x = endpos.x - startpos.x
delta_y = endpos.y - startpos.y
dist_m = math.sqrt(delta_x*delta_x + delta_y*delta_y)
self.progress("GSF yaw reset triggered at %f meters" % dist_m)
self.do_RTL()
self.context_pop()
self.reboot_sitl()
dist_m_max = 8
if dist_m > dist_m_max:
raise NotAchievedException("GSF reset failed, vehicle flew too far (%f > %f)" % (dist_m, dist_m_max))
def FlyRangeFinderMAVlink(self):
'''fly mavlink-connected rangefinder'''
self.fly_rangefinder_mavlink_distance_sensor()
self.set_parameters({
"SERIAL5_PROTOCOL": 1,
"RNGFND1_TYPE": 10,
"RTL_ALT_M": 5,
"RTL_ALT_TYPE": 1,
"SIM_TERRAIN": 0,
})
self.customise_SITL_commandline(['--serial5=sim:rf_mavlink'])
self.change_mode('GUIDED')
self.wait_ready_to_arm()
self.arm_vehicle()
expected_alt = 5
self.user_takeoff(alt_min=expected_alt)
self.context_push()
self.context_set_message_rate_hz('RANGEFINDER', self.sitl_streamrate())
tstart = self.get_sim_time()
while True:
if self.get_sim_time() - tstart > 5:
raise NotAchievedException("Mavlink rangefinder not working")
rf = self.assert_receive_message("RANGEFINDER")
gpi = self.assert_receive_message('GLOBAL_POSITION_INT')
if abs(rf.distance - gpi.relative_alt/1000.0) > 1:
self.progress("rangefinder alt (%s) disagrees with global-position-int.relative_alt (%s)" %
(rf.distance, gpi.relative_alt/1000.0))
continue
ds = self.assert_receive_message("DISTANCE_SENSOR", timeout=2, verbose=True)
if abs(ds.current_distance/100.0 - gpi.relative_alt/1000.0) > 1:
self.progress(
"distance sensor.current_distance (%f) disagrees with global-position-int.relative_alt (%s)" %
(ds.current_distance/100.0, gpi.relative_alt/1000.0))
continue
break
self.context_pop()
self.progress("mavlink rangefinder OK")
self.land_and_disarm()
def MaxBotixI2CXL(self):
'''Test maxbotix rangefinder drivers'''
ex = None
try:
self.context_push()
self.start_subtest("No messages")
self.assert_not_receiving_message("DISTANCE_SENSOR", timeout=5)
self.start_subtest("Default address")
self.set_parameter("RNGFND1_TYPE", 2)
self.reboot_sitl()
self.do_timesync_roundtrip()
self.assert_receive_message("DISTANCE_SENSOR", timeout=5, verbose=True)
self.start_subtest("Explicitly set to default address")
self.set_parameters({
"RNGFND1_TYPE": 2,
"RNGFND1_ADDR": 0x70,
})
self.reboot_sitl()
self.do_timesync_roundtrip()
rf = self.assert_receive_message("DISTANCE_SENSOR", timeout=5, verbose=True)
self.start_subtest("Explicitly set to non-default address")
self.set_parameter("RNGFND1_ADDR", 0x71)
self.reboot_sitl()
self.do_timesync_roundtrip()
rf = self.assert_receive_message("DISTANCE_SENSOR", timeout=5, verbose=True)
self.start_subtest("Two MaxBotix RangeFinders")
self.set_parameters({
"RNGFND1_TYPE": 2,
"RNGFND1_ADDR": 0x70,
"RNGFND1_MIN": 1.50,
"RNGFND2_TYPE": 2,
"RNGFND2_ADDR": 0x71,
"RNGFND2_MIN": 2.50,
})
self.reboot_sitl()
self.do_timesync_roundtrip()
for i in [0, 1]:
rf = self.assert_receive_message(
"DISTANCE_SENSOR",
timeout=5,
condition=f"DISTANCE_SENSOR.id=={i}",
verbose=True,
)
expected_dist = 150
if i == 1:
expected_dist = 250
if rf.min_distance != expected_dist:
raise NotAchievedException("Unexpected min_cm (want=%u got=%u)" %
(expected_dist, rf.min_distance))
self.context_pop()
except Exception as e:
self.print_exception_caught(e)
ex = e
self.reboot_sitl()
if ex is not None:
raise ex
def FlyRangeFinderSITL(self):
'''fly the type=100 perfect rangefinder'''
self.set_parameters({
"RNGFND1_TYPE": 100,
"RTL_ALT_M": 5,
"RTL_ALT_TYPE": 1,
"SIM_TERRAIN": 0,
})
self.reboot_sitl()
self.fly_rangefinder_drivers_fly([("unused", "sitl")])
self.wait_disarmed()
def RangeFinderDrivers(self):
'''Test rangefinder drivers'''
self.set_parameters({
"RTL_ALT_M": 5,
"RTL_ALT_TYPE": 1,
"SIM_TERRAIN": 0,
})
drivers = [
("lightwareserial", 8),
("lightwareserial-binary", 8),
("USD1_v0", 11),
("USD1_v1", 11),
("leddarone", 12),
("maxsonarseriallv", 13),
("nmea", 17, {"baud": 9600}),
("wasp", 18),
("benewake_tf02", 19),
("blping", 23),
("benewake_tfmini", 20),
("lanbao", 26),
("benewake_tf03", 27),
("gyus42v2", 31),
("teraranger_serial", 35),
("nooploop_tofsense", 37),
("ainsteinlrd1", 42),
("rds02uf", 43),
("lightware_grf", 45),
]
while len(drivers):
do_drivers = drivers[0:3]
drivers = drivers[3:]
command_line_args = []
self.context_push()
for offs in range(3):
serial_num = offs + 4
if len(do_drivers) > offs:
if len(do_drivers[offs]) > 2:
(sim_name, rngfnd_param_value, kwargs) = do_drivers[offs]
else:
(sim_name, rngfnd_param_value) = do_drivers[offs]
kwargs = {}
command_line_args.append("--serial%s=sim:%s" %
(serial_num, sim_name))
sets = {
"SERIAL%u_PROTOCOL" % serial_num: 9,
"RNGFND%u_TYPE" % (offs+1): rngfnd_param_value,
}
if "baud" in kwargs:
sets["SERIAL%u_BAUD" % serial_num] = kwargs["baud"]
self.set_parameters(sets)
self.customise_SITL_commandline(command_line_args)
self.fly_rangefinder_drivers_fly([x[0] for x in do_drivers])
self.context_pop()
class I2CDriverToTest:
def __init__(self, name, rngfnd_type, rngfnd_addr=None):
self.name = name
self.rngfnd_type = rngfnd_type
self.rngfnd_addr = rngfnd_addr
i2c_drivers = [
I2CDriverToTest("maxbotixi2cxl", 2),
I2CDriverToTest("terarangeri2c", 14, rngfnd_addr=0x31),
I2CDriverToTest("lightware_legacy16bit", 7, rngfnd_addr=0x66),
I2CDriverToTest("tfs20l", 46, rngfnd_addr=0x10),
I2CDriverToTest("TFMiniPlus", 25, rngfnd_addr=0x09),
]
while len(i2c_drivers):
do_drivers = i2c_drivers[0:9]
i2c_drivers = i2c_drivers[9:]
count = 1
p = {}
for d in do_drivers:
p[f"RNGFND{count}_TYPE"] = d.rngfnd_type
if d.rngfnd_addr is not None:
p[f"RNGFND{count}_ADDR"] = d.rngfnd_addr
count += 1
self.set_parameters(p)
self.reboot_sitl()
self.fly_rangefinder_drivers_fly([x.name for x in do_drivers])
def RangeFinderDriversMaxAlt_FlyDriver(self,
name : str,
rngfnd_type : int,
simname : str,
maxalt : float,
sqalt : float | None = None,
sq_at_sqalt : float | None = None,
expected_statustext : str | None = None,
):
'''test max-height behaviour'''
self.progress(f"Flying {name}")
self.set_parameters({
"SERIAL4_PROTOCOL": 9,
"RNGFND1_TYPE": rngfnd_type,
"WPNAV_SPEED_UP": 1000,
})
self.customise_SITL_commandline([
f"--serial4=sim:{simname}",
])
takeoff_alt = sqalt
if sqalt is None:
takeoff_alt = maxalt
self.context_set_message_rate_hz('RANGEFINDER', self.sitl_streamrate())
self.takeoff(takeoff_alt, mode='GUIDED', timeout=240, max_err=0.5)
self.assert_rangefinder_distance_between(takeoff_alt-5, takeoff_alt+5)
self.wait_rangefinder_distance(takeoff_alt-5, takeoff_alt+5)
rf_bit = mavutil.mavlink.MAV_SYS_STATUS_SENSOR_LASER_POSITION
self.assert_sensor_state(rf_bit, present=True, enabled=True, healthy=True)
if sq_at_sqalt is not None:
self.assert_distance_sensor_quality(sq_at_sqalt)
self.progress("Moving higher to be out of max rangefinder range")
self.fly_guided_move_local(0, 0, takeoff_alt + 50)
self.assert_sensor_state(rf_bit, present=True, enabled=True, healthy=True)
self.assert_distance_sensor_quality(1)
self.progress("Test behaviour above 655.35 metres (UINT16_MAX cm)")
self.context_push()
self.context_collect('STATUSTEXT')
self.fly_guided_move_local(0, 0, 700)
self.assert_sensor_state(rf_bit, present=True, enabled=True, healthy=True)
self.assert_distance_sensor_quality(1)
if expected_statustext is not None:
self.wait_statustext(expected_statustext, check_context=True)
self.context_pop()
self.disarm_vehicle(force=True)
def RangeFinderDriversMaxAlt_LightwareSerial(self) -> None:
'''test max-height behaviour - LightwareSerial'''
self.RangeFinderDriversMaxAlt_FlyDriver(
name="LightwareSerial",
rngfnd_type=8,
simname='lightwareserial',
maxalt=100,
sqalt=95,
sq_at_sqalt=100,
expected_statustext=None,
)
def RangeFinderDriversMaxAlt_AinsteinLRD1(self) -> None:
'''test max-height behaviour - Ainstein LRD1 - pre v19.0.0 firmware'''
self.context_collect('STATUSTEXT')
self.RangeFinderDriversMaxAlt_FlyDriver(
name="Ainstein-LR-D1",
rngfnd_type=42,
simname='ainsteinlrd1',
maxalt=500,
sqalt=495,
sq_at_sqalt=39,
expected_statustext='Altitude reading overflow',
)
self.wait_text("Rangefinder: Temperature alert", check_context=True)
def RangeFinderDriversMaxAlt_AinsteinLRD1_v19(self) -> None:
'''test max-height behaviour - Ainstein LRD1 - v19.0.0 firmware'''
self.context_collect('STATUSTEXT')
self.RangeFinderDriversMaxAlt_FlyDriver(
name="Ainstein-LR-D1-v19",
rngfnd_type=42,
simname='ainsteinlrd1_v19',
maxalt=500,
sqalt=495,
sq_at_sqalt=39,
)
self.wait_text("Rangefinder: MCU Temperature alert", check_context=True)
def RangeFinderDriversLongRange(self):
'''test rangefinder above 327m'''
self.set_parameters({
"SERIAL4_PROTOCOL": 9,
"RNGFND1_TYPE": 19,
"WPNAV_SPEED_UP": 1000,
})
self.customise_SITL_commandline([
"--serial4=sim:benewake_tf02",
])
text_good = "GOOD"
text_out_of_range_high = "OUT_OF_RANGE_HIGH"
rf_bit = mavutil.mavlink.MAV_SYS_STATUS_SENSOR_LASER_POSITION
alt = 3
self.takeoff(alt, mode='GUIDED')
self.assert_parameter_value("RNGFND1_MAX", 7.00)
self.assert_sensor_state(rf_bit, present=True, enabled=True, healthy=True)
m = self.assert_receive_message('DISTANCE_SENSOR', verbose=True)
if abs(m.current_distance * 0.01 - alt) > 1:
raise NotAchievedException(f"Expected {alt}m range got range={m.current_distance * 0.01}")
self.assert_parameter_value("RNGFND1_MAX", m.max_distance * 0.01, epsilon=0.00001)
self.assert_parameter_value("RNGFND1_MIN", m.min_distance * 0.01, epsilon=0.00001)
self.send_statustext(text_good)
alt = 10
self.fly_guided_move_local(0, 0, alt)
self.assert_sensor_state(rf_bit, present=True, enabled=True, healthy=True)
m = self.assert_receive_message('DISTANCE_SENSOR', verbose=True)
if abs(m.current_distance * 0.01 - alt) > 1:
raise NotAchievedException(f"Expected {alt}m range")
self.assert_parameter_value("RNGFND1_MAX", m.max_distance * 0.01, epsilon=0.00001)
self.assert_parameter_value("RNGFND1_MIN", m.min_distance * 0.01, epsilon=0.00001)
self.send_statustext(text_out_of_range_high)
self.progress("Moving the goalposts")
self.set_parameter("RNGFND1_MAX", 20)
self.assert_sensor_state(rf_bit, present=True, enabled=True, healthy=True)
m = self.assert_receive_message('DISTANCE_SENSOR', verbose=True)
if abs(m.current_distance * 0.01 - alt) > 1:
raise NotAchievedException(f"Expected {alt}m range")
self.assert_parameter_value("RNGFND1_MAX", m.max_distance * 0.01, epsilon=0.00001)
self.assert_parameter_value("RNGFND1_MIN", m.min_distance * 0.01, epsilon=0.00001)
self.send_statustext(text_good)
self.delay_sim_time(2)
dfreader = self.dfreader_for_current_onboard_log()
self.do_RTL()
self.progress("Checking in/out of range markers in log")
required_range = None
count = 0
while True:
m = dfreader.recv_match(type=["MSG", "RFND"])
if m is None:
break
m_type = m.get_type()
if m_type == "MSG":
for t in [text_good, text_out_of_range_high]:
if t in m.Message:
required_range = t
continue
if m_type == "RFND":
if required_range is None:
continue
if required_range == text_good:
required_state = 4
elif required_range == text_out_of_range_high:
required_state = 3
else:
raise ValueError(f"Unexpected text {required_range}")
if m.Stat != required_state:
raise NotAchievedException(f"Not in expected state want={required_state} got={m.Stat} dist={m.Dist}")
self.progress(f"In expected state {required_range}")
required_range = None
count += 1
if count < 3:
raise NotAchievedException("Did not see range markers")
def RangeFinderSITLLongRange(self):
'''test rangefinder above 327m'''
self.set_parameters({
"RNGFND1_TYPE": 100,
"WPNAV_SPEED_UP": 1000,
"RNGFND1_MAX": 7000,
})
self.reboot_sitl()
text_good = "GOOD"
text_high = "HIGH"
rf_bit = mavutil.mavlink.MAV_SYS_STATUS_SENSOR_LASER_POSITION
alt = 3
self.takeoff(alt, mode='GUIDED')
self.assert_sensor_state(rf_bit, present=True, enabled=True, healthy=True)
m = self.assert_receive_message('DISTANCE_SENSOR', verbose=True)
got = m.current_distance * 0.01
if abs(got - alt) > 1:
raise NotAchievedException(f"Expected {alt}m range {got=}")
self.send_statustext(text_good)
alt = 635
self.fly_guided_move_local(0, 0, alt)
self.assert_sensor_state(rf_bit, present=True, enabled=True, healthy=True)
m = self.assert_receive_message('DISTANCE_SENSOR', verbose=True)
if abs(m.current_distance * 0.01 - alt) > 1:
raise NotAchievedException(f"Expected {alt}m range")
self.send_statustext(text_high)
dfreader = self.dfreader_for_current_onboard_log()
def hook(msg, m):
if m.get_type() != 'HEARTBEAT':
return
bitmask = 0
bitmask |= mavutil.mavlink.ATTITUDE_TARGET_TYPEMASK_BODY_ROLL_RATE_IGNORE
bitmask |= mavutil.mavlink.ATTITUDE_TARGET_TYPEMASK_BODY_PITCH_RATE_IGNORE
bitmask |= mavutil.mavlink.ATTITUDE_TARGET_TYPEMASK_BODY_YAW_RATE_IGNORE
bitmask |= mavutil.mavlink.ATTITUDE_TARGET_TYPEMASK_THROTTLE_IGNORE
self.mav.mav.set_attitude_target_send(
0,
1,
1,
0,
mavextra.euler_to_quat([
math.radians(-180),
math.radians(0),
math.radians(0)]),
0,
0,
0,
1
)
self.install_message_hook_context(hook)
self.wait_altitude(0, 30, timeout=200, relative=True)
self.do_RTL()
self.progress("Checking in/out of range markers in log")
good = False
while True:
m = dfreader.recv_match(type=["MSG", "RFND"])
if m is None:
break
m_type = m.get_type()
if m_type == "RFND":
if m.Dist < 600:
continue
good = True
break
if not good:
raise NotAchievedException("Did not see good alt")
def ShipTakeoff(self):
'''Fly Simulated Ship Takeoff'''
self.wait_groundspeed(0, 2)
self.set_parameters({
"SIM_SHIP_ENABLE": 1,
"SIM_SHIP_SPEED": 10,
"SIM_SHIP_DSIZE": 2,
})
self.wait_ready_to_arm()
self.wait_groundspeed(9, 11)
self.takeoff(10)
self.wait_groundspeed(0, 2)
self.land_and_disarm()
self.wait_groundspeed(0, 2)
def ParameterValidation(self):
'''Test parameters are checked for validity'''
self.delay_sim_time(10)
self.progress("invalid; min must be less than max:")
self.set_parameters({
"MOT_PWM_MIN": 100,
"MOT_PWM_MAX": 50,
})
self.drain_mav()
self.assert_prearm_failure("Motors: Check MOT_PWM_MIN and MOT_PWM_MAX")
self.progress("invalid; min must be less than max (equal case):")
self.set_parameters({
"MOT_PWM_MIN": 100,
"MOT_PWM_MAX": 100,
})
self.drain_mav()
self.assert_prearm_failure("Motors: Check MOT_PWM_MIN and MOT_PWM_MAX")
self.progress("Spin min more than 0.3")
self.set_parameters({
"MOT_PWM_MIN": 1000,
"MOT_PWM_MAX": 2000,
"MOT_SPIN_MIN": 0.5,
})
self.drain_mav()
self.assert_prearm_failure("PreArm: Motors: MOT_SPIN_MIN too high 0.50 > 0.3")
self.progress("Spin arm more than spin min")
self.set_parameters({
"MOT_SPIN_MIN": 0.1,
"MOT_SPIN_ARM": 0.2,
})
self.drain_mav()
self.assert_prearm_failure("PreArm: Motors: MOT_SPIN_ARM > MOT_SPIN_MIN")
def SensorErrorFlags(self):
'''Test we get ERR messages when sensors have issues'''
self.reboot_sitl()
for (param_names, param_value, expected_subsys, expected_ecode, desc) in [
(['SIM_BARO_DISABLE', 'SIM_BAR2_DISABLE'], 1, 18, 4, 'unhealthy'),
(['SIM_BARO_DISABLE', 'SIM_BAR2_DISABLE'], 0, 18, 0, 'healthy'),
(['SIM_MAG1_FAIL', 'SIM_MAG2_FAIL', 'SIM_MAG3_FAIL'], 1, 3, 4, 'unhealthy'),
(['SIM_MAG1_FAIL', 'SIM_MAG2_FAIL', 'SIM_MAG3_FAIL'], 0, 3, 0, 'healthy'),
]:
sp = dict()
for name in param_names:
sp[name] = param_value
self.set_parameters(sp)
self.delay_sim_time(1)
mlog = self.dfreader_for_current_onboard_log()
success = False
while True:
m = mlog.recv_match(type='ERR')
print("Got (%s)" % str(m))
if m is None:
break
if m.Subsys == expected_subsys and m.ECode == expected_ecode:
success = True
break
if not success:
raise NotAchievedException("Did not find %s log message" % desc)
def AltEstimation(self):
'''Test that Alt Estimation is mandatory for ALT_HOLD'''
self.set_parameters({
"SIM_BARO_DISABLE": 1,
"SIM_BAR2_DISABLE": 1,
})
self.wait_gps_disable(position_vertical=True)
self.set_parameter("ARMING_SKIPCHK", -1)
self.delay_sim_time(12)
self.change_mode("ALT_HOLD")
self.assert_prearm_failure("Need Alt Estimate")
self.change_mode("STABILIZE")
self.arm_vehicle()
self.set_rc(3, 1700)
try:
self.change_mode("ALT_HOLD", timeout=10)
except AutoTestTimeoutException:
self.progress("PASS not able to set mode without Position : %s" % "ALT_HOLD")
if self.mode_is("ALT_HOLD"):
raise NotAchievedException("Changed to ALT_HOLD with no altitude estimate")
self.disarm_vehicle(force=True)
def EKFSource(self):
'''Check EKF Source Prearms work'''
self.wait_ready_to_arm()
self.start_subtest("bad yaw source")
self.set_parameter("EK3_SRC3_YAW", 17)
self.assert_prearm_failure("Check EK3_SRC3_YAW")
self.context_push()
self.start_subtest("missing required yaw source")
self.set_parameters({
"EK3_SRC3_YAW": 3,
"COMPASS_USE": 0,
"COMPASS_USE2": 0,
"COMPASS_USE3": 0,
})
self.assert_prearm_failure("EK3 sources require Compass")
self.context_pop()
def EK3_EXT_NAV_vel_without_vert(self):
'''Test that EK3 External Navigation velocity works without vertical velocity.'''
self.set_parameters({
"VISO_TYPE": 2,
"SERIAL5_PROTOCOL": 2,
"EK3_ENABLE": 1,
"EK3_SRC2_POSXY": 6,
"EK3_SRC2_POSZ": 1,
"EK3_SRC2_VELXY": 6,
"EK3_SRC2_VELZ": 0,
"EK3_SRC2_YAW": 1,
"EK3_SRC_OPTIONS": 0,
"RC8_OPTION": 90,
"AHRS_EKF_TYPE": 3,
})
self.customise_SITL_commandline(["--serial5=sim:vicon:"])
self.set_rc(8, 1500)
self.progress("Moving")
self.takeoffAndMoveAway()
self.set_parameter("SIM_VICON_VGLI_Z", 1)
innov_limit = 0.1
max_variance = 0.0
tstart = self.get_sim_time()
while True:
msg = self.assert_receive_message(type="EKF_STATUS_REPORT")
variance = msg.velocity_variance
max_variance = max(max_variance, variance)
if variance > innov_limit:
raise NotAchievedException(
f"Velocity variance too high {variance:.2f} > {innov_limit:.2f}"
)
if self.get_sim_time_cached() - tstart > 10:
break
print(f"Max variance before enabling vertical velocity fusion: {max_variance:.2f}")
self.set_parameter("EK3_SRC2_VELZ", 6)
spike_detected = False
max_variance = 0.0
tstart = self.get_sim_time()
while True:
msg = self.assert_receive_message(type="EKF_STATUS_REPORT")
variance = msg.velocity_variance
max_variance = max(max_variance, variance)
if variance > innov_limit:
spike_detected = True
if self.get_sim_time_cached() - tstart > 10:
break
print(f"Max variance after enabling vertical velocity fusion: {max_variance:.2f}")
if not spike_detected:
raise NotAchievedException("Velocity variance did not spike as expected after enabling vertical velocity fusion.")
self.disarm_vehicle(force=True)
def test_replay_gps_bit(self):
self.set_parameters({
"LOG_REPLAY": 1,
"LOG_DISARMED": 1,
"EK3_ENABLE": 1,
"EK2_ENABLE": 1,
"AHRS_TRIM_X": 0.01,
"AHRS_TRIM_Y": -0.03,
"GPS2_TYPE": 1,
"GPS1_POS_X": 0.1,
"GPS1_POS_Y": 0.2,
"GPS1_POS_Z": 0.3,
"GPS2_POS_X": -0.1,
"GPS2_POS_Y": -0.02,
"GPS2_POS_Z": -0.31,
"INS_POS1_X": 0.12,
"INS_POS1_Y": 0.14,
"INS_POS1_Z": -0.02,
"INS_POS2_X": 0.07,
"INS_POS2_Y": 0.012,
"INS_POS2_Z": -0.06,
"RNGFND1_TYPE": 1,
"RNGFND1_PIN": 0,
"RNGFND1_SCALING": 30,
"RNGFND1_POS_X": 0.17,
"RNGFND1_POS_Y": -0.07,
"RNGFND1_POS_Z": -0.005,
"SIM_SONAR_SCALE": 30,
"SIM_GPS2_ENABLE": 1,
})
self.reboot_sitl()
self.wait_sensor_state(mavutil.mavlink.MAV_SYS_STATUS_LOGGING, True, True, True)
current_log_filepath = self.current_onboard_log_filepath()
self.progress("Current log path: %s" % str(current_log_filepath))
self.change_mode("LOITER")
self.wait_ready_to_arm(require_absolute=True)
self.arm_vehicle()
self.takeoffAndMoveAway()
self.do_RTL()
self.reboot_sitl()
return current_log_filepath
def test_replay_gps_yaw_bit(self):
self.load_default_params_file("copter-gps-for-yaw.parm")
self.set_parameters({
"LOG_REPLAY": 1,
"LOG_DISARMED": 1,
})
self.reboot_sitl()
self.wait_sensor_state(mavutil.mavlink.MAV_SYS_STATUS_LOGGING, True, True, True)
self.wait_gps_fix_type_gte(6, message_type="GPS2_RAW", verbose=True)
current_log_filepath = self.current_onboard_log_filepath()
self.progress("Current log path: %s" % str(current_log_filepath))
self.change_mode("LOITER")
self.wait_ready_to_arm(require_absolute=True)
self.arm_vehicle()
self.takeoffAndMoveAway()
self.do_RTL()
self.reboot_sitl()
return current_log_filepath
def test_replay_beacon_bit(self):
self.set_parameters({
"LOG_REPLAY": 1,
"LOG_DISARMED": 1,
})
old_onboard_logs = sorted(self.log_list())
self.BeaconPosition()
new_onboard_logs = sorted(self.log_list())
self.reboot_sitl()
log_difference = [x for x in new_onboard_logs if x not in old_onboard_logs]
return log_difference[1]
def test_replay_optical_flow_bit(self):
self.set_parameters({
"LOG_REPLAY": 1,
"LOG_DISARMED": 1,
})
old_onboard_logs = sorted(self.log_list())
self.OpticalFlowLimits()
new_onboard_logs = sorted(self.log_list())
log_difference = [x for x in new_onboard_logs if x not in old_onboard_logs]
print("log difference: %s" % str(log_difference))
return log_difference[0]
def test_replay_wind_and_airspeed_bit(self):
self.set_parameters({
"LOG_REPLAY": 1,
"LOG_DISARMED": 1,
"EK3_ENABLE": 1,
"WP_YAW_BEHAVIOR": 1,
"EK3_DRAG_BCOEF_X": 17.209,
"EK3_DRAG_BCOEF_Y": 17.209,
"EK3_DRAG_MCOEF": 0.209,
"SIM_WIND_SPD": 3,
"ARSPD_ENABLE": 1,
"ARSPD_TYPE": 100,
"ARSPD_USE": 1,
})
self.reboot_sitl()
self.wait_sensor_state(mavutil.mavlink.MAV_SYS_STATUS_LOGGING, True, True, True)
current_log_filepath = self.current_onboard_log_filepath()
self.progress("Current log path: %s" % str(current_log_filepath))
self.change_mode("LOITER")
self.wait_ready_to_arm(require_absolute=True)
self.wait_sensor_state(mavutil.mavlink.MAV_SYS_STATUS_SENSOR_DIFFERENTIAL_PRESSURE, True, True, True)
self.arm_vehicle()
self.takeoffAndMoveAway()
m = self.assert_receive_message('EKF_STATUS_REPORT')
if m.airspeed_variance == 0:
raise NotAchievedException("never fused airspeed")
self.do_RTL()
self.reboot_sitl()
return current_log_filepath
def GPSBlendingLog(self):
'''Test GPS Blending'''
'''ensure we get dataflash log messages for blended instance'''
self.set_parameters({
"GPS2_TYPE": 1,
"SIM_GPS2_TYPE": 1,
"SIM_GPS2_ENABLE": 1,
"GPS_AUTO_SWITCH": 2,
})
self.reboot_sitl()
tstart = self.get_sim_time()
while True:
if self.get_sim_time_cached() - tstart > 60:
raise NotAchievedException("Did not get good GPS2_RAW message")
m = self.assert_receive_message('GPS2_RAW', verbose=True)
if m.lat == 0:
continue
break
self.change_mode('LOITER')
self.wait_ready_to_arm()
self.arm_vehicle()
self.delay_sim_time(5)
self.disarm_vehicle()
dfreader = self.dfreader_for_current_onboard_log()
wanted = set([0, 1, 2])
seen_primary_change = False
while True:
m = dfreader.recv_match(type=["GPS", "EV"])
if m is None:
break
mtype = m.get_type()
if mtype == 'GPS':
try:
wanted.remove(m.I)
except KeyError:
continue
elif mtype == 'EV':
if m.Id == 67:
seen_primary_change = True
if len(wanted) == 0 and seen_primary_change:
break
if len(wanted):
raise NotAchievedException("Did not get all three GPS types")
if not seen_primary_change:
raise NotAchievedException("Did not see primary change")
def GPSBlending(self):
'''Test GPS Blending'''
'''ensure we get dataflash log messages for blended instance'''
self.set_parameters({
"WP_YAW_BEHAVIOR": 0,
"GPS2_TYPE": 1,
"SIM_GPS2_TYPE": 1,
"SIM_GPS2_ENABLE": 1,
"SIM_GPS1_POS_X": 1.0,
"SIM_GPS1_POS_Y": -1.0,
"SIM_GPS2_POS_X": -1.0,
"SIM_GPS2_POS_Y": 1.0,
"GPS_AUTO_SWITCH": 2,
})
self.reboot_sitl()
alt = 10
self.takeoff(alt, mode='GUIDED')
self.fly_guided_move_local(30, 0, alt)
self.fly_guided_move_local(30, 30, alt)
self.fly_guided_move_local(0, 30, alt)
self.fly_guided_move_local(0, 0, alt)
self.change_mode('LAND')
current_log_file = self.dfreader_for_current_onboard_log()
self.wait_disarmed()
passes = 0
current_ts = None
while True:
m = current_log_file.recv_match(type='GPS')
if m is None:
break
if current_ts is None:
if m.I != 0:
continue
current_ts = m.TimeUS
measurements = {}
if m.TimeUS != current_ts:
current_ts = None
continue
measurements[m.I] = (m.Lat, m.Lng)
if len(measurements) == 3:
for n in 0, 1:
expected_blended = (measurements[0][n] + measurements[1][n])/2
epsilon = 0.0000002
error = abs(measurements[2][n] - expected_blended)
if error > epsilon:
raise NotAchievedException("Blended diverged")
current_ts = None
passes += 1
if passes == 0:
raise NotAchievedException("Did not see three GPS measurements!")
def GPSWeightedBlending(self):
'''Test GPS Weighted Blending'''
self.context_push()
self.set_parameters({
"WP_YAW_BEHAVIOR": 0,
"GPS2_TYPE": 1,
"SIM_GPS2_TYPE": 1,
"SIM_GPS2_ENABLE": 1,
"SIM_GPS1_POS_X": 1.0,
"SIM_GPS1_POS_Y": -1.0,
"SIM_GPS2_POS_X": -1.0,
"SIM_GPS2_POS_Y": 1.0,
"GPS_AUTO_SWITCH": 2,
})
self.set_parameters({
"SIM_GPS1_VERR_X": 0.3,
"SIM_GPS1_VERR_Y": 0.4,
"SIM_GPS2_VERR_X": 0.6,
"SIM_GPS2_VERR_Y": 0.8,
"GPS_BLEND_MASK": 4,
})
self.reboot_sitl()
alt = 10
self.takeoff(alt, mode='GUIDED')
self.fly_guided_move_local(30, 0, alt)
self.fly_guided_move_local(30, 30, alt)
self.fly_guided_move_local(0, 30, alt)
self.fly_guided_move_local(0, 0, alt)
self.change_mode('LAND')
current_log_file = self.dfreader_for_current_onboard_log()
self.wait_disarmed()
current_ts = None
while True:
m = current_log_file.recv_match(type='GPS')
if m is None:
break
if current_ts is None:
if m.I != 0:
continue
current_ts = m.TimeUS
measurements = {}
if m.TimeUS != current_ts:
current_ts = None
continue
measurements[m.I] = (m.Lat, m.Lng, m.Alt)
if len(measurements) == 3:
for n in 0, 1, 2:
expected_blended = 0.8*measurements[0][n] + 0.2*measurements[1][n]
axis_epsilons = [0.0000002, 0.0000002, 0.2]
epsilon = axis_epsilons[n]
error = abs(measurements[2][n] - expected_blended)
if error > epsilon:
raise NotAchievedException(f"Blended diverged {n=} {measurements[0][n]=} {measurements[1][n]=}")
current_ts = None
self.context_pop()
self.reboot_sitl()
def GPSBlendingAffinity(self):
'''test blending when affinity in use'''
self.set_parameters({
"WP_YAW_BEHAVIOR": 0,
"GPS2_TYPE": 1,
"SIM_GPS2_TYPE": 1,
"SIM_GPS2_ENABLE": 1,
"SIM_GPS1_POS_X": 1.0,
"SIM_GPS1_POS_Y": -1.0,
"SIM_GPS2_POS_X": -1.0,
"SIM_GPS2_POS_Y": 1.0,
"GPS_AUTO_SWITCH": 2,
"EK3_AFFINITY" : 1,
"EK3_IMU_MASK": 7,
"SIM_IMU_COUNT": 3,
"INS_ACC3OFFS_X": 0.001,
"INS_ACC3OFFS_Y": 0.001,
"INS_ACC3OFFS_Z": 0.001,
})
self.run_cmd(mavutil.mavlink.MAV_CMD_PREFLIGHT_CALIBRATION, p5=76)
self.reboot_sitl()
alt = 10
self.takeoff(alt, mode='GUIDED')
self.fly_guided_move_local(30, 0, alt)
self.fly_guided_move_local(30, 30, alt)
self.fly_guided_move_local(0, 30, alt)
self.fly_guided_move_local(0, 0, alt)
self.change_mode('LAND')
current_log_file = self.dfreader_for_current_onboard_log()
self.wait_disarmed()
current_ts = None
max_errors = [0, 0, 0]
while True:
m = current_log_file.recv_match(type='XKF1')
if m is None:
break
if current_ts is None:
if m.C != 0:
continue
current_ts = m.TimeUS
measurements = {}
if m.TimeUS != current_ts:
current_ts = None
continue
measurements[m.C] = (m.PN, m.PE, m.PD)
if len(measurements) == 3:
axis_epsilons = [0.05, 0.05, 0.06]
for n in 0, 1, 2:
expected_blended = 0.5*measurements[0][n] + 0.5*measurements[1][n]
epsilon = axis_epsilons[n]
error = abs(measurements[2][n] - expected_blended)
if error > max_errors[n]:
max_errors[n] = error
if error > epsilon:
raise NotAchievedException(f"Blended diverged {n=} {measurements[0][n]=} {measurements[1][n]=} {measurements[2][n]=} {error=}")
current_ts = None
self.progress(f"{max_errors=}")
def Callisto(self):
'''Test Callisto'''
self.customise_SITL_commandline(
[],
defaults_filepath=self.model_defaults_filepath('Callisto'),
model="octa-quad:@ROMFS/models/Callisto.json",
wipe=True,
)
self.takeoff(10)
self.do_RTL()
def FlyEachFrame(self):
'''Fly each supported internal frame'''
vinfo = vehicleinfo.VehicleInfo()
copter_vinfo_options = vinfo.options[self.vehicleinfo_key()]
known_broken_frames = {
'heli-compound': "wrong binary, different takeoff regime",
'heli-dual': "wrong binary, different takeoff regime",
'heli': "wrong binary, different takeoff regime",
'heli-gas': "wrong binary, different takeoff regime",
'heli-blade360': "wrong binary, different takeoff regime",
"quad-can" : "needs CAN periph",
}
for frame in sorted(copter_vinfo_options["frames"].keys()):
self.start_subtest("Testing frame (%s)" % str(frame))
if frame in known_broken_frames:
self.progress("Actually, no I'm not - it is known-broken (%s)" %
(known_broken_frames[frame]))
continue
frame_bits = copter_vinfo_options["frames"][frame]
print("frame_bits: %s" % str(frame_bits))
if frame_bits.get("external", False):
self.progress("Actually, no I'm not - it is an external simulation")
continue
model = frame_bits.get("model", frame)
defaults = self.model_defaults_filepath(frame)
if not isinstance(defaults, list):
defaults = [defaults]
self.context_push()
frame_script = frame_bits.get('frame_example_script', None)
if frame_script is not None:
self.install_example_script_context(frame_script)
self.customise_SITL_commandline(
[],
defaults_filepath=defaults,
model=model,
wipe=True,
)
if frame_script is not None:
self.set_parameters({
"SCR_ENABLE": 1,
"LOG_BITMASK": 65535,
})
self.reboot_sitl()
def verify_yaw(mav, m):
if m.get_type() != 'ATTITUDE':
return
yawspeed_thresh_rads = math.radians(20)
if m.yawspeed > yawspeed_thresh_rads:
raise NotAchievedException("Excessive yaw on takeoff: %f deg/s > %f deg/s (frame=%s)" %
(math.degrees(m.yawspeed), math.degrees(yawspeed_thresh_rads), frame))
self.context_push()
self.install_message_hook_context(verify_yaw)
self.takeoff(10)
self.context_pop()
self.hover()
self.change_mode('ALT_HOLD')
self.delay_sim_time(1)
def verify_rollpitch(mav, m):
if m.get_type() != 'ATTITUDE':
return
pitch_thresh_rad = math.radians(2)
if m.pitch > pitch_thresh_rad:
raise NotAchievedException("Excessive pitch %f deg > %f deg" %
(math.degrees(m.pitch), math.degrees(pitch_thresh_rad)))
roll_thresh_rad = math.radians(2)
if m.roll > roll_thresh_rad:
raise NotAchievedException("Excessive roll %f deg > %f deg" %
(math.degrees(m.roll), math.degrees(roll_thresh_rad)))
self.context_push()
self.install_message_hook_context(verify_rollpitch)
for i in range(5):
self.set_rc(4, 2000)
self.delay_sim_time(0.5)
self.set_rc(4, 1500)
self.delay_sim_time(5)
self.context_pop()
self.do_RTL()
self.context_pop()
def Replay(self):
'''test replay correctness'''
self.progress("Building Replay")
util.build_SITL('tool/Replay', clean=False, configure=False)
self.set_parameters({
"LOG_DARM_RATEMAX": 0,
"LOG_FILE_RATEMAX": 0,
})
bits = [
('GPS', self.test_replay_gps_bit),
('GPSForYaw', self.test_replay_gps_yaw_bit),
('WindAndAirspeed', self.test_replay_wind_and_airspeed_bit),
('Beacon', self.test_replay_beacon_bit),
('OpticalFlow', self.test_replay_optical_flow_bit),
]
for (name, func) in bits:
self.start_subtest("%s" % name)
self.test_replay_bit(func)
def test_replay_bit(self, bit):
self.context_push()
current_log_filepath = bit()
self.progress("Running replay on (%s) (%u bytes)" % (
(current_log_filepath, os.path.getsize(current_log_filepath))
))
self.zero_throttle()
self.run_replay(current_log_filepath)
replay_log_filepath = self.current_onboard_log_filepath()
self.context_pop()
self.progress("Replay log path: %s" % str(replay_log_filepath))
check_replay = util.load_local_module("Tools/Replay/check_replay.py")
ok = check_replay.check_log(replay_log_filepath, self.progress, verbose=True)
if not ok:
raise NotAchievedException("check_replay (%s) failed" % current_log_filepath)
def DefaultIntervalsFromFiles(self):
'''Test setting default mavlink message intervals from files'''
ex = None
intervals_filepath = util.reltopdir("message-intervals-chan0.txt")
self.progress("Using filepath (%s)" % intervals_filepath)
try:
with open(intervals_filepath, "w") as f:
f.write("""30 50
28 100
29 200
""")
f.close()
def custom_stream_rate_setter():
for stream in mavutil.mavlink.MAV_DATA_STREAM_EXTRA3, mavutil.mavlink.MAV_DATA_STREAM_RC_CHANNELS:
self.set_streamrate(5, stream=stream)
self.customise_SITL_commandline(
[],
wipe=True,
set_streamrate_callback=custom_stream_rate_setter,
)
self.assert_message_rate_hz("ATTITUDE", 20)
self.assert_message_rate_hz("SCALED_PRESSURE", 5)
except Exception as e:
self.print_exception_caught(e)
ex = e
os.unlink(intervals_filepath)
self.reboot_sitl()
if ex is not None:
raise ex
def BaroDrivers(self):
'''Test Baro Drivers'''
sensors = [
("MS5611", 2),
]
for (name, bus) in sensors:
self.context_push()
if bus is not None:
self.set_parameter("BARO_EXT_BUS", bus)
self.set_parameter("BARO_PROBE_EXT", 1 << 2)
self.reboot_sitl()
self.wait_ready_to_arm()
self.arm_vehicle()
messages = [None, None, None]
global count
count = 0
def check_pressure(mav, m):
global count
m_type = m.get_type()
count += 1
if m_type == 'SCALED_PRESSURE3':
off = 2
elif m_type == 'SCALED_PRESSURE2':
off = 1
elif m_type == 'SCALED_PRESSURE':
off = 0
else:
return
messages[off] = m
if None in messages:
return
first = messages[0]
for msg in messages[1:]:
delta_press_abs = abs(first.press_abs - msg.press_abs)
if delta_press_abs > 0.5:
raise NotAchievedException("Press_Abs mismatch (press1=%s press2=%s)" % (first, msg))
delta_temperature = abs(first.temperature - msg.temperature)
if delta_temperature > 300:
raise NotAchievedException("Temperature mismatch (t1=%s t2=%s)" % (first, msg))
self.install_message_hook_context(check_pressure)
self.fly_mission("copter_mission.txt", strict=False)
if None in messages:
raise NotAchievedException("Missing a message")
self.context_pop()
self.reboot_sitl()
def PositionWhenGPSIsZero(self):
'''Ensure position doesn't zero when GPS lost'''
self.progress("arm the vehicle and takeoff in Guided")
self.takeoff(20, mode='GUIDED')
self.progress("fly 50m North (or whatever)")
old_pos = self.assert_receive_message('GLOBAL_POSITION_INT')
self.fly_guided_move_global_relative_alt(50, 0, 20)
self.set_parameter('GPS1_TYPE', 0)
self.drain_mav()
tstart = self.get_sim_time()
while True:
if self.get_sim_time_cached() - tstart > 30 and self.mode_is('LAND'):
self.progress("Bug not reproduced")
break
m = self.assert_receive_message('GLOBAL_POSITION_INT', verbose=True)
pos_delta = self.get_distance_int(old_pos, m)
self.progress("Distance: %f" % pos_delta)
if pos_delta < 5:
raise NotAchievedException("Bug reproduced - returned to near origin")
self.wait_disarmed()
self.reboot_sitl()
def RTL_ALT_FINAL_M(self):
'''Test RTL with RTL_ALT_FINAL_M'''
self.progress("arm the vehicle and takeoff in Guided")
self.takeoff(20, mode='GUIDED')
self.set_home(self.mav.location())
self.progress("fly 50m North (or whatever)")
self.fly_guided_move_local(50, 0, 50)
target_alt = 10
self.set_parameter('RTL_ALT_FINAL_M', target_alt)
self.progress("Waiting RTL to reach Home and hold")
self.change_mode('RTL')
tstart = self.get_sim_time()
reachedHome = False
while self.get_sim_time_cached() < tstart + 250:
m = self.assert_receive_message('GLOBAL_POSITION_INT')
alt = m.relative_alt / 1000.0
home_distance = self.distance_to_home(use_cached_home=True)
home = math.sqrt((alt-target_alt)**2 + home_distance**2) < 2
if home and not reachedHome:
reachedHome = True
self.progress("Reached home - holding")
self.delay_sim_time(20)
continue
if reachedHome:
if not home:
raise NotAchievedException("Should still be at home")
if not self.armed():
raise NotAchievedException("Should still be armed")
break
self.progress("Hold at home successful - landing")
self.change_mode("LAND")
self.wait_landed_and_disarmed()
def SMART_RTL(self):
'''Check SMART_RTL'''
self.progress("arm the vehicle and takeoff in Guided")
self.takeoff(20, mode='GUIDED')
self.progress("fly around a bit (or whatever)")
locs = [
(50, 0, 20),
(-50, 50, 20),
(-50, 0, 20),
]
for (lat, lng, alt) in locs:
self.fly_guided_move_local(lat, lng, alt)
self.change_mode('SMART_RTL')
for (lat, lng, alt) in reversed(locs):
self.wait_distance_to_local_position(
(lat, lng, -alt),
0,
10,
timeout=60
)
self.wait_disarmed()
def get_ground_effect_duration_from_current_onboard_log(self, bit, ignore_multi=False):
'''returns a duration in seconds we were expecting to interact with
the ground. Will die if there's more than one such block of
time and ignore_multi is not set (will return first duration
otherwise)
'''
ret = []
dfreader = self.dfreader_for_current_onboard_log()
seen_expected_start_TimeUS = None
first = None
last = None
while True:
m = dfreader.recv_match(type="XKF4")
if m is None:
break
last = m
if first is None:
first = m
expected = m.SS & (1 << bit)
if expected:
if seen_expected_start_TimeUS is None:
seen_expected_start_TimeUS = m.TimeUS
continue
else:
if seen_expected_start_TimeUS is not None:
duration = (m.TimeUS - seen_expected_start_TimeUS)/1000000.0
ret.append(duration)
seen_expected_start_TimeUS = None
if seen_expected_start_TimeUS is not None:
duration = (last.TimeUS - seen_expected_start_TimeUS)/1000000.0
ret.append(duration)
return ret
def get_takeoffexpected_durations_from_current_onboard_log(self, ignore_multi=False):
return self.get_ground_effect_duration_from_current_onboard_log(11, ignore_multi=ignore_multi)
def get_touchdownexpected_durations_from_current_onboard_log(self, ignore_multi=False):
return self.get_ground_effect_duration_from_current_onboard_log(12, ignore_multi=ignore_multi)
def ThrowDoubleDrop(self):
'''Test a more complicated drop-mode scenario'''
self.progress("Getting a lift to altitude")
self.set_parameters({
"SIM_SHOVE_Z": -11,
"THROW_TYPE": 1,
"MOT_SPOOL_TIME": 2,
})
self.change_mode('THROW')
self.wait_ready_to_arm()
self.arm_vehicle()
try:
self.set_parameter("SIM_SHOVE_TIME", 30000)
except ValueError:
pass
self.wait_altitude(100, 1000, timeout=100, relative=True)
self.context_collect('STATUSTEXT')
self.wait_statustext("throw detected - spooling motors", check_context=True, timeout=10)
self.wait_statustext("throttle is unlimited - uprighting", check_context=True)
self.wait_statustext("uprighted - controlling height", check_context=True)
self.wait_statustext("height achieved - controlling position", check_context=True)
self.progress("Waiting for still")
self.wait_speed_vector(Vector3(0, 0, 0))
self.change_mode('ALT_HOLD')
self.set_rc(3, 1000)
self.wait_disarmed(timeout=90)
self.zero_throttle()
self.progress("second flight")
self.upload_square_mission_items_around_location(self.poll_home_position())
self.set_parameters({
"THROW_NEXTMODE": 3,
})
self.change_mode('THROW')
self.wait_ready_to_arm()
self.arm_vehicle()
try:
self.set_parameter("SIM_SHOVE_TIME", 30000)
except ValueError:
pass
self.wait_altitude(100, 1000, timeout=100, relative=True)
self.wait_statustext("throw detected - spooling motors", check_context=True, timeout=10)
self.wait_statustext("throttle is unlimited - uprighting", check_context=True)
self.wait_statustext("uprighted - controlling height", check_context=True)
self.wait_statustext("height achieved - controlling position", check_context=True)
self.wait_mode('AUTO')
self.wait_disarmed(timeout=240)
def GroundEffectCompensation_takeOffExpected(self):
'''Test EKF's handling of takeoff-expected'''
self.change_mode('ALT_HOLD')
self.set_parameter("LOG_FILE_DSRMROT", 1)
self.progress("Making sure we'll have a short log to look at")
self.wait_ready_to_arm()
self.arm_vehicle()
self.disarm_vehicle()
self.start_subtest("Check ground effect compensation remains set in EKF while we're at idle on the ground")
self.arm_vehicle()
self.wait_disarmed()
durations = self.get_takeoffexpected_durations_from_current_onboard_log()
duration = durations[0]
want = 9
self.progress("takeoff-expected duration: %fs" % (duration,))
if duration < want:
raise NotAchievedException("Should have been expecting takeoff for longer than %fs (want>%f)" %
(duration, want))
self.start_subtest("takeoffExpected should be false very soon after we launch into the air")
self.takeoff(mode='ALT_HOLD', alt_min=5)
self.change_mode('LAND')
self.wait_disarmed()
durations = self.get_takeoffexpected_durations_from_current_onboard_log(ignore_multi=True)
self.progress("touchdown-durations: %s" % str(durations))
duration = durations[0]
self.progress("takeoff-expected-duration %f" % (duration,))
want_lt = 5
if duration >= want_lt:
raise NotAchievedException("Was expecting takeoff for longer than expected; got=%f want<=%f" %
(duration, want_lt))
def _MAV_CMD_CONDITION_YAW(self, command):
self.start_subtest("absolute")
self.takeoff(20, mode='GUIDED')
m = self.assert_receive_message('VFR_HUD')
initial_heading = m.heading
self.progress("Ensuring initial heading is steady")
target = initial_heading
command(
mavutil.mavlink.MAV_CMD_CONDITION_YAW,
p1=target,
p2=10,
p3=1,
p4=0,
)
self.wait_heading(target, minimum_duration=2, timeout=50)
self.wait_yaw_speed(0)
degsecond = 2
def rate_watcher(mav, m):
if m.get_type() != 'ATTITUDE':
return
if abs(math.degrees(m.yawspeed)) > 5*degsecond:
raise NotAchievedException("Moved too fast (%f>%f)" %
(math.degrees(m.yawspeed), 5*degsecond))
self.install_message_hook_context(rate_watcher)
self.progress("Yaw CW 60 degrees")
target = initial_heading + 60
part_way_target = initial_heading + 10
command(
mavutil.mavlink.MAV_CMD_CONDITION_YAW,
p1=target,
p2=degsecond,
p3=1,
p4=0,
)
self.wait_heading(part_way_target)
self.wait_heading(target, minimum_duration=2)
self.progress("Yaw CCW 60 degrees")
target = initial_heading
part_way_target = initial_heading + 30
command(
mavutil.mavlink.MAV_CMD_CONDITION_YAW,
p1=target,
p2=degsecond,
p3=-1,
p4=0,
)
self.wait_heading(part_way_target)
self.wait_heading(target, minimum_duration=2)
self.disarm_vehicle(force=True)
self.reboot_sitl()
def MAV_CMD_CONDITION_YAW(self):
'''Test response to MAV_CMD_CONDITION_YAW via mavlink'''
self.context_push()
self._MAV_CMD_CONDITION_YAW(self.run_cmd_int)
self.context_pop()
self.context_push()
self._MAV_CMD_CONDITION_YAW(self.run_cmd)
self.context_pop()
def GroundEffectCompensation_touchDownExpected(self):
'''Test EKF's handling of touchdown-expected'''
self.zero_throttle()
self.change_mode('ALT_HOLD')
self.set_parameter("LOG_FILE_DSRMROT", 1)
self.progress("Making sure we'll have a short log to look at")
self.wait_ready_to_arm()
self.arm_vehicle()
self.disarm_vehicle()
self.start_subtest("Make sure touchdown-expected duration is about right")
self.takeoff(20, mode='ALT_HOLD')
self.change_mode('LAND')
self.wait_disarmed()
durations = self.get_touchdownexpected_durations_from_current_onboard_log(ignore_multi=True)
self.progress("touchdown-durations: %s" % str(durations))
duration = durations[-1]
expected = 23
if abs(duration - expected) > 5:
raise NotAchievedException("Was expecting roughly %fs of touchdown expected, got %f" % (expected, duration))
def upload_square_mission_items_around_location(self, loc):
alt = 20
loc.alt = alt
items = [
(mavutil.mavlink.MAV_CMD_NAV_TAKEOFF, 0, 0, alt)
]
for (ofs_n, ofs_e) in (20, 20), (20, -20), (-20, -20), (-20, 20), (20, 20):
items.append((mavutil.mavlink.MAV_CMD_NAV_WAYPOINT, ofs_n, ofs_e, alt))
items.append((mavutil.mavlink.MAV_CMD_NAV_RETURN_TO_LAUNCH, 0, 0, 0))
self.upload_simple_relhome_mission(items)
def RefindGPS(self):
'''Refind the GPS and attempt to RTL rather than continue to land'''
self.progress("arm the vehicle and takeoff in Guided")
self.takeoff(50, mode='GUIDED')
self.progress("fly 50m North (or whatever)")
old_pos = self.assert_receive_message('GLOBAL_POSITION_INT')
self.fly_guided_move_global_relative_alt(50, 0, 50)
self.set_parameter('GPS1_TYPE', 0)
self.drain_mav()
tstart = self.get_sim_time()
while True:
if self.get_sim_time_cached() - tstart > 30 and self.mode_is('LAND'):
self.progress("Bug not reproduced")
break
m = self.assert_receive_message('GLOBAL_POSITION_INT', verbose=True)
pos_delta = self.get_distance_int(old_pos, m)
self.progress("Distance: %f" % pos_delta)
if pos_delta < 5:
raise NotAchievedException("Bug reproduced - returned to near origin")
self.set_parameter('GPS1_TYPE', 1)
self.do_RTL()
def GPSForYaw(self):
'''Moving baseline GPS yaw'''
self.load_default_params_file("copter-gps-for-yaw.parm")
self.reboot_sitl()
self.wait_gps_fix_type_gte(6, message_type="GPS2_RAW", verbose=True)
m = self.assert_receive_message("GPS2_RAW", very_verbose=True)
want = 27000
if abs(m.yaw - want) > 500:
raise NotAchievedException("Expected to get GPS-from-yaw (want %f got %f)" % (want, m.yaw))
self.wait_ready_to_arm()
def SMART_RTL_EnterLeave(self):
'''check SmartRTL behaviour when entering/leaving'''
self.upload_simple_relhome_mission([
(mavutil.mavlink.MAV_CMD_NAV_TAKEOFF, 0, 0, 10),
(mavutil.mavlink.MAV_CMD_NAV_RETURN_TO_LAUNCH, 0, 0, 0),
])
self.set_parameter('AUTO_OPTIONS', 3)
self.change_mode('AUTO')
self.wait_ready_to_arm()
self.change_mode('ALT_HOLD')
self.change_mode('SMART_RTL')
self.change_mode('ALT_HOLD')
self.change_mode('SMART_RTL')
def SMART_RTL_Repeat(self):
'''Test whether Smart RTL catches the repeat'''
self.takeoff(alt_min=10, mode='GUIDED')
self.set_rc(3, 1500)
self.change_mode("CIRCLE")
self.delay_sim_time(1300)
self.change_mode("SMART_RTL")
self.wait_disarmed()
def GPSForYawCompassLearn(self):
'''Moving baseline GPS yaw - with compass learning'''
self.context_push()
self.load_default_params_file("copter-gps-for-yaw.parm")
self.set_parameter("EK3_SRC1_YAW", 3)
self.reboot_sitl()
self.wait_gps_fix_type_gte(6, message_type="GPS2_RAW", verbose=True)
self.wait_ready_to_arm()
self.takeoff(10, mode='GUIDED')
tstart = self.get_sim_time()
compass_learn_set = False
while True:
delta_t = self.get_sim_time_cached() - tstart
if delta_t > 30:
break
if not compass_learn_set and delta_t > 10:
self.set_parameter("COMPASS_LEARN", 3)
compass_learn_set = True
self.check_attitudes_match()
self.delay_sim_time(1)
self.do_RTL()
self.context_pop()
self.reboot_sitl()
def AP_Avoidance(self):
'''ADSB-based avoidance'''
self.set_parameters({
"AVD_ENABLE": 1,
"ADSB_TYPE": 1,
"AVD_F_ACTION": 2,
})
self.reboot_sitl()
self.wait_ready_to_arm()
here = self.mav.location()
self.context_push()
self.start_subtest("F_ALT_MIN zero - disabled, can't arm in face of threat")
self.set_parameters({
"AVD_F_ALT_MIN": 0,
})
self.wait_ready_to_arm()
self.test_adsb_send_threatening_adsb_message(here)
self.delay_sim_time(1)
self.try_arm(result=False,
expect_msg="ADSB threat detected")
self.wait_ready_to_arm(timeout=60)
self.context_pop()
self.start_subtest("F_ALT_MIN 16m relative - arm in face of threat")
self.context_push()
self.set_parameters({
"AVD_F_ALT_MIN": int(16 + here.alt),
})
self.wait_ready_to_arm()
self.test_adsb_send_threatening_adsb_message(here)
self.arm_vehicle()
self.disarm_vehicle()
self.context_pop()
def PAUSE_CONTINUE(self):
'''Test MAV_CMD_DO_PAUSE_CONTINUE in AUTO mode'''
self.load_mission(filename="copter_mission.txt", strict=False)
self.set_parameter(name="AUTO_OPTIONS", value=3)
self.change_mode(mode="AUTO")
self.wait_ready_to_arm()
self.arm_vehicle()
self.wait_current_waypoint(wpnum=3, timeout=500)
self.send_pause_command()
self.wait_groundspeed(speed_min=0, speed_max=1, minimum_duration=5)
self.send_resume_command()
self.wait_current_waypoint(wpnum=4, timeout=500)
self.send_pause_command()
self.wait_groundspeed(speed_min=0, speed_max=1, minimum_duration=5)
self.send_resume_command()
self.wait_current_waypoint(wpnum=5, timeout=500)
self.send_pause_command()
self.send_pause_command()
self.wait_groundspeed(speed_min=0, speed_max=1, minimum_duration=5)
self.send_resume_command()
self.send_resume_command()
self.wait_disarmed(timeout=500)
def PAUSE_CONTINUE_GUIDED(self):
'''Test MAV_CMD_DO_PAUSE_CONTINUE in GUIDED mode'''
self.start_subtest("Started test for Pause/Continue in GUIDED mode with LOCATION!")
self.change_mode(mode="GUIDED")
self.wait_ready_to_arm()
self.arm_vehicle()
self.set_parameter(name="GUID_TIMEOUT", value=120)
self.user_takeoff(alt_min=30)
location = self.home_relative_loc_ne(n=300, e=0)
target_typemask = MAV_POS_TARGET_TYPE_MASK.POS_ONLY
self.mav.mav.set_position_target_global_int_send(
0,
1,
1,
mavutil.mavlink.MAV_FRAME_GLOBAL_RELATIVE_ALT_INT,
target_typemask | MAV_POS_TARGET_TYPE_MASK.LAST_BYTE,
int(location.lat * 1e7),
int(location.lng * 1e7),
30,
0,
0,
0,
0,
0,
0,
0,
0)
self.wait_distance_to_home(distance_min=100, distance_max=150, timeout=120)
self.send_pause_command()
self.wait_groundspeed(speed_min=0, speed_max=1, minimum_duration=5)
self.send_resume_command()
self.wait_location(loc=location, timeout=120)
self.end_subtest("Ended test for Pause/Continue in GUIDED mode with LOCATION!")
self.start_subtest("Started test for Pause/Continue in GUIDED mode with DESTINATION!")
self.guided_achieve_heading(heading=270)
location = self.offset_location_ne(location=self.mav.location(), metres_north=0, metres_east=-300)
self.mav.mav.set_position_target_global_int_send(
0,
1,
1,
mavutil.mavlink.MAV_FRAME_GLOBAL_RELATIVE_ALT_INT,
MAV_POS_TARGET_TYPE_MASK.POS_ONLY,
int(location.lat*1e7),
int(location.lng*1e7),
30,
0,
0,
0,
0,
0,
0,
0,
0)
self.wait_location(loc=location, accuracy=200, timeout=120)
self.send_pause_command()
self.wait_groundspeed(speed_min=0, speed_max=1, minimum_duration=5)
self.send_resume_command()
self.wait_location(loc=location, timeout=120)
self.end_subtest("Ended test for Pause/Continue in GUIDED mode with DESTINATION!")
self.start_subtest("Started test for Pause/Continue in GUIDED mode with VELOCITY!")
vx, vy, vz_up = (5, 5, 0)
self.test_guided_local_velocity_target(vx=vx, vy=vy, vz_up=vz_up, timeout=10)
self.wait_for_local_velocity(vx=vx, vy=vy, vz_up=vz_up, timeout=10)
self.send_pause_command()
self.wait_for_local_velocity(vx=0, vy=0, vz_up=0, timeout=10)
self.send_resume_command()
self.wait_for_local_velocity(vx=vx, vy=vy, vz_up=vz_up, timeout=10)
self.test_guided_local_velocity_target(vx=0, vy=0, vz_up=0, timeout=10)
self.wait_for_local_velocity(vx=0, vy=0, vz_up=0, timeout=10)
self.end_subtest("Ended test for Pause/Continue in GUIDED mode with VELOCITY!")
self.start_subtest("Started test for Pause/Continue in GUIDED mode with ACCELERATION!")
ax, ay, az_up = (1, 1, 0)
target_typemask = (MAV_POS_TARGET_TYPE_MASK.POS_IGNORE | MAV_POS_TARGET_TYPE_MASK.VEL_IGNORE |
MAV_POS_TARGET_TYPE_MASK.YAW_IGNORE | MAV_POS_TARGET_TYPE_MASK.YAW_RATE_IGNORE)
self.mav.mav.set_position_target_local_ned_send(
0,
1,
1,
mavutil.mavlink.MAV_FRAME_LOCAL_NED,
target_typemask | MAV_POS_TARGET_TYPE_MASK.LAST_BYTE,
0,
0,
0,
0,
0,
0,
ax,
ay,
-az_up,
0,
0,
)
self.wait_for_local_velocity(vx=5, vy=5, vz_up=0, timeout=10)
self.send_pause_command()
self.wait_for_local_velocity(vx=0, vy=0, vz_up=0, timeout=10)
self.send_resume_command()
self.wait_for_local_velocity(vx=5, vy=5, vz_up=0, timeout=10)
self.test_guided_local_velocity_target(vx=0, vy=0, vz_up=0, timeout=10)
self.wait_for_local_velocity(vx=0, vy=0, vz_up=0, timeout=10)
self.end_subtest("Ended test for Pause/Continue in GUIDED mode with ACCELERATION!")
self.start_subtest("Started test for Pause/Continue in GUIDED mode with POSITION and VELOCITY and ACCELERATION!")
self.guided_achieve_heading(heading=0)
x, y, z_up = (-300, 0, 30)
target_typemask = (MAV_POS_TARGET_TYPE_MASK.YAW_IGNORE | MAV_POS_TARGET_TYPE_MASK.YAW_RATE_IGNORE)
self.mav.mav.set_position_target_local_ned_send(
0,
1,
1,
mavutil.mavlink.MAV_FRAME_LOCAL_NED,
target_typemask | MAV_POS_TARGET_TYPE_MASK.LAST_BYTE,
x,
y,
-z_up,
0,
0,
0,
0,
0,
0,
0,
0,
)
self.wait_distance_to_local_position(local_position=(x, y, -z_up), distance_min=400, distance_max=450, timeout=120)
self.send_pause_command()
self.wait_for_local_velocity(0, 0, 0, timeout=10)
self.send_resume_command()
self.wait_distance_to_local_position(local_position=(x, y, -z_up), distance_min=0, distance_max=10, timeout=120)
self.end_subtest("Ended test for Pause/Continue in GUIDED mode with POSITION and VELOCITY and ACCELERATION!")
self.do_RTL(timeout=120)
def DO_CHANGE_SPEED(self):
'''Change speed during mission using waypoint items'''
self.load_mission("mission.txt", strict=False)
self.set_parameters({
"AUTO_OPTIONS": 3,
"ATC_ANGLE_MAX": 45,
})
self.change_mode('AUTO')
self.wait_ready_to_arm()
self.arm_vehicle()
self.wait_current_waypoint(1)
self.wait_groundspeed(
3.5, 4.5,
minimum_duration=5,
timeout=60,
)
self.wait_current_waypoint(3)
self.wait_groundspeed(
14.5, 15.5,
minimum_duration=10,
timeout=60,
)
self.wait_current_waypoint(5)
self.wait_groundspeed(
9.5, 11.5,
minimum_duration=10,
timeout=60,
)
self.set_parameter("ATC_ANGLE_MAX", 60)
self.wait_current_waypoint(7)
self.wait_groundspeed(
15.5, 16.5,
minimum_duration=10,
timeout=60,
)
self.wait_disarmed()
def AUTO_LAND_TO_BRAKE(self):
'''ensure terrain altitude is taken into account when braking'''
self.set_parameters({
"PLND_ACC_P_NSE": 2.500000,
"PLND_ALT_MAX": 8.000000,
"PLND_ALT_MIN": 0.750000,
"PLND_BUS": -1,
"PLND_CAM_POS_X": 0.000000,
"PLND_CAM_POS_Y": 0.000000,
"PLND_CAM_POS_Z": 0.000000,
"PLND_ENABLED": 1,
"PLND_EST_TYPE": 1,
"PLND_LAG": 0.020000,
"PLND_LAND_OFS_X": 0.000000,
"PLND_LAND_OFS_Y": 0.000000,
"PLND_OPTIONS": 0,
"PLND_RET_BEHAVE": 0,
"PLND_RET_MAX": 4,
"PLND_STRICT": 1,
"PLND_TIMEOUT": 4.000000,
"PLND_TYPE": 4,
"PLND_XY_DIST_MAX": 2.500000,
"PLND_YAW_ALIGN": 0.000000,
"SIM_PLD_ALT_LMT": 15.000000,
"SIM_PLD_DIST_LMT": 10.000000,
"SIM_PLD_ENABLE": 1,
"SIM_PLD_HEIGHT": 0,
"SIM_PLD_LAT": -20.558929,
"SIM_PLD_LON": -47.415035,
"SIM_PLD_RATE": 100,
"SIM_PLD_TYPE": 1,
"SIM_PLD_YAW": 87,
"SIM_SONAR_SCALE": 12,
})
self.set_analog_rangefinder_parameters()
self.load_mission('mission.txt')
self.customise_SITL_commandline([
"--home", self.sitl_home_string_from_mission("mission.txt"),
])
self.set_parameter('AUTO_OPTIONS', 3)
self.change_mode('AUTO')
self.wait_ready_to_arm()
self.arm_vehicle()
self.wait_current_waypoint(7)
self.wait_altitude(10, 15, relative=True, timeout=60)
self.change_mode('BRAKE')
self.wait_altitude(10, 15, relative=True, minimum_duration=20)
self.change_mode('AUTO')
self.wait_disarmed()
def MAVLandedStateTakeoff(self):
'''check EXTENDED_SYS_STATE message'''
ex = None
try:
self.set_message_rate_hz(id=mavutil.mavlink.MAVLINK_MSG_ID_EXTENDED_SYS_STATE, rate_hz=1)
self.wait_extended_sys_state(vtol_state=mavutil.mavlink.MAV_VTOL_STATE_MC,
landed_state=mavutil.mavlink.MAV_LANDED_STATE_ON_GROUND, timeout=10)
self.load_mission(filename="copter_mission.txt")
self.set_parameter(name="AUTO_OPTIONS", value=3)
self.change_mode(mode="AUTO")
self.wait_ready_to_arm()
self.arm_vehicle()
self.wait_extended_sys_state(vtol_state=mavutil.mavlink.MAV_VTOL_STATE_MC,
landed_state=mavutil.mavlink.MAV_LANDED_STATE_TAKEOFF, timeout=30)
self.wait_extended_sys_state(vtol_state=mavutil.mavlink.MAV_VTOL_STATE_MC,
landed_state=mavutil.mavlink.MAV_LANDED_STATE_IN_AIR, timeout=60)
self.land_and_disarm()
except Exception as e:
self.print_exception_caught(e)
ex = e
self.set_message_rate_hz(mavutil.mavlink.MAVLINK_MSG_ID_EXTENDED_SYS_STATE, -1)
if ex is not None:
raise ex
def ATTITUDE_FAST(self):
'''ensure that when ATTITUDE_FAST is set we get many messages'''
log_bitmask_old = int(self.get_parameter('LOG_BITMASK'))
log_bitmask_new = (log_bitmask_old | (1 << 0)) & ~(1 << 12)
path = self.generate_rate_sample_log(log_bitmask=log_bitmask_new)
self.check_dflog_message_rates(path, {
'ANG': 400,
})
def BaseLoggingRates(self):
'''ensure messages come out at specific rates'''
path = self.generate_rate_sample_log()
self.check_dflog_message_rates(path, {
"ATT": 10,
"IMU": 25,
})
def FETtecESC_flight(self):
'''fly with servo outputs from FETtec ESC'''
self.start_subtest("FETtec ESC flight")
num_wp = self.load_mission("copter_mission.txt", strict=False)
self.fly_loaded_mission(num_wp)
def FETtecESC_esc_power_checks(self):
'''Make sure state machine copes with ESCs rebooting'''
self.start_subtest("FETtec ESC reboot")
self.wait_ready_to_arm()
self.context_collect('STATUSTEXT')
self.progress("Turning off an ESC off ")
mask = int(self.get_parameter("SIM_FTOWESC_POW"))
for mot_id_to_kill in 1, 2:
self.progress("Turning ESC=%u off" % mot_id_to_kill)
self.set_parameter("SIM_FTOWESC_POW", mask & ~(1 << mot_id_to_kill))
self.delay_sim_time(1)
self.assert_prearm_failure("are not running")
self.progress("Turning it back on")
self.set_parameter("SIM_FTOWESC_POW", mask)
self.wait_ready_to_arm()
self.progress("Turning ESC=%u off (again)" % mot_id_to_kill)
self.set_parameter("SIM_FTOWESC_POW", mask & ~(1 << mot_id_to_kill))
self.delay_sim_time(1)
self.assert_prearm_failure("are not running")
self.progress("Turning it back on")
self.set_parameter("SIM_FTOWESC_POW", mask)
self.wait_ready_to_arm()
self.progress("Turning all ESCs off")
self.set_parameter("SIM_FTOWESC_POW", 0)
self.delay_sim_time(1)
self.assert_prearm_failure("are not running")
self.progress("Turning them back on")
self.set_parameter("SIM_FTOWESC_POW", mask)
self.wait_ready_to_arm()
def fettec_assert_bad_mask(self, mask):
'''assert the mask is bad for fettec driver'''
self.start_subsubtest("Checking mask (%s) is bad" % (mask,))
self.context_push()
self.set_parameter("SERVO_FTW_MASK", mask)
self.reboot_sitl()
self.delay_sim_time(12)
tstart = self.get_sim_time()
while True:
if self.get_sim_time_cached() - tstart > 20:
raise NotAchievedException("Expected mask to be only problem within 20 seconds")
try:
self.assert_prearm_failure("Invalid motor mask")
break
except NotAchievedException:
self.delay_sim_time(1)
self.context_pop()
self.reboot_sitl()
def fettec_assert_good_mask(self, mask):
'''assert the mask is bad for fettec driver'''
self.start_subsubtest("Checking mask (%s) is good" % (mask,))
self.context_push()
self.set_parameter("SERVO_FTW_MASK", mask)
self.reboot_sitl()
self.wait_ready_to_arm()
self.context_pop()
self.reboot_sitl()
def FETtecESC_safety_switch(self):
mot = self.find_first_set_bit(int(self.get_parameter("SERVO_FTW_MASK"))) + 1
self.wait_esc_telem_rpm(mot, 0, 0)
self.wait_ready_to_arm()
self.context_push()
self.set_parameter("DISARM_DELAY", 0)
self.arm_vehicle()
self.wait_esc_telem_rpm(
esc=mot,
rpm_min=17640,
rpm_max=17640,
minimum_duration=2,
timeout=5,
)
self.set_safetyswitch_on()
self.wait_esc_telem_rpm(mot, 0, 0)
self.set_safetyswitch_off()
self.wait_esc_telem_rpm(
esc=mot,
rpm_min=17640,
rpm_max=17640,
minimum_duration=2,
timeout=5,
)
self.context_pop()
self.wait_disarmed()
def FETtecESC_btw_mask_checks(self):
'''ensure prearm checks work as expected'''
for bad_mask in [0b1000000000000000, 0b10100000000000000]:
self.fettec_assert_bad_mask(bad_mask)
for good_mask in [0b00001, 0b00101, 0b110000000000]:
self.fettec_assert_good_mask(good_mask)
def FETtecESC(self):
'''Test FETtecESC'''
self.set_parameters({
"SERIAL5_PROTOCOL": 38,
"SERVO_FTW_MASK": 0b11101000,
"SIM_FTOWESC_ENA": 1,
"SERVO1_FUNCTION": 0,
"SERVO2_FUNCTION": 0,
"SERVO3_FUNCTION": 0,
"SERVO4_FUNCTION": 33,
"SERVO5_FUNCTION": 0,
"SERVO6_FUNCTION": 34,
"SERVO7_FUNCTION": 35,
"SERVO8_FUNCTION": 36,
"SIM_ESC_TELEM": 0,
})
self.customise_SITL_commandline(["--serial5=sim:fetteconewireesc"])
self.FETtecESC_safety_switch()
self.FETtecESC_esc_power_checks()
self.FETtecESC_btw_mask_checks()
self.FETtecESC_flight()
def PerfInfo(self):
'''Test Scheduler PerfInfo output'''
self.set_parameter('SCHED_OPTIONS', 1)
content = self.fetch_file_via_ftp("@SYS/tasks.txt")
self.delay_sim_time(5)
content = self.fetch_file_via_ftp("@SYS/tasks.txt")
self.progress("Got content (%s)" % str(content))
lines = content.split("\n")
if not lines[0].startswith("TasksV2"):
raise NotAchievedException("Expected TasksV2 as first line first not (%s)" % lines[0])
if not lines[-2].startswith("AP_Vehicle::update_arming"):
raise NotAchievedException("Expected EFI last not (%s)" % lines[-2])
def RTL_TO_RALLY(self, target_system=1, target_component=1):
'''Check RTL to rally point'''
self.wait_ready_to_arm()
rally_loc = self.home_relative_loc_ne(50, -25)
rally_alt = 37
items = [
self.mav.mav.mission_item_int_encode(
target_system,
target_component,
0,
mavutil.mavlink.MAV_FRAME_GLOBAL_RELATIVE_ALT,
mavutil.mavlink.MAV_CMD_NAV_RALLY_POINT,
0,
0,
0,
0,
0,
0,
int(rally_loc.lat * 1e7),
int(rally_loc.lng * 1e7),
rally_alt,
mavutil.mavlink.MAV_MISSION_TYPE_RALLY),
]
self.upload_using_mission_protocol(
mavutil.mavlink.MAV_MISSION_TYPE_RALLY,
items
)
self.set_parameters({
'RALLY_INCL_HOME': 0,
})
self.takeoff(10)
self.change_mode('RTL')
self.wait_location(rally_loc)
self.assert_altitude(rally_alt, relative=True)
self.progress("Ensuring we're descending")
self.wait_altitude(20, 25, relative=True)
self.change_mode('LOITER')
self.progress("Flying home")
self.clear_mission(mavutil.mavlink.MAV_MISSION_TYPE_RALLY)
self.change_mode('RTL')
self.wait_disarmed()
self.assert_at_home()
def NoRCOnBootPreArmFailure(self):
'''Ensure we can't arm with no RC on boot if THR_FS_VALUE set'''
self.context_push()
for rc_failure_mode in 1, 2:
self.set_parameters({
"SIM_RC_FAIL": rc_failure_mode,
})
self.reboot_sitl()
if rc_failure_mode == 1:
self.assert_prearm_failure("RC not found",
other_prearm_failures_fatal=False)
elif rc_failure_mode == 2:
self.assert_prearm_failure("Throttle below failsafe",
other_prearm_failures_fatal=False)
self.context_pop()
self.reboot_sitl()
def IMUConsistency(self):
'''test IMUs must be consistent with one another'''
self.wait_ready_to_arm()
self.start_subsubtest("prearm checks for accel inconsistency")
self.context_push()
self.set_parameters({
"SIM_ACC1_BIAS_X": 5,
})
self.assert_prearm_failure("Accels inconsistent")
self.context_pop()
tstart = self.get_sim_time()
self.wait_ready_to_arm()
if self.get_sim_time() - tstart < 8:
raise NotAchievedException("Should take 10 seconds to become armableafter IMU upset")
self.start_subsubtest("prearm checks for gyro inconsistency")
self.context_push()
self.set_parameters({
"SIM_GYR1_BIAS_X": math.radians(10),
})
self.assert_prearm_failure("Gyros inconsistent")
self.context_pop()
tstart = self.get_sim_time()
self.wait_ready_to_arm()
if self.get_sim_time() - tstart < 8:
raise NotAchievedException("Should take 10 seconds to become armableafter IMU upset")
def Sprayer(self):
"""Test sprayer functionality."""
self.context_push()
rc_ch = 9
pump_ch = 5
spinner_ch = 6
pump_ch_min = 1050
pump_ch_trim = 1520
pump_ch_max = 1950
spinner_ch_min = 975
spinner_ch_trim = 1510
spinner_ch_max = 1975
self.set_parameters({
"SPRAY_ENABLE": 1,
"SERVO%u_FUNCTION" % pump_ch: 22,
"SERVO%u_MIN" % pump_ch: pump_ch_min,
"SERVO%u_TRIM" % pump_ch: pump_ch_trim,
"SERVO%u_MAX" % pump_ch: pump_ch_max,
"SERVO%u_FUNCTION" % spinner_ch: 23,
"SERVO%u_MIN" % spinner_ch: spinner_ch_min,
"SERVO%u_TRIM" % spinner_ch: spinner_ch_trim,
"SERVO%u_MAX" % spinner_ch: spinner_ch_max,
"SIM_SPR_ENABLE": 1,
"SIM_SPR_PUMP": pump_ch,
"SIM_SPR_SPIN": spinner_ch,
"RC%u_OPTION" % rc_ch: 15,
"LOG_DISARMED": 1,
})
self.reboot_sitl()
self.wait_ready_to_arm()
self.arm_vehicle()
self.progress("test boot-up state - it's zero-output!")
self.wait_servo_channel_value(spinner_ch, 0)
self.wait_servo_channel_value(pump_ch, 0)
self.progress("Enable sprayer")
self.set_rc(rc_ch, 2000)
self.progress("Testing zero-speed state")
self.wait_servo_channel_value(spinner_ch, spinner_ch_min)
self.wait_servo_channel_value(pump_ch, pump_ch_min)
self.progress("Testing turning it off")
self.set_rc(rc_ch, 1000)
self.wait_servo_channel_value(spinner_ch, spinner_ch_min)
self.wait_servo_channel_value(pump_ch, pump_ch_min)
self.progress("Testing turning it back on")
self.set_rc(rc_ch, 2000)
self.wait_servo_channel_value(spinner_ch, spinner_ch_min)
self.wait_servo_channel_value(pump_ch, pump_ch_min)
self.takeoff(30, mode='LOITER')
self.progress("Testing speed-ramping")
self.set_rc(1, 1700)
self.wait_servo_channel_value(
pump_ch,
1458,
timeout=60,
comparator=lambda x, y : abs(x-y) < 5
)
self.progress("Turning it off again")
self.set_rc(rc_ch, 1000)
self.wait_servo_channel_value(spinner_ch, spinner_ch_min)
self.wait_servo_channel_value(pump_ch, pump_ch_min)
self.start_subtest("Checking mavlink commands")
self.progress("Starting Sprayer")
self.run_cmd_int(mavutil.mavlink.MAV_CMD_DO_SPRAYER, p1=1)
self.progress("Testing speed-ramping")
self.wait_servo_channel_value(
pump_ch,
1458,
timeout=60,
comparator=lambda x, y : abs(x-y) < 5
)
self.start_subtest("Stopping Sprayer")
self.run_cmd(mavutil.mavlink.MAV_CMD_DO_SPRAYER, p1=0)
self.wait_servo_channel_value(pump_ch, pump_ch_min)
self.disarm_vehicle(force=True)
self.context_pop()
self.reboot_sitl()
self.progress("Sprayer OK")
def tests1a(self):
'''return list of all tests'''
ret = super(AutoTestCopter, self).tests()
ret.extend([
self.NavDelayTakeoffAbsTime,
self.NavDelayAbsTime,
self.NavDelay,
self.GuidedSubModeChange,
self.MAV_CMD_CONDITION_YAW,
self.LoiterToAlt,
self.PayloadPlaceMission,
self.PayloadPlaceMissionOpenGripper,
self.PrecisionLoiterCompanion,
self.Landing,
self.PrecisionLanding,
self.SetModesViaModeSwitch,
self.BackupFence,
self.SetModesViaAuxSwitch,
self.AuxSwitchOptions,
self.AuxFunctionsInMission,
self.AutoTune,
self.AutoTuneYawD,
self.NoRCOnBootPreArmFailure,
])
return ret
def tests1b(self):
'''return list of all tests'''
ret = ([
self.ThrowMode,
self.BrakeMode,
self.RecordThenPlayMission,
self.ThrottleFailsafe,
self.ThrottleFailsafePassthrough,
self.GCSFailsafe,
self.CustomController,
self.WPArcs,
self.WPArcs2,
])
return ret
def tests1c(self):
'''return list of all tests'''
ret = ([
self.BatteryFailsafe,
self.BatteryMissing,
self.VibrationFailsafe,
self.EK3AccelBias,
self.StabilityPatch,
self.OBSTACLE_DISTANCE_3D,
self.AC_Avoidance_Proximity,
self.AC_Avoidance_Proximity_AVOID_ALT_MIN,
self.AC_Avoidance_Fence,
self.AC_Avoidance_Beacon,
self.AvoidanceAltFence,
self.BaroWindCorrection,
self.SetpointGlobalPos,
self.ThrowDoubleDrop,
self.SetpointGlobalVel,
self.SetpointBadVel,
self.SplineTerrain,
self.TakeoffCheck,
self.GainBackoffTakeoff,
])
return ret
def tests1d(self):
'''return list of all tests'''
ret = ([
self.HorizontalFence,
self.HorizontalAvoidFence,
self.MaxAltFence,
self.MaxAltFenceAvoid,
self.MinAltFence,
self.MinAltFenceAvoid,
self.FenceFloorEnabledLanding,
self.FenceFloorAutoDisableLanding,
self.FenceFloorAutoEnableOnArming,
self.FenceMargin,
self.FenceUpload_MissionItem,
self.AutoTuneSwitch,
self.AutoTuneAux,
self.GPSGlitchLoiter,
self.GPSGlitchLoiter2,
self.GPSGlitchAuto,
self.ModeAltHold,
self.ModeLoiter,
self.SimpleMode,
self.SuperSimpleCircle,
self.ModeCircle,
self.MagFail,
self.OpticalFlow,
self.OpticalFlowLocation,
self.OpticalFlowLimits,
self.OpticalFlowCalibration,
self.MotorFail,
self.ModeFlip,
self.CopterMission,
self.TakeoffAlt,
self.SplineLastWaypoint,
self.Gripper,
self.TestLocalHomePosition,
self.TestGripperMission,
self.VisionPosition,
self.ATTITUDE_FAST,
self.BaseLoggingRates,
self.BodyFrameOdom,
self.GPSViconSwitching,
])
return ret
def tests1e(self):
'''return list of all tests'''
ret = ([
self.BeaconPosition,
self.RTLSpeed,
self.Mount,
self.MountYawVehicleForMountROI,
self.MAV_CMD_DO_MOUNT_CONTROL,
self.MAV_CMD_DO_GIMBAL_MANAGER_CONFIGURE,
self.AutoYawDO_MOUNT_CONTROL,
self.MountPOIFromAuxFunction,
self.Button,
self.ShipTakeoff,
self.RangeFinder,
self.BaroDrivers,
self.SurfaceTracking,
self.Parachute,
self.ParameterChecks,
self.ManualThrottleModeChange,
self.MANUAL_CONTROL,
self.ModeZigZag,
self.PosHoldTakeOff,
self.ModeFollow,
self.ModeFollow_with_FOLLOW_TARGET,
self.RangeFinderDrivers,
self.FlyRangeFinderMAVlink,
self.FlyRangeFinderSITL,
self.RangeFinderDriversMaxAlt_LightwareSerial,
self.RangeFinderDriversMaxAlt_AinsteinLRD1,
self.RangeFinderDriversMaxAlt_AinsteinLRD1_v19,
self.RangeFinderDriversLongRange,
self.RangeFinderSITLLongRange,
self.MaxBotixI2CXL,
self.MAVProximity,
self.ParameterValidation,
self.AltTypes,
self.PAUSE_CONTINUE,
self.PAUSE_CONTINUE_GUIDED,
self.RichenPower,
self.IE24,
self.LoweheiserAuto,
self.LoweheiserManual,
self.MAVLandedStateTakeoff,
self.Weathervane,
self.MAV_CMD_AIRFRAME_CONFIGURATION,
self.MAV_CMD_NAV_LOITER_UNLIM,
self.MAV_CMD_NAV_RETURN_TO_LAUNCH,
self.MAV_CMD_NAV_VTOL_LAND,
self.clear_roi,
self.ReadOnlyDefaults,
])
return ret
def tests2a(self):
'''return list of all tests'''
ret = ([
self.FixedYawCalibration,
self.SITLCompassCalibration,
])
return ret
def ScriptMountPOI(self):
'''test the MountPOI example script'''
self.context_push()
self.install_terrain_handlers_context()
self.set_parameters({
"SCR_ENABLE": 1,
"RC12_OPTION": 300,
})
self.setup_servo_mount()
self.reboot_sitl()
self.set_rc(6, 1300)
self.install_applet_script_context('mount-poi.lua')
self.reboot_sitl()
self.wait_ready_to_arm()
self.context_collect('STATUSTEXT')
self.set_rc(12, 2000)
self.wait_statustext('POI.*-35.*149', check_context=True, regex=True)
self.set_rc(12, 1000)
self.context_pop()
self.reboot_sitl()
def ScriptMountAllModes(self):
'''test location from all mount modes using the scripting backend'''
self.set_parameters({
"SCR_ENABLE": 1,
"MNT1_TYPE": 9,
})
self.reboot_sitl()
self.install_example_script_context('get-target-location-mount-backend.lua')
self.reboot_sitl()
self.takeoff(50)
self.context_collect("STATUSTEXT")
self.start_subtest("Test RETRACT (mode 0)")
self.run_cmd(
mavutil.mavlink.MAV_CMD_DO_MOUNT_CONTROL,
p7=mavutil.mavlink.MAV_MOUNT_MODE_RETRACT,
)
self.wait_statustext("Mode 0: No target", check_context=True)
self.start_subtest("Test NEUTRAL (mode 1)")
self.run_cmd(
mavutil.mavlink.MAV_CMD_DO_MOUNT_CONTROL,
p7=mavutil.mavlink.MAV_MOUNT_MODE_NEUTRAL,
)
self.wait_statustext("Mode 1: No target", check_context=True)
self.start_subtest("Test MAVLINK_TARGETING (mode 2)")
self.run_cmd(
mavutil.mavlink.MAV_CMD_DO_MOUNT_CONTROL,
p7=mavutil.mavlink.MAV_MOUNT_MODE_MAVLINK_TARGETING,
)
self.wait_statustext("Mode 2: No target", check_context=True)
self.start_subtest("Test RC_TARGETING (mode 3)")
self.run_cmd(
mavutil.mavlink.MAV_CMD_DO_MOUNT_CONTROL,
p7=mavutil.mavlink.MAV_MOUNT_MODE_RC_TARGETING,
)
self.wait_statustext("Mode 3: No target", check_context=True)
self.start_subtest("Test GPS_POINT (mode 4)")
roi_lat = -35.363150
roi_lng = 149.165320
roi_alt = 580.0
self.run_cmd_int(
mavutil.mavlink.MAV_CMD_DO_SET_ROI_LOCATION,
p5=int(roi_lat * 1e7),
p6=int(roi_lng * 1e7),
p7=roi_alt,
frame=mavutil.mavlink.MAV_FRAME_GLOBAL_RELATIVE_ALT,
)
expected_roi_lat = f"{roi_lat:.3f}"
expected_roi_lng = f"{roi_lng:.3f}"
self.wait_statustext(
rf"Mode 4:.*{expected_roi_lat}.*{expected_roi_lng}",
check_context=True,
regex=True,
)
self.start_subtest("Test SYSID_TARGET (mode 5)")
self.run_cmd(
mavutil.mavlink.MAV_CMD_DO_SET_ROI_SYSID,
p1=self.mav.source_system,
)
self.wait_statustext("Mode 5: No target", check_context=True)
self.start_subtest("Test HOME_LOCATION (mode 6)")
loc_home = self.poll_home_position()
expected_lat = f"{loc_home.latitude/1e7:.6f}"
expected_lng = f"{loc_home.longitude/1e7:.6f}"
self.run_cmd(
mavutil.mavlink.MAV_CMD_DO_MOUNT_CONTROL,
p7=mavutil.mavlink.MAV_MOUNT_MODE_HOME_LOCATION,
)
self.wait_statustext(
rf"Mode 6:.*{expected_lat}.*{expected_lng}",
check_context=True,
regex=True,
)
self.do_RTL()
def ScriptCopterPosOffsets(self):
'''test the copter-posoffset.lua example script'''
self.context_push()
self.set_parameters({
"SCR_ENABLE": 1,
"AUTO_OPTIONS": 3,
"RC12_OPTION": 300
})
self.reboot_sitl()
self.install_example_script_context('copter-posoffset.lua')
self.reboot_sitl()
self.upload_simple_relhome_mission([
(mavutil.mavlink.MAV_CMD_NAV_TAKEOFF, 0, 0, 20)
])
self.change_mode('LOITER')
self.wait_ready_to_arm()
self.change_mode('AUTO')
self.arm_vehicle()
self.wait_altitude(8, 12, relative=True)
self.set_parameter("PSC_OFS_POS_E", 20)
self.set_rc(12, 2000)
self.wait_distance(18)
self.set_parameter("PSC_OFS_POS_E", 0)
self.wait_distance_to_home(distance_min=0, distance_max=4, timeout=20)
self.set_parameter("PSC_OFS_VEL_N", 5)
self.wait_groundspeed(4.8, 5.2, minimum_duration=5, timeout=20)
self.set_parameter("PSC_OFS_VEL_N", 0)
self.set_rc(12, 1000)
self.do_RTL()
self.context_pop()
self.reboot_sitl()
def AHRSTrimLand(self):
'''test land detector with significant AHRS trim'''
self.context_push()
self.set_parameters({
"SIM_ACC_TRIM_X": 0.12,
"AHRS_TRIM_X": 0.12,
})
self.reboot_sitl()
self.wait_ready_to_arm()
self.takeoff(alt_min=20, mode='LOITER')
self.do_RTL()
self.context_pop()
self.reboot_sitl()
def GainBackoffTakeoff(self):
'''test gain backoff on takeoff'''
self.context_push()
self.progress("Test gains are fully backed off")
self.set_parameters({
"ATC_LAND_R_MULT": 0.0,
"ATC_LAND_P_MULT": 0.0,
"ATC_LAND_Y_MULT": 0.0,
"GCS_PID_MASK" : 7,
"LOG_BITMASK": 180222,
})
self.reboot_sitl()
self.wait_ready_to_arm()
self.change_mode('ALT_HOLD')
class ValidatePDZero(vehicle_test_suite.TestSuite.MessageHook):
'''asserts correct values in PID_TUNING'''
def __init__(self, suite, axis):
super(ValidatePDZero, self).__init__(suite)
self.pid_tuning_count = 0
self.p_sum = 0
self.d_sum = 0
self.i_sum = 0
self.axis = axis
def hook_removed(self):
if self.pid_tuning_count == 0:
raise NotAchievedException("Did not get PID_TUNING")
if self.i_sum == 0:
raise ValueError("I sum is zero")
print(f"ValidatePDZero: PID_TUNING count: {self.pid_tuning_count}")
def process(self, mav, m):
if m.get_type() != 'PID_TUNING' or m.axis != self.axis:
return
self.pid_tuning_count += 1
self.p_sum += m.P
self.d_sum += m.D
self.i_sum += m.I
if self.p_sum > 0:
raise ValueError("P sum is not zero")
if self.d_sum > 0:
raise ValueError("D sum is not zero")
self.progress("Check that PD values are zero")
self.install_message_hook_context(ValidatePDZero(self, mavutil.mavlink.PID_TUNING_ROLL))
self.install_message_hook_context(ValidatePDZero(self, mavutil.mavlink.PID_TUNING_PITCH))
self.install_message_hook_context(ValidatePDZero(self, mavutil.mavlink.PID_TUNING_YAW))
self.arm_vehicle()
self.set_rc(3, 1500)
self.delay_sim_time(2)
self.set_rc(2, 1250)
self.delay_sim_time(5)
self.assert_receive_message('PID_TUNING', timeout=5)
self.set_rc_default()
self.zero_throttle()
self.disarm_vehicle()
self.context_pop()
self.context_push()
self.progress("Test gains are not backed off")
self.set_parameters({
"ATC_LAND_R_MULT": 1.0,
"ATC_LAND_P_MULT": 1.0,
"ATC_LAND_Y_MULT": 1.0,
"GCS_PID_MASK" : 7,
"LOG_BITMASK": 180222,
})
self.reboot_sitl()
self.wait_ready_to_arm()
self.change_mode('ALT_HOLD')
class ValidatePDNonZero(vehicle_test_suite.TestSuite.MessageHook):
'''asserts correct values in PID_TUNING'''
def __init__(self, suite, axis):
super(ValidatePDNonZero, self).__init__(suite)
self.pid_tuning_count = 0
self.p_sum = 0
self.d_sum = 0
self.i_sum = 0
self.axis = axis
def hook_removed(self):
if self.pid_tuning_count == 0:
raise NotAchievedException("Did not get PID_TUNING")
if self.p_sum == 0:
raise ValueError("P sum is zero")
if self.i_sum == 0:
raise ValueError("I sum is zero")
print(f"ValidatePDNonZero: PID_TUNING count: {self.pid_tuning_count}")
def process(self, mav, m):
if m.get_type() != 'PID_TUNING' or m.axis != self.axis:
return
self.pid_tuning_count += 1
self.p_sum += m.P
self.d_sum += m.D
self.i_sum += m.I
self.progress("Check that PD values are non-zero")
self.install_message_hook_context(ValidatePDNonZero(self, mavutil.mavlink.PID_TUNING_ROLL))
self.install_message_hook_context(ValidatePDNonZero(self, mavutil.mavlink.PID_TUNING_PITCH))
self.install_message_hook_context(ValidatePDNonZero(self, mavutil.mavlink.PID_TUNING_YAW))
self.arm_vehicle()
self.set_rc(3, 1500)
self.delay_sim_time(2)
self.set_rc(2, 1250)
self.delay_sim_time(5)
self.assert_receive_message('PID_TUNING', timeout=5)
self.set_rc_default()
self.zero_throttle()
self.disarm_vehicle()
self.context_pop()
self.reboot_sitl()
def SafetySwitch(self):
'''test safety switch behaviour'''
self.wait_ready_to_arm()
self.set_safetyswitch_on()
self.assert_prearm_failure("safety switch")
self.set_safetyswitch_off()
self.wait_ready_to_arm()
self.takeoff(2, mode='LOITER')
self.set_safetyswitch_on()
self.wait_servo_channel_value(1, 0)
self.set_safetyswitch_off()
self.change_mode('LAND')
self.wait_disarmed()
self.run_cmd_int(mavutil.mavlink.MAV_CMD_DO_SET_SAFETY_SWITCH_STATE, mavutil.mavlink.SAFETY_SWITCH_STATE_SAFE)
self.assert_prearm_failure("safety switch")
self.run_cmd_int(mavutil.mavlink.MAV_CMD_DO_SET_SAFETY_SWITCH_STATE, mavutil.mavlink.SAFETY_SWITCH_STATE_DANGEROUS)
self.wait_ready_to_arm()
def ArmSwitchAfterReboot(self):
'''test that the arming switch does not trigger after a reboot'''
self.wait_ready_to_arm()
self.set_parameters({
"RC8_OPTION": 153,
})
self.set_rc(8, 2000)
self.wait_armed()
self.disarm_vehicle()
self.context_collect('STATUSTEXT')
self.reboot_sitl()
tstart = self.get_sim_time()
while True:
if self.get_sim_time_cached() - tstart > 60:
break
if self.armed():
raise NotAchievedException("Armed after reboot with switch high")
armmsg = self.statustext_in_collections('Arm: ')
if armmsg is not None:
raise NotAchievedException("statustext(%s) means we tried to arm" % armmsg.text)
self.progress("Did not arm via arming switfch after a reboot")
def GuidedYawRate(self):
'''ensuer guided yaw rate is not affected by rate of sewt-attitude messages'''
self.takeoff(30, mode='GUIDED')
rates = {}
for rate in 1, 10:
tstart = self.get_sim_time()
interval = 1/rate
yawspeed_rads_sum = 0
yawspeed_rads_count = 0
last_sent = 0
while True:
self.drain_mav()
tnow = self.get_sim_time_cached()
if tnow - last_sent > interval:
self.do_yaw_rate(60)
last_sent = tnow
if tnow - tstart < 5:
continue
yawspeed_rads_sum += self.mav.messages['ATTITUDE'].yawspeed
yawspeed_rads_count += 1
if tnow - tstart > 15:
break
yawspeed_degs = math.degrees(yawspeed_rads_sum / yawspeed_rads_count)
rates[rate] = yawspeed_degs
self.progress("Input rate %u hz: average yaw rate %f deg/s" % (rate, yawspeed_degs))
if rates[10] < rates[1] * 0.95:
raise NotAchievedException("Guided yaw rate slower for higher rate updates")
self.do_RTL()
def test_rplidar(self, sim_device_name, expected_distances):
'''plonks a Copter with a RPLidarA2 in the middle of a simulated field
of posts and checks that the measurements are what we expect.'''
self.set_parameters({
"SERIAL5_PROTOCOL": 11,
"PRX1_TYPE": 5,
})
self.customise_SITL_commandline([
"--serial5=sim:%s:" % sim_device_name,
"--home", "51.8752066,14.6487840,0,0",
])
self.wait_ready_to_arm()
wanting_distances = copy.copy(expected_distances)
tstart = self.get_sim_time()
timeout = 60
while True:
now = self.get_sim_time_cached()
if now - tstart > timeout:
raise NotAchievedException("Did not get all distances")
m = self.mav.recv_match(type="DISTANCE_SENSOR",
blocking=True,
timeout=1)
if m is None:
continue
self.progress("Got (%s)" % str(m))
if m.orientation not in wanting_distances:
continue
if abs(m.current_distance - wanting_distances[m.orientation]) > 5:
self.progress("Wrong distance orient=%u want=%u got=%u" %
(m.orientation,
wanting_distances[m.orientation],
m.current_distance))
continue
self.progress("Correct distance for orient %u (want=%u got=%u)" %
(m.orientation,
wanting_distances[m.orientation],
m.current_distance))
del wanting_distances[m.orientation]
if len(wanting_distances.items()) == 0:
break
def RPLidarA2(self):
'''test Raspberry Pi Lidar A2'''
expected_distances = {
mavutil.mavlink.MAV_SENSOR_ROTATION_NONE: 276,
mavutil.mavlink.MAV_SENSOR_ROTATION_YAW_45: 256,
mavutil.mavlink.MAV_SENSOR_ROTATION_YAW_90: 1130,
mavutil.mavlink.MAV_SENSOR_ROTATION_YAW_135: 1286,
mavutil.mavlink.MAV_SENSOR_ROTATION_YAW_180: 626,
mavutil.mavlink.MAV_SENSOR_ROTATION_YAW_225: 971,
mavutil.mavlink.MAV_SENSOR_ROTATION_YAW_270: 762,
mavutil.mavlink.MAV_SENSOR_ROTATION_YAW_315: 792,
}
self.test_rplidar("rplidara2", expected_distances)
def RPLidarA1(self):
'''test Raspberry Pi Lidar A1'''
return
expected_distances = {
mavutil.mavlink.MAV_SENSOR_ROTATION_NONE: 276,
mavutil.mavlink.MAV_SENSOR_ROTATION_YAW_45: 256,
mavutil.mavlink.MAV_SENSOR_ROTATION_YAW_90: 800,
mavutil.mavlink.MAV_SENSOR_ROTATION_YAW_135: 800,
mavutil.mavlink.MAV_SENSOR_ROTATION_YAW_180: 626,
mavutil.mavlink.MAV_SENSOR_ROTATION_YAW_225: 800,
mavutil.mavlink.MAV_SENSOR_ROTATION_YAW_270: 762,
mavutil.mavlink.MAV_SENSOR_ROTATION_YAW_315: 792,
}
self.test_rplidar("rplidara1", expected_distances)
def BrakeZ(self):
'''check jerk limit correct in Brake mode'''
self.set_parameter('PSC_JERK_D', 3)
self.takeoff(50, mode='GUIDED')
vx, vy, vz_up = (0, 0, -1)
self.test_guided_local_velocity_target(vx=vx, vy=vy, vz_up=vz_up, timeout=10)
self.wait_for_local_velocity(vx=vx, vy=vy, vz_up=vz_up, timeout=10)
self.change_mode('BRAKE')
self.wait_for_local_velocity(vx=0, vy=0, vz_up=0, timeout=10)
self.land_and_disarm()
def MISSION_START(self):
'''test mavlink command MAV_CMD_MISSION_START'''
self.upload_simple_relhome_mission([
(mavutil.mavlink.MAV_CMD_NAV_TAKEOFF, 0, 0, 200),
(mavutil.mavlink.MAV_CMD_NAV_RETURN_TO_LAUNCH, 0, 0, 0),
])
for command in self.run_cmd, self.run_cmd_int:
self.change_mode('LOITER')
self.set_current_waypoint(1)
self.wait_ready_to_arm()
self.arm_vehicle()
self.change_mode('AUTO')
command(mavutil.mavlink.MAV_CMD_MISSION_START)
self.wait_altitude(20, 1000, relative=True)
self.change_mode('RTL')
self.wait_disarmed()
def DO_CHANGE_SPEED_in_guided(self):
'''test Copter DO_CHANGE_SPEED handling in guided mode'''
self.takeoff(20, mode='GUIDED')
new_loc = self.mav.location()
new_loc_offset_n = 2000
new_loc_offset_e = 0
self.location_offset_ne(new_loc, new_loc_offset_n, new_loc_offset_e)
second_loc_offset_n = -1000
second_loc_offset_e = 0
second_loc = self.mav.location()
self.location_offset_ne(second_loc, second_loc_offset_n, second_loc_offset_e)
for (tloc, command) in (new_loc, self.run_cmd), (second_loc, self.run_cmd_int):
self.run_cmd_int(
mavutil.mavlink.MAV_CMD_DO_REPOSITION,
p1=-1,
p2=0,
p3=0,
p4=float("nan"),
p5=int(tloc.lat * 1e7),
p6=int(tloc.lng * 1e7),
p7=tloc.alt,
frame=mavutil.mavlink.MAV_FRAME_GLOBAL,
)
for speed in [2, 10, 4]:
command(
mavutil.mavlink.MAV_CMD_DO_CHANGE_SPEED,
p1=1,
p2=speed,
p3=-1,
p4=0,
)
self.wait_groundspeed(speed-0.2, speed+0.2, minimum_duration=10, timeout=20)
self.disarm_vehicle(force=True)
self.reboot_sitl()
def _MAV_CMD_DO_FLIGHTTERMINATION(self, command):
self.set_parameters({
"MAV_GCS_SYSID": self.mav.source_system,
"DISARM_DELAY": 0,
})
self.wait_ready_to_arm()
self.arm_vehicle()
self.context_collect('STATUSTEXT')
command(mavutil.mavlink.MAV_CMD_DO_FLIGHTTERMINATION, p1=1)
self.wait_disarmed()
self.reboot_sitl()
def MAV_CMD_DO_FLIGHTTERMINATION(self):
'''test MAV_CMD_DO_FLIGHTTERMINATION works on Copter'''
self._MAV_CMD_DO_FLIGHTTERMINATION(self.run_cmd)
self._MAV_CMD_DO_FLIGHTTERMINATION(self.run_cmd_int)
def MAV_CMD_NAV_LOITER_UNLIM(self):
'''ensure MAV_CMD_NAV_LOITER_UNLIM via mavlink works'''
for command in self.run_cmd, self.run_cmd_int:
self.change_mode('STABILIZE')
command(mavutil.mavlink.MAV_CMD_NAV_LOITER_UNLIM)
self.wait_mode('LOITER')
def MAV_CMD_NAV_RETURN_TO_LAUNCH(self):
'''ensure MAV_CMD_NAV_RETURN_TO_LAUNCH via mavlink works'''
for command in self.run_cmd, self.run_cmd_int:
self.change_mode('STABILIZE')
command(mavutil.mavlink.MAV_CMD_NAV_RETURN_TO_LAUNCH)
self.wait_mode('RTL')
def MAV_CMD_NAV_VTOL_LAND(self):
'''ensure MAV_CMD_NAV_LAND via mavlink works'''
for command in self.run_cmd, self.run_cmd_int:
self.change_mode('STABILIZE')
command(mavutil.mavlink.MAV_CMD_NAV_VTOL_LAND)
self.wait_mode('LAND')
self.change_mode('STABILIZE')
command(mavutil.mavlink.MAV_CMD_NAV_LAND)
self.wait_mode('LAND')
def clear_roi(self):
'''ensure three commands that clear ROI are equivalent'''
self.upload_simple_relhome_mission([
(mavutil.mavlink.MAV_CMD_NAV_TAKEOFF, 0, 0, 20),
(mavutil.mavlink.MAV_CMD_NAV_WAYPOINT, 0, 0, 20),
(mavutil.mavlink.MAV_CMD_NAV_WAYPOINT, 200, 0, 20),
(mavutil.mavlink.MAV_CMD_NAV_WAYPOINT, 400, 0, 20),
])
self.set_parameter("AUTO_OPTIONS", 3)
self.change_mode('AUTO')
self.wait_ready_to_arm()
self.arm_vehicle()
home_loc = self.mav.location()
cmd_ids = [
mavutil.mavlink.MAV_CMD_DO_SET_ROI,
mavutil.mavlink.MAV_CMD_DO_SET_ROI_LOCATION,
mavutil.mavlink.MAV_CMD_DO_SET_ROI_NONE,
]
for command in self.run_cmd, self.run_cmd_int:
for cmd_id in cmd_ids:
self.wait_waypoint(2, 2)
self.run_cmd(mavutil.mavlink.MAV_CMD_DO_SET_ROI_LOCATION, p5=home_loc.lat, p6=home_loc.lng, p7=home_loc.alt)
self.wait_heading(180)
self.progress("Clear ROI using %s(%d)" % (command.__name__, cmd_id))
command(cmd_id)
self.wait_heading(0)
self.wait_waypoint(4, 4)
self.set_current_waypoint_using_mav_cmd_do_set_mission_current(seq=2)
self.land_and_disarm()
def start_flying_simple_relhome_mission(self, items):
'''uploads items, changes mode to auto, waits ready to arm and arms
vehicle. If the first item it a takeoff you can expect the
vehicle to fly after this method returns
'''
self.upload_simple_relhome_mission(items)
self.set_parameter("AUTO_OPTIONS", 3)
self.change_mode('AUTO')
self.wait_ready_to_arm()
self.arm_vehicle()
def _MAV_CMD_DO_LAND_START(self, run_cmd):
alt = 5
self.start_flying_simple_relhome_mission([
(mavutil.mavlink.MAV_CMD_NAV_TAKEOFF, 0, 0, alt),
(mavutil.mavlink.MAV_CMD_NAV_WAYPOINT, 200, 0, alt),
(mavutil.mavlink.MAV_CMD_NAV_LAND, 0, 0, 0),
(mavutil.mavlink.MAV_CMD_DO_LAND_START, 0, 0, alt),
(mavutil.mavlink.MAV_CMD_NAV_WAYPOINT, 200, 2000, alt),
(mavutil.mavlink.MAV_CMD_NAV_LAND, 0, 0, 0),
])
self.wait_current_waypoint(2)
run_cmd(mavutil.mavlink.MAV_CMD_DO_LAND_START)
self.wait_current_waypoint(5)
self.wait_mode("AUTO_RTL")
self.do_RTL()
def MAV_CMD_DO_LAND_START(self):
'''test handling of mavlink-received MAV_CMD_DO_LAND_START command'''
self._MAV_CMD_DO_LAND_START(self.run_cmd)
self.zero_throttle()
self._MAV_CMD_DO_LAND_START(self.run_cmd_int)
def _MAV_CMD_SET_EKF_SOURCE_SET(self, run_cmd):
run_cmd(
mavutil.mavlink.MAV_CMD_SET_EKF_SOURCE_SET,
17,
want_result=mavutil.mavlink.MAV_RESULT_DENIED,
)
self.change_mode('LOITER')
self.wait_ready_to_arm()
run_cmd(mavutil.mavlink.MAV_CMD_SET_EKF_SOURCE_SET, 2)
self.assert_prearm_failure('Need Position Estimate')
run_cmd(mavutil.mavlink.MAV_CMD_SET_EKF_SOURCE_SET, 1)
self.wait_ready_to_arm()
def MAV_CMD_SET_EKF_SOURCE_SET(self):
'''test setting of source sets using mavlink command'''
self._MAV_CMD_SET_EKF_SOURCE_SET(self.run_cmd)
self._MAV_CMD_SET_EKF_SOURCE_SET(self.run_cmd_int)
def MAV_CMD_NAV_TAKEOFF(self):
'''test issuing takeoff command via mavlink'''
self.change_mode('GUIDED')
self.wait_ready_to_arm()
self.arm_vehicle()
self.run_cmd(mavutil.mavlink.MAV_CMD_NAV_TAKEOFF, p7=5)
self.wait_altitude(4.5, 5.5, minimum_duration=5, relative=True)
self.change_mode('LAND')
self.wait_disarmed()
self.start_subtest("Check NAV_TAKEOFF is above home location, not current location")
current_alt_abs = self.get_altitude(relative=False)
loc = self.mav.location()
home_z_ofs = 20
self.run_cmd(
mavutil.mavlink.MAV_CMD_DO_SET_HOME,
p5=loc.lat,
p6=loc.lng,
p7=current_alt_abs + home_z_ofs,
)
self.change_mode('GUIDED')
self.arm_vehicle()
takeoff_alt = 5
self.run_cmd(mavutil.mavlink.MAV_CMD_NAV_TAKEOFF, p7=takeoff_alt)
self.wait_altitude(
current_alt_abs + home_z_ofs + takeoff_alt - 0.5,
current_alt_abs + home_z_ofs + takeoff_alt + 0.5,
minimum_duration=5,
relative=False,
)
self.change_mode('LAND')
self.wait_disarmed()
self.reboot_sitl()
def MAV_CMD_NAV_TAKEOFF_command_int(self):
'''test issuing takeoff command via mavlink and command_int'''
self.change_mode('GUIDED')
self.wait_ready_to_arm()
self.start_subtest("Check NAV_TAKEOFF is above home location, not current location")
current_alt_abs = self.get_altitude(relative=False)
loc = self.mav.location()
home_z_ofs = 20
self.run_cmd(
mavutil.mavlink.MAV_CMD_DO_SET_HOME,
p5=loc.lat,
p6=loc.lng,
p7=current_alt_abs + home_z_ofs,
)
self.change_mode('GUIDED')
self.arm_vehicle()
takeoff_alt = 5
self.run_cmd_int(
mavutil.mavlink.MAV_CMD_NAV_TAKEOFF,
p7=takeoff_alt,
frame=mavutil.mavlink.MAV_FRAME_GLOBAL_RELATIVE_ALT,
)
self.wait_altitude(
current_alt_abs + home_z_ofs + takeoff_alt - 0.5,
current_alt_abs + home_z_ofs + takeoff_alt + 0.5,
minimum_duration=5,
relative=False,
)
self.change_mode('LAND')
self.wait_disarmed()
self.reboot_sitl()
def Ch6TuningWPSpeed(self):
'''test waypoint speed can be changed via Ch6 tuning knob'''
self.set_parameters({
"RC6_OPTION": 219,
"TUNE": 10,
"TUNE_MIN": 0.02,
"TUNE_MAX": 1000,
"AUTO_OPTIONS": 3,
})
self.set_rc(6, 2000)
self.reboot_sitl()
self.upload_simple_relhome_mission([
(mavutil.mavlink.MAV_CMD_NAV_TAKEOFF, 0, 0, 20),
(mavutil.mavlink.MAV_CMD_NAV_WAYPOINT, 2000, 0, 20),
(mavutil.mavlink.MAV_CMD_NAV_RETURN_TO_LAUNCH, 0, 0, 0),
])
self.change_mode('AUTO')
self.wait_ready_to_arm()
self.arm_vehicle()
self.wait_groundspeed(9.5, 10.5, minimum_duration=5)
self.set_rc(6, 1500)
self.wait_groundspeed(4.5, 5.5, minimum_duration=5)
self.set_rc(6, 2000)
self.wait_groundspeed(9.5, 10.5, minimum_duration=5)
self.set_rc(6, 1300)
self.wait_groundspeed(2.5, 3.5, minimum_duration=5)
self.do_RTL()
def Ch6TuningLoitMaxXYSpeed(self):
'''test loiter can be changed via Ch6 tuning knob'''
self.set_parameters({
"RC6_OPTION": 219,
"TUNE": 60,
"TUNE_MIN": 0.2,
"TUNE_MAX": 10,
"AUTO_OPTIONS": 3,
})
self.set_rc(6, 2000)
self.reboot_sitl()
self.takeoff(mode='LOITER')
self.set_rc(2, 1000)
self.wait_groundspeed(9.5, 10.5, minimum_duration=5)
self.set_rc(6, 1500)
self.wait_groundspeed(4.5, 5.5, minimum_duration=5)
self.set_rc(6, 2000)
self.wait_groundspeed(9.5, 10.5, minimum_duration=5)
self.set_rc(6, 1300)
self.wait_groundspeed(2.5, 3.5, minimum_duration=5)
self.set_rc(2, 1500)
self.do_RTL()
def DualTuningChannels(self):
'''check tuning channel options work independently'''
RC6_MIN = 1100
RC6_MAX = 1200
TUNE_MIN = 0.25
TUNE_MAX = 10.0
RC7_MIN = 1000
RC7_MAX = 2000
TUNE2_MIN = 100
TUNE2_MAX = 500
self.set_parameters({
"RC6_OPTION": 219,
"RC6_MIN": RC6_MIN,
"RC6_MAX": RC6_MAX,
"TUNE": 60,
"TUNE_MIN": TUNE_MIN,
"TUNE_MAX": TUNE_MAX,
"RC7_OPTION": 220,
"RC7_MIN": RC7_MIN,
"RC7_MAX": RC7_MAX,
"TUNE2": 28,
"TUNE2_MIN": TUNE2_MIN,
"TUNE2_MAX": TUNE2_MAX,
})
self.reboot_sitl()
self.set_rc(6, RC6_MIN)
self.delay_sim_time(1)
self.assert_parameter_value("LOIT_SPEED_MS", TUNE_MIN)
self.set_rc(6, RC6_MAX)
self.delay_sim_time(1)
self.assert_parameter_value("LOIT_SPEED_MS", TUNE_MAX)
self.set_rc(6, RC6_MIN)
self.delay_sim_time(1)
self.assert_parameter_value("LOIT_SPEED_MS", TUNE_MIN)
self.set_rc(6, int((RC6_MIN+RC6_MAX)/2))
self.delay_sim_time(1)
self.assert_parameter_value("LOIT_SPEED_MS", int((TUNE_MIN+TUNE_MAX)/2), epsilon=1)
self.set_rc(7, RC7_MIN)
self.delay_sim_time(1)
self.assert_parameter_value("PSC_NE_VEL_I", TUNE2_MIN)
self.set_rc(7, RC7_MAX)
self.delay_sim_time(1)
self.assert_parameter_value("PSC_NE_VEL_I", TUNE2_MAX)
self.assert_parameter_value("LOIT_SPEED_MS", int((TUNE_MIN+TUNE_MAX)/2), epsilon=1)
self.set_rc(7, int((RC7_MIN+RC7_MAX)/2))
self.delay_sim_time(1)
self.assert_parameter_value("PSC_NE_VEL_I", int((TUNE2_MIN+TUNE2_MAX)/2), epsilon=1)
def PILOT_THR_BHV(self):
'''test the PILOT_THR_BHV parameter'''
self.start_subtest("Test default behaviour, no disarm on land")
self.set_parameters({
"DISARM_DELAY": 0,
})
self.takeoff(2, mode='GUIDED')
self.set_rc(3, 1500)
self.change_mode('LOITER')
self.set_rc(3, 1300)
maintain_armed = WaitAndMaintainArmed(self, minimum_duration=20)
maintain_armed.run()
self.start_subtest("Test THR_BEHAVE_DISARM_ON_LAND_DETECT")
self.set_parameters({
"PILOT_THR_BHV": 4,
})
self.zero_throttle()
self.takeoff(2, mode='GUIDED')
self.set_rc(3, 1500)
self.change_mode('LOITER')
self.set_rc(3, 1300)
self.wait_disarmed()
def CameraLogMessages(self):
'''ensure Camera log messages are good'''
self.set_parameter("RC12_OPTION", 9)
self.set_parameter("CAM1_TYPE", 1)
self.reboot_sitl()
gpis = []
gps_raws = []
attitudes = []
self.takeoff(10, mode='GUIDED')
self.set_rc(12, 2000)
gpis.append(self.assert_receive_message('GLOBAL_POSITION_INT'))
gps_raws.append(self.assert_receive_message('GPS_RAW_INT'))
attitudes.append(self.assert_receive_message('ATTITUDE'))
self.set_rc(12, 1000)
self.fly_guided_move_local(0, 0, 20)
self.set_rc(12, 2000)
gpis.append(self.assert_receive_message('GLOBAL_POSITION_INT'))
gps_raws.append(self.assert_receive_message('GPS_RAW_INT'))
attitudes.append(self.assert_receive_message('ATTITUDE'))
self.set_rc(12, 1000)
self.change_mode('LOITER')
self.set_rc(1, 1000)
self.set_rc(2, 1000)
self.delay_sim_time(5)
self.set_rc(12, 2000)
gpis.append(self.assert_receive_message('GLOBAL_POSITION_INT'))
gps_raws.append(self.assert_receive_message('GPS_RAW_INT'))
attitudes.append(self.assert_receive_message('ATTITUDE'))
self.set_rc(12, 1000)
self.set_rc(1, 1500)
self.set_rc(2, 1500)
dfreader = self.dfreader_for_current_onboard_log()
self.do_RTL()
for i in range(len(gpis)):
gpi = gpis[i]
gps_raw = gps_raws[i]
attitude = attitudes[i]
m = dfreader.recv_match(type="CAM")
things = [
["absalt", gpi.alt*0.001, m.Alt],
["relalt", gpi.relative_alt*0.001, m.RelAlt],
["gpsalt", gps_raw.alt*0.001, m.GPSAlt],
["R", math.degrees(attitude.roll), m.R],
["P", math.degrees(attitude.pitch), m.P],
["Y", mavextra.wrap_360(math.degrees(attitude.yaw)), m.Y],
]
for (name, want, got) in things:
if abs(got - want) > 1:
raise NotAchievedException(f"Incorrect {name} {want=} {got=}")
self.progress(f"{name} {want=} {got=}")
want = gpi.relative_alt*0.001
got = m.RelAlt
if abs(got - want) > 1:
raise NotAchievedException(f"Incorrect relalt {want=} {got=}")
def LoiterToGuidedHomeVSOrigin(self):
'''test moving from guided to loiter mode when home is a different alt
to origin'''
self.set_parameters({
"TERRAIN_ENABLE": 1,
"SIM_TERRAIN": 1,
})
self.takeoff(10, mode='GUIDED')
here = self.mav.location()
self.set_home(here)
self.change_mode('LOITER')
self.wait_altitude(here.alt-1, here.alt+1, minimum_duration=10)
self.disarm_vehicle(force=True)
self.reboot_sitl()
def GuidedModeThrust(self):
'''test handling of option-bit-3, where mavlink commands are
intrepreted as thrust not climb rate'''
self.set_parameter('GUID_OPTIONS', 8)
self.change_mode('GUIDED')
self.wait_ready_to_arm()
self.arm_vehicle()
self.mav.mav.set_attitude_target_send(
0,
1,
1,
0,
mavextra.euler_to_quat([0, 0, 0]),
0,
0,
0,
0.5
)
self.wait_altitude(0.5, 100, relative=True, timeout=10)
self.do_RTL()
def AutoRTL(self):
'''Test Auto RTL mode using do land start and return path start mission items'''
alt = 50
guided_loc = self.home_relative_loc_ne(1000, 0)
guided_loc.alt += alt
self.takeoff(mode='GUIDED')
self.fly_guided_move_to(guided_loc, timeout=240)
try:
self.change_mode('AUTO_RTL', timeout=10)
raise NotAchievedException("Should not change mode with no mission")
except WaitModeTimeout:
pass
except Exception as e:
raise e
cmd_return_path_start = 188
self.run_cmd(mavutil.mavlink.MAV_CMD_DO_LAND_START, want_result=mavutil.mavlink.MAV_RESULT_FAILED)
self.run_cmd(cmd_return_path_start, want_result=mavutil.mavlink.MAV_RESULT_FAILED)
self.upload_simple_relhome_mission([
(mavutil.mavlink.MAV_CMD_NAV_WAYPOINT, 1000, 0, alt),
(mavutil.mavlink.MAV_CMD_NAV_WAYPOINT, 750, 0, alt),
self.create_MISSION_ITEM_INT(mavutil.mavlink.MAV_CMD_DO_LAND_START),
(mavutil.mavlink.MAV_CMD_NAV_WAYPOINT, 500, 0, alt),
(mavutil.mavlink.MAV_CMD_NAV_WAYPOINT, 250, 0, alt),
])
self.run_cmd(cmd_return_path_start, want_result=mavutil.mavlink.MAV_RESULT_FAILED)
self.run_cmd(mavutil.mavlink.MAV_CMD_DO_LAND_START, want_result=mavutil.mavlink.MAV_RESULT_ACCEPTED)
self.drain_mav()
self.assert_current_waypoint(4)
self.change_mode('GUIDED')
self.fly_guided_move_to(guided_loc)
self.change_mode('AUTO_RTL')
self.drain_mav()
self.assert_current_waypoint(4)
self.change_mode('GUIDED')
self.fly_guided_move_to(guided_loc)
self.upload_simple_relhome_mission([
(mavutil.mavlink.MAV_CMD_NAV_WAYPOINT, 1250, 0, alt),
self.create_MISSION_ITEM_INT(cmd_return_path_start),
(mavutil.mavlink.MAV_CMD_NAV_WAYPOINT, 900, 0, alt),
(mavutil.mavlink.MAV_CMD_NAV_WAYPOINT, 750, 0, alt),
(mavutil.mavlink.MAV_CMD_NAV_WAYPOINT, 550, 0, alt),
(mavutil.mavlink.MAV_CMD_NAV_WAYPOINT, 500, 0, alt),
self.create_MISSION_ITEM_INT(mavutil.mavlink.MAV_CMD_DO_LAND_START),
(mavutil.mavlink.MAV_CMD_NAV_WAYPOINT, 250, 0, alt),
(mavutil.mavlink.MAV_CMD_NAV_WAYPOINT, -250, 0, alt),
(mavutil.mavlink.MAV_CMD_NAV_WAYPOINT, -500, 0, alt),
])
self.run_cmd(cmd_return_path_start, want_result=mavutil.mavlink.MAV_RESULT_ACCEPTED)
self.drain_mav()
self.assert_current_waypoint(3)
self.change_mode('GUIDED')
self.fly_guided_move_to(guided_loc)
self.change_mode('AUTO_RTL')
self.drain_mav()
self.assert_current_waypoint(3)
self.run_cmd(mavutil.mavlink.MAV_CMD_DO_LAND_START, want_result=mavutil.mavlink.MAV_RESULT_ACCEPTED)
self.drain_mav()
self.assert_current_waypoint(8)
return_path_test = self.home_relative_loc_ne(600, 0)
return_path_test.alt += alt
self.change_mode('GUIDED')
self.fly_guided_move_to(return_path_test, timeout=100)
self.run_cmd(cmd_return_path_start, want_result=mavutil.mavlink.MAV_RESULT_ACCEPTED)
self.drain_mav()
self.assert_current_waypoint(5)
home = self.home_relative_loc_ne(0, 0)
home.alt += alt
self.change_mode('GUIDED')
self.fly_guided_move_to(home, timeout=140)
self.run_cmd(cmd_return_path_start, want_result=mavutil.mavlink.MAV_RESULT_ACCEPTED)
self.drain_mav()
self.assert_current_waypoint(6)
self.land_and_disarm()
def EK3_OGN_HGT_MASK(self):
'''test baraometer-alt-compensation based on long-term GPS readings'''
self.context_push()
self.set_parameters({
'EK3_OGN_HGT_MASK': 1,
})
self.reboot_sitl()
expected_alt = 10
self.change_mode('GUIDED')
self.wait_ready_to_arm()
current_alt = self.get_altitude()
expected_alt_abs = current_alt + expected_alt
self.takeoff(expected_alt, mode='GUIDED')
self.delay_sim_time(5)
self.set_parameter("SIM_BARO_DRIFT", 0.01)
def check_altitude(mav, m):
m_type = m.get_type()
epsilon = 10
if m_type == 'GPS_RAW_INT':
got_gps_alt = m.alt * 0.001
if abs(expected_alt_abs - got_gps_alt) > epsilon:
raise NotAchievedException(f"Bad GPS altitude (got={got_gps_alt} want={expected_alt_abs})")
elif m_type == 'GLOBAL_POSITION_INT':
got_canonical_alt = m.relative_alt * 0.001
if abs(expected_alt - got_canonical_alt) > epsilon:
raise NotAchievedException(f"Bad canonical altitude (got={got_canonical_alt} want={expected_alt})")
self.install_message_hook_context(check_altitude)
self.delay_sim_time(1500)
self.disarm_vehicle(force=True)
self.context_pop()
self.reboot_sitl(force=True)
def GuidedForceArm(self):
'''ensure Guided acts appropriately when force-armed'''
self.set_parameters({
"EK3_SRC2_VELXY": 5,
"SIM_GPS1_ENABLE": 0,
})
self.load_default_params_file("copter-optflow.parm")
self.reboot_sitl()
self.delay_sim_time(30)
self.change_mode('GUIDED')
self.arm_vehicle(force=True)
self.takeoff(20, mode='GUIDED')
location = self.offset_location_ne(self.sim_location(), metres_north=0, metres_east=-300)
self.progress("Ensure we don't move for 10 seconds")
tstart = self.get_sim_time()
startpos = self.sim_location_int()
while True:
now = self.get_sim_time_cached()
if now - tstart > 10:
break
self.send_set_position_target_global_int(int(location.lat*1e7), int(location.lng*1e7), 10)
dist = self.get_distance_int(startpos, self.sim_location_int())
if dist > 10:
raise NotAchievedException("Wandered too far from start position")
self.delay_sim_time(1)
self.disarm_vehicle(force=True)
self.reboot_sitl()
def EK3_OGN_HGT_MASK_climbing(self):
'''check combination of height bits doesn't cause climb'''
self.context_push()
self.set_parameters({
'EK3_OGN_HGT_MASK': 5,
})
self.reboot_sitl()
expected_alt = 10
self.change_mode('GUIDED')
self.wait_ready_to_arm()
current_alt = self.get_altitude()
expected_alt_abs = current_alt + expected_alt
self.takeoff(expected_alt, mode='GUIDED')
self.delay_sim_time(5)
def check_altitude(mav, m):
m_type = m.get_type()
epsilon = 10
if m_type == 'GPS_RAW_INT':
got_gps_alt = m.alt * 0.001
if abs(expected_alt_abs - got_gps_alt) > epsilon:
raise NotAchievedException(f"Bad GPS altitude (got={got_gps_alt} want={expected_alt_abs})")
elif m_type == 'GLOBAL_POSITION_INT':
if abs(expected_alt - m.relative_alt * 0.001) > epsilon:
raise NotAchievedException("Bad canonical altitude")
self.install_message_hook_context(check_altitude)
self.delay_sim_time(1500)
self.disarm_vehicle(force=True)
self.context_pop()
self.reboot_sitl(force=True)
def GuidedWeatherVane(self):
'''check Copter Guided mode weathervane option'''
self.set_parameters({
'SIM_WIND_SPD': 10,
'SIM_WIND_DIR': 90,
'WVANE_ENABLE': 1,
})
self.takeoff(20, mode='GUIDED')
self.guided_achieve_heading(0)
self.set_parameter("GUID_OPTIONS", 128)
self.wait_heading(90, timeout=60, minimum_duration=10)
self.do_RTL()
def Clamp(self):
'''test Copter docking clamp'''
clamp_ch = 11
self.set_parameters({
"SIM_CLAMP_CH": clamp_ch,
})
self.takeoff(1, mode='LOITER')
self.context_push()
self.context_collect('STATUSTEXT')
self.progress("Ensure can't take off with clamp in place")
self.run_cmd(mavutil.mavlink.MAV_CMD_DO_SET_SERVO, p1=11, p2=2000)
self.wait_statustext("SITL: Clamp: grabbed vehicle", check_context=True)
self.arm_vehicle()
self.set_rc(3, 2000)
self.wait_altitude(0, 5, minimum_duration=5, relative=True)
self.run_cmd(mavutil.mavlink.MAV_CMD_DO_SET_SERVO, p1=11, p2=1000)
self.wait_statustext("SITL: Clamp: released vehicle", check_context=True)
self.wait_altitude(5, 5000, minimum_duration=1, relative=True)
self.do_RTL()
self.set_rc(3, 1000)
self.change_mode('LOITER')
self.context_pop()
self.progress("Same again for repeatability")
self.context_push()
self.context_collect('STATUSTEXT')
self.run_cmd(mavutil.mavlink.MAV_CMD_DO_SET_SERVO, p1=11, p2=2000)
self.wait_statustext("SITL: Clamp: grabbed vehicle", check_context=True)
self.arm_vehicle()
self.set_rc(3, 2000)
self.wait_altitude(0, 1, minimum_duration=5, relative=True)
self.run_cmd(mavutil.mavlink.MAV_CMD_DO_SET_SERVO, p1=11, p2=1000)
self.wait_statustext("SITL: Clamp: released vehicle", check_context=True)
self.wait_altitude(5, 5000, minimum_duration=1, relative=True)
self.do_RTL()
self.set_rc(3, 1000)
self.change_mode('LOITER')
self.context_pop()
here = self.mav.location()
loc = self.offset_location_ne(here, 10, 0)
self.takeoff(5, mode='GUIDED')
self.send_do_reposition(loc, frame=mavutil.mavlink.MAV_FRAME_GLOBAL)
self.wait_location(loc, timeout=120)
self.land_and_disarm()
self.set_home(here)
self.context_push()
self.context_collect('STATUSTEXT')
self.run_cmd(mavutil.mavlink.MAV_CMD_DO_SET_SERVO, p1=11, p2=2000)
self.wait_statustext("SITL: Clamp: missed vehicle", check_context=True)
self.run_cmd(mavutil.mavlink.MAV_CMD_DO_SET_SERVO, p1=11, p2=1000)
self.context_pop()
self.takeoff(5, mode='GUIDED')
self.do_RTL()
self.takeoff(5, mode='GUIDED')
self.land_and_disarm()
self.context_push()
self.context_collect('STATUSTEXT')
self.run_cmd(mavutil.mavlink.MAV_CMD_DO_SET_SERVO, p1=11, p2=2000)
self.wait_statustext("SITL: Clamp: grabbed vehicle", check_context=True)
self.context_pop()
self.reboot_sitl()
def GripperReleaseOnThrustLoss(self):
'''tests that gripper is released on thrust loss if option set'''
self.context_push()
self.set_servo_gripper_parameters()
self.reboot_sitl()
self.takeoff(30, mode='LOITER')
self.context_push()
self.context_collect('STATUSTEXT')
self.set_parameters({
"SIM_ENGINE_MUL": 0.5,
"SIM_ENGINE_FAIL": 1 << 1,
"FLIGHT_OPTIONS": 4,
})
self.wait_statustext("Gripper load released", regex=True, timeout=60)
self.context_pop()
self.do_RTL()
self.context_pop()
self.reboot_sitl()
def GripperInitialPosition(self):
'''test gripper initial position'''
self.set_servo_gripper_parameters()
self.set_parameters({
"RC8_OPTION": 19,
})
grip_neutral = 1423
self.set_parameter('GRIP_NEUTRAL', grip_neutral)
global seen_grip_neutral
seen_grip_neutral = False
def hook(mav, m):
global seen_grip_neutral
if m.get_type() != 'SERVO_OUTPUT_RAW':
return
if m.servo8_raw == 0:
if seen_grip_neutral:
raise NotAchievedException("Saw 0 after seeing grip neutral")
return
if m.servo8_raw == grip_neutral:
seen_grip_neutral = True
return
raise NotAchievedException(f"Received bad value {m.servo8_raw=} for servo gripper")
self.install_message_hook_context(hook)
self.reboot_sitl()
self.wait_ready_to_arm()
if not seen_grip_neutral:
raise NotAchievedException("Never saw grip neutral")
def REQUIRE_LOCATION_FOR_ARMING(self):
'''check AP_Arming::Option::REQUIRE_LOCATION_FOR_ARMING works'''
self.context_push()
self.set_parameters({
"SIM_GPS1_NUMSATS": 3,
})
self.reboot_sitl()
self.change_mode('STABILIZE')
self.wait_prearm_sys_status_healthy()
self.assert_home_position_not_set()
self.arm_vehicle()
self.disarm_vehicle()
self.change_mode('LOITER')
self.assert_prearm_failure("waiting for home", other_prearm_failures_fatal=False)
self.change_mode('STABILIZE')
self.set_parameters({
"ARMING_NEED_LOC": 1,
})
self.assert_prearm_failure("Need Position Estimate", other_prearm_failures_fatal=False)
self.context_pop()
self.reboot_sitl()
def AutoContinueOnRCFailsafe(self):
'''check LOITER when entered after RC failsafe is ignored in auto'''
self.set_parameters({
"FS_OPTIONS": 1,
})
self.upload_simple_relhome_mission([
(mavutil.mavlink.MAV_CMD_NAV_TAKEOFF, 0, 0, 10),
(mavutil.mavlink.MAV_CMD_NAV_WAYPOINT, 20, 0, 10),
(mavutil.mavlink.MAV_CMD_NAV_WAYPOINT, 40, 0, 10),
(mavutil.mavlink.MAV_CMD_NAV_WAYPOINT, 120, 0, 10),
])
self.takeoff(mode='LOITER')
self.set_rc(1, 1200)
self.delay_sim_time(1)
self.change_mode('AUTO')
self.wait_waypoint(2, 2, max_dist=3)
self.set_parameters({
'SIM_RC_FAIL': 1,
})
self.wait_waypoint(3, 3, max_dist=3)
self.assert_mode_is('AUTO')
self.change_mode('LOITER')
self.wait_groundspeed(0, 0.1, minimum_duration=30, timeout=450)
self.do_RTL()
def MissionRTLYawBehaviour(self):
'''check end-of-mission yaw behaviour'''
self.set_parameters({
"AUTO_OPTIONS": 3,
})
self.start_subtest("behaviour with WP_YAW_BEHAVE set to next-waypoint-except-RTL")
self.upload_simple_relhome_mission([
(mavutil.mavlink.MAV_CMD_NAV_TAKEOFF, 0, 0, 10),
(mavutil.mavlink.MAV_CMD_NAV_WAYPOINT, 20, 0, 10),
(mavutil.mavlink.MAV_CMD_NAV_RETURN_TO_LAUNCH, 0, 0, 0),
])
self.change_mode('AUTO')
self.wait_ready_to_arm()
original_heading = self.get_heading()
if abs(original_heading) < 5:
raise NotAchievedException(f"Bad original heading {original_heading}")
self.arm_vehicle()
self.wait_current_waypoint(3)
self.wait_rtl_complete()
self.wait_disarmed()
if abs(self.get_heading()) > 5:
raise NotAchievedException("Should have yaw zero without option")
self.change_mode('LOITER')
self.change_mode('AUTO')
self.start_subtest("behaviour with WP_YAW_BEHAVE set to next-waypoint")
self.upload_simple_relhome_mission([
(mavutil.mavlink.MAV_CMD_NAV_TAKEOFF, 0, 0, 10),
(mavutil.mavlink.MAV_CMD_NAV_WAYPOINT, 0, 20, 20),
(mavutil.mavlink.MAV_CMD_NAV_RETURN_TO_LAUNCH, 0, 0, 0),
])
self.set_parameters({
"WP_YAW_BEHAVIOR": 1,
})
self.change_mode('AUTO')
self.wait_ready_to_arm()
original_heading = self.get_heading()
if abs(original_heading) > 1:
raise NotAchievedException("Bad original heading")
self.arm_vehicle()
self.wait_current_waypoint(3)
self.wait_rtl_complete()
self.wait_disarmed()
new_heading = self.get_heading()
if abs(new_heading - original_heading) > 5:
raise NotAchievedException(f"Should return to original heading want={original_heading} got={new_heading}")
def BatteryInternalUseOnly(self):
'''batteries marked as internal use only should not appear over mavlink'''
self.set_parameters({
"BATT_MONITOR": 4,
"BATT2_MONITOR": 4,
})
self.reboot_sitl()
self.wait_message_field_values('BATTERY_STATUS', {
"id": 0,
})
self.wait_message_field_values('BATTERY_STATUS', {
"id": 1,
})
self.progress("Making battery private")
self.set_parameters({
"BATT_OPTIONS": 256,
})
self.wait_message_field_values('BATTERY_STATUS', {
"id": 1,
})
for i in range(10):
self.assert_received_message_field_values('BATTERY_STATUS', {
"id": 1
})
def MAV_CMD_MISSION_START_p1_p2(self):
'''make sure we deny MAV_CMD_MISSION_START if either p1 or p2 non-zero'''
self.upload_simple_relhome_mission([
(mavutil.mavlink.MAV_CMD_NAV_TAKEOFF, 0, 0, 20),
])
self.set_parameters({
"AUTO_OPTIONS": 3,
})
self.change_mode('AUTO')
self.wait_ready_to_arm()
self.run_cmd(
mavutil.mavlink.MAV_CMD_MISSION_START,
p1=1,
want_result=mavutil.mavlink.MAV_RESULT_DENIED,
)
self.run_cmd(
mavutil.mavlink.MAV_CMD_MISSION_START,
p2=1,
want_result=mavutil.mavlink.MAV_RESULT_DENIED,
)
self.run_cmd(
mavutil.mavlink.MAV_CMD_MISSION_START,
p1=1,
p2=1,
want_result=mavutil.mavlink.MAV_RESULT_DENIED,
)
def ScriptingAHRSSource(self):
'''test ahrs-source.lua script'''
self.install_example_script_context("ahrs-source.lua")
self.set_parameters({
"RC10_OPTION": 90,
"SCR_ENABLE": 1,
"SCR_USER1": 10,
"SCR_USER2": 10,
"SCR_USER3": 0.2,
})
self.set_rc(10, 2000)
self.reboot_sitl()
self.context_collect('STATUSTEXT')
self.set_rc(10, 1000)
self.wait_statustext('Using EKF Source Set 1', check_context=True)
self.set_rc(10, 1500)
self.wait_statustext('Using EKF Source Set 2', check_context=True)
self.set_rc(10, 2000)
self.wait_statustext('Using EKF Source Set 3', check_context=True)
def CommonOrigin(self):
"""Test common origin between EKF2 and EKF3"""
self.context_push()
self.set_parameters({
'AHRS_EKF_TYPE': 2,
'EK2_ENABLE': 1,
'EK3_CHECK_SCALE': 1,
})
self.reboot_sitl()
self.context_collect('STATUSTEXT')
self.wait_statustext("EKF2 IMU0 origin set", timeout=60, check_context=True)
self.wait_statustext("EKF2 IMU0 is using GPS", timeout=60, check_context=True)
self.wait_statustext("EKF2 active", timeout=60, check_context=True)
self.context_collect('GPS_GLOBAL_ORIGIN')
self.run_cmd(mavutil.mavlink.MAV_CMD_GET_HOME_POSITION)
ek2_origin = self.assert_receive_message('GPS_GLOBAL_ORIGIN', check_context=True)
self.set_parameters({
'SIM_GPS1_GLTCH_X' : 0.001,
'EK3_CHECK_SCALE' : 100,
'AHRS_EKF_TYPE' : 3})
self.wait_statustext("EKF3 IMU0 is using GPS", timeout=60, check_context=True)
self.wait_statustext("EKF3 active", timeout=60, check_context=True)
self.run_cmd(mavutil.mavlink.MAV_CMD_GET_HOME_POSITION)
ek3_origin = self.assert_receive_message('GPS_GLOBAL_ORIGIN', check_context=True)
self.progress("Checking origins")
if ek2_origin.time_usec == ek3_origin.time_usec:
raise NotAchievedException("Did not get a new GPS_GLOBAL_ORIGIN message")
print(ek2_origin, ek3_origin)
if (ek2_origin.latitude != ek3_origin.latitude or
ek2_origin.longitude != ek3_origin.longitude or
ek2_origin.altitude != ek3_origin.altitude):
raise NotAchievedException("Did not get matching EK2 and EK3 origins")
self.context_pop()
self.reboot_sitl()
def AHRSOriginRecorded(self):
"""Test AHRS option to record and reuse origin"""
self.context_push()
self.set_parameter('AHRS_OPTIONS', 8)
self.set_parameter('LOG_DISARMED', 1)
self.wait_ready_to_arm()
self.assert_parameter_value('AHRS_OPTIONS', 8)
self.assert_parameter_value('AHRS_ORIGIN_LAT', -35, epsilon=1)
self.assert_parameter_value('AHRS_ORIGIN_LON', 149, epsilon=1)
self.assert_parameter_value('AHRS_ORIGIN_ALT', 584, epsilon=10)
self.set_parameter("SIM_FLOW_ENABLE", 1)
self.set_parameter("FLOW_TYPE", 10)
self.set_analog_rangefinder_parameters()
self.configure_EKFs_to_use_optical_flow_instead_of_GPS()
self.reboot_sitl()
self.wait_statustext("EKF3 IMU0 origin set")
self.change_mode("LOITER")
self.wait_ready_to_arm(timeout=120, require_absolute=False, check_prearm_bit=True)
self.arm_vehicle()
self.takeoff(10, mode="LOITER")
self.do_RTL()
self.context_pop()
def ReadOnlyDefaults(self):
'''test that defaults marked "readonly" can't be set'''
defaults_filepath = tempfile.NamedTemporaryFile(mode='w', delete=False)
defaults_filepath.write("""
DISARM_DELAY 77 @READONLY
RTL_ALT_M 123
RTL_ALT_FINAL_M 129
""")
defaults_filepath.close()
self.customise_SITL_commandline([
], defaults_filepath=defaults_filepath.name)
self.context_collect('STATUSTEXT')
self.send_set_parameter_direct("DISARM_DELAY", 88)
self.wait_statustext("Param write denied (DISARM_DELAY)")
self.assert_parameter_value("DISARM_DELAY", 77)
self.assert_parameter_value("RTL_ALT_M", 123)
self.start_subtest('Ensure something is writable....')
self.set_parameter('RTL_ALT_FINAL_M', 101)
new_values_filepath = tempfile.NamedTemporaryFile(mode='w', delete=False)
new_values_filepath.write("""
DISARM_DELAY 99
RTL_ALT_M 111
""")
new_values_filepath.close()
self.start_subtest("Ensure parameters can't be set via FTP either")
mavproxy = self.start_mavproxy()
mavproxy.expect("Received .* parameters")
self.mavproxy_load_module(mavproxy, 'ftp')
mavproxy.send(f"param ftpload {new_values_filepath.name}\n")
mavproxy.expect("Loaded")
self.delay_sim_time(1)
self.stop_mavproxy(mavproxy)
self.assert_parameter_value("DISARM_DELAY", 77)
self.assert_parameter_value("RTL_ALT_M", 111)
self.assert_parameter_value('RTL_ALT_FINAL_M', 101)
def ScriptingFlipMode(self):
'''test adding custom mode from scripting'''
self.set_parameters({
"SCR_ENABLE": 1,
})
self.install_example_script_context('Flip_Mode.lua')
self.reboot_sitl()
self.takeoff(10, mode="LOITER")
try:
self.change_mode(100, timeout=10)
except AutoTestTimeoutException:
self.progress("PASS not able to enter from loiter")
self.change_mode("ALT_HOLD")
self.change_mode(100)
self.wait_mode("ALT_HOLD")
self.land_and_disarm()
def ScriptingFlyVelocity(self):
'''test flying a velocity vector using scripting'''
self.set_parameters({
"SCR_ENABLE": 1,
})
self.install_example_script_context('set-target-velocity.lua')
self.reboot_sitl()
self.change_mode("ALT_HOLD")
self.wait_ready_to_arm()
self.arm_vehicle()
self.set_rc(6, 2000)
self.wait_mode("RTL", timeout=120)
self.land_and_disarm()
def RTLYaw(self):
'''test that vehicle yaws to original heading on RTL'''
for behaviour in 1, 3:
self.set_parameters({
'WP_YAW_BEHAVIOR': behaviour,
})
self.change_mode('GUIDED')
original_heading = self.get_heading()
target_heading = 100
if original_heading - target_heading < 90:
raise NotAchievedException("Bad initial heading")
self.takeoff(10, mode='GUIDED')
self.guided_achieve_heading(target_heading)
self.change_mode('RTL')
self.wait_heading(original_heading)
self.wait_disarmed()
def CompassLearnCopyFromEKF(self):
'''test compass learning whereby we copy learnt offsets from the EKF'''
self.reboot_sitl()
self.context_push()
self.set_parameters({
"SIM_MAG1_OFS_X": 1100,
})
self.assert_prearm_failure("Check mag field", other_prearm_failures_fatal=False)
self.context_pop()
self.wait_ready_to_arm()
self.takeoff(30, mode='ALT_HOLD')
self.set_parameters({
'COMPASS_USE2': 0,
'COMPASS_USE3': 0,
})
self.assert_parameter_value("COMPASS_OFS_X", 20, epsilon=30)
self.set_parameter("COMPASS_OFS_X", 20)
new_compass_ofs_x = 200
self.set_parameters({
"SIM_MAG1_OFS_X": new_compass_ofs_x,
})
self.set_parameter("COMPASS_LEARN", 2)
self.set_rc(4, 1450)
self.set_rc(1, 1450)
for i in range(0, 5):
self.change_mode('LOITER')
self.delay_sim_time(10)
self.change_mode('ALT_HOLD')
self.change_mode('FLIP')
self.set_parameter('ATC_ANGLE_MAX', 70)
self.change_mode('ALT_HOLD')
for j in 1000, 2000:
for i in 1, 2, 4:
self.set_rc(i, j)
self.delay_sim_time(10)
self.set_rc(1, 1500)
self.set_rc(2, 1500)
self.set_rc(4, 1500)
self.do_RTL()
self.assert_parameter_value("COMPASS_OFS_X", new_compass_ofs_x, epsilon=30)
self.reboot_sitl()
self.assert_parameter_value("COMPASS_OFS_X", new_compass_ofs_x, epsilon=30)
def RudderDisarmMidair(self):
'''check disarm behaviour mid-air'''
self.change_mode('LOITER')
self.wait_ready_to_arm()
self.change_mode('STABILIZE')
self.set_home(self.mav.location())
self.set_rc(4, 2000)
self.wait_armed()
self.set_rc(4, 1500)
self.set_rc(3, 2000)
self.wait_altitude(300, 20000, relative=True, timeout=10000)
self.hover()
self.set_rc(4, 1000)
self.delay_sim_time(11)
self.progress("Checking we are still armed")
self.assert_armed()
self.set_rc(3, 2000)
self.wait_altitude(300, 20000, relative=True)
self.set_rc(3, 1000)
self.set_rc(4, 1000)
self.delay_sim_time(11)
self.progress("Checking we disarm")
self.wait_disarmed(timeout=5)
self.set_rc(4, 1500)
self.arm_vehicle()
self.set_rc(3, 1600)
self.delay_sim_time(10)
self.do_RTL(timeout=600)
def mission_NAV_LOITER_TURNS(self):
'''test that loiter turns basically works'''
self.upload_simple_relhome_mission([
(mavutil.mavlink.MAV_CMD_NAV_TAKEOFF, 0, 0, 20),
self.create_MISSION_ITEM_INT(
mavutil.mavlink.MAV_CMD_NAV_LOITER_TURNS,
p1=1,
p3=30,
z=30,
frame=mavutil.mavlink.MAV_FRAME_GLOBAL_RELATIVE_ALT,
),
(mavutil.mavlink.MAV_CMD_NAV_RETURN_TO_LAUNCH, 0, 0, 0),
])
self.change_mode('AUTO')
self.set_parameter('AUTO_OPTIONS', 3)
self.wait_ready_to_arm()
self.arm_vehicle()
self.wait_disarmed()
def mission_NAV_LOITER_TURNS_off_center(self):
'''test that loiter turns basically works - copter on edge of circle'''
self.start_subtest("Start circle when on edge of circle")
radius = 30
self.wait_ready_to_arm()
here = self.mav.location()
circle_centre_loc = self.offset_location_ne(here, radius, 0)
self.upload_simple_relhome_mission([
(mavutil.mavlink.MAV_CMD_NAV_TAKEOFF, 0, 0, 20),
self.create_MISSION_ITEM_INT(
mavutil.mavlink.MAV_CMD_NAV_LOITER_TURNS,
p1=1,
p3=radius,
x=int(circle_centre_loc.lat*1e7),
y=int(circle_centre_loc.lng*1e7),
z=30,
frame=mavutil.mavlink.MAV_FRAME_GLOBAL_RELATIVE_ALT,
),
(mavutil.mavlink.MAV_CMD_NAV_RETURN_TO_LAUNCH, 0, 0, 0),
])
self.change_mode('AUTO')
self.set_parameter('AUTO_OPTIONS', 3)
self.wait_ready_to_arm()
self.arm_vehicle()
self.wait_disarmed()
def AHRSAutoTrim(self):
'''calibrate AHRS trim using RC input'''
self.progress("Making earth frame same as body frame")
self.takeoff(5, mode='GUIDED')
self.guided_achieve_heading(0)
self.do_land()
self.set_parameters({
'RC9_OPTION': 182,
})
param_last_check_time = 0
for mode in ['STABILIZE', 'ALT_HOLD']:
self.set_parameters({
'AHRS_TRIM_X': 0.1,
'AHRS_TRIM_Y': -0.1,
})
self.takeoff(mode=mode)
self.set_rc(9, 2000)
tstart = self.get_sim_time()
while True:
now = self.get_sim_time_cached()
if now - tstart > 30:
raise ValueError(f"Failed to reduce trims in {mode}!")
lpn = self.assert_receive_message('LOCAL_POSITION_NED')
delta = 40
roll_input = 1500
if lpn.vx > 0:
roll_input -= delta
elif lpn.vx < 0:
roll_input += delta
pitch_input = 1500
if lpn.vy > 0:
pitch_input += delta
elif lpn.vy < 0:
pitch_input -= delta
self.set_rc_from_map({
1: roll_input,
2: pitch_input,
}, quiet=True)
if now - param_last_check_time > 1:
param_last_check_time = now
trim_x = self.get_parameter('AHRS_TRIM_X', verbose=False)
trim_y = self.get_parameter('AHRS_TRIM_Y', verbose=False)
self.progress(f"trim_x={trim_x} trim_y={trim_y}")
if abs(trim_x) < 0.01 and abs(trim_y) < 0.01:
self.progress("Good AHRS trims")
self.progress(f"vx={lpn.vx} vy={lpn.vy}")
if abs(lpn.vx) > 1 or abs(lpn.vy) > 1:
raise NotAchievedException("Velocity after trimming?!")
break
self.context_collect('STATUSTEXT')
self.set_rc(9, 1000)
self.wait_statustext('Trim saved', check_context=True)
self.context_stop_collecting('STATUSTEXT')
self.do_land()
self.set_rc_default()
self.progress("Landing should cancel the autotrim")
self.takeoff(10, mode='STABILIZE')
self.context_collect('STATUSTEXT')
self.set_rc(9, 2000)
self.wait_statustext('AutoTrim running', check_context=True)
self.do_land()
self.wait_statustext('AutoTrim cancelled', check_context=True)
self.set_rc(9, 1000)
self.progress("Changing mode to LOITER")
self.takeoff(10, mode='STABILIZE')
self.context_collect('STATUSTEXT')
self.set_rc(9, 2000)
self.wait_statustext('AutoTrim running', check_context=True)
self.change_mode('LOITER')
self.wait_statustext('AutoTrim cancelled', check_context=True)
self.do_land()
self.set_rc(9, 1000)
def RTLStoppingDistanceSpeed(self):
'''test stopping distance unaffected by RTL speed'''
self.upload_simple_relhome_mission([
(mavutil.mavlink.MAV_CMD_NAV_TAKEOFF, 0, 0, 20),
(mavutil.mavlink.MAV_CMD_NAV_WAYPOINT, 200, 0, 20),
(mavutil.mavlink.MAV_CMD_NAV_RETURN_TO_LAUNCH, 0, 0, 0),
])
self.set_parameters({
"AUTO_OPTIONS": 3,
})
self.context_push()
self.set_parameters({
'RTL_SPEED_MS': 1,
})
self.change_mode('AUTO')
self.wait_ready_to_arm()
self.arm_vehicle()
self.wait_current_waypoint(2)
self.progress("Waiting for vehicle to get up to speed")
wpnav_speed = self.get_parameter('WPNAV_SPEED')
self.wait_groundspeed(wpnav_speed/100-0.1, wpnav_speed/100+0.1)
rtl_start_pos = self.get_local_position_NED()
self.context_set_message_rate_hz('VFR_HUD', 50)
self.change_mode('RTL')
self.progress("Waiting for vehicle to stop")
self.wait_groundspeed(-0.3, 0.3)
rtl_stopping_point_pos = self.get_local_position_NED()
self.progress("Checking vehicle deviation from track")
y_delta = abs(rtl_start_pos.y - rtl_stopping_point_pos.y)
self.progress(f"deviated {y_delta}m from track")
if y_delta > 0.2:
raise NotAchievedException(f"RTL deviated from track {y_delta}m")
self.context_pop()
self.change_mode('LOITER')
self.do_RTL()
def ModeAllowsEntryWhenNoPilotInput(self):
'''test we can't enter modes when no RC input'''
self.upload_simple_relhome_mission([
(mavutil.mavlink.MAV_CMD_NAV_TAKEOFF, 0, 0, 20),
(mavutil.mavlink.MAV_CMD_NAV_WAYPOINT, 2000, 0, 20),
(mavutil.mavlink.MAV_CMD_NAV_RETURN_TO_LAUNCH, 0, 0, 0),
])
self.set_parameters({
"AUTO_OPTIONS": 3,
"FS_THR_ENABLE": 4,
})
self.change_mode('AUTO')
self.wait_ready_to_arm()
self.arm_vehicle()
self.wait_current_waypoint(2)
self.context_collect('STATUSTEXT')
self.set_rc(3, 200)
self.wait_statustext('Radio Failsafe', check_context=True)
self.send_cmd_do_set_mode('STABILIZE')
self.wait_statustext('in RC failsafe', check_context=True)
self.hover()
self.wait_statustext('Radio Failsafe Cleared', check_context=True)
self.do_RTL()
def RCOverridesNoRCReceiver(self):
'''test RC override timeout with no RC receiver present'''
self.set_parameters({
"MAV_GCS_SYSID": 250,
"SIM_RC_FAIL": 1,
})
self.reboot_sitl()
rc_send_enabled = True
rc1_value = 1500
rc2_value = 1500
rc3_value = 1000
rc4_value = 1500
def rc_override_sender(mav, message):
if message.get_type() == 'ATTITUDE':
if not rc_send_enabled:
return
self.mav.mav.rc_channels_override_send(
mav.target_system,
1,
rc1_value,
rc2_value,
rc3_value,
rc4_value,
65535,
65535,
65535,
65535
)
self.install_message_hook_context(rc_override_sender)
self.do_set_mode_via_command_int('LOITER')
self.wait_ready_to_arm()
self.arm_vehicle()
self.progress("Takeoff throttle")
rc3_value = 1800
self.delay_sim_time(1)
self.wait_altitude(20, 30, relative=True)
self.progress("Neutral throttle")
rc3_value = 1600
self.progress("yaw ccw")
rc4_value = 1450
self.wait_yaw_speed(math.radians(-30), accuracy=5, minimum_duration=10)
self.progress("Kill overrides and wait for speed to get neutralised")
rc_send_enabled = False
self.wait_yaw_speed(0, 1, minimum_duration=10)
self.do_set_mode_via_command_int('RTL')
self.wait_disarmed()
def MISSION_OPTION_CLEAR_MISSION_AT_BOOT(self):
'''check functionality of mission-clear-at-boot option'''
self.upload_simple_relhome_mission([
(mavutil.mavlink.MAV_CMD_NAV_TAKEOFF, 0, 0, 20),
(mavutil.mavlink.MAV_CMD_NAV_WAYPOINT, 20, 0, 20),
(mavutil.mavlink.MAV_CMD_NAV_RETURN_TO_LAUNCH, 0, 0, 0),
])
if len(self.download_using_mission_protocol(mavutil.mavlink.MAV_MISSION_TYPE_MISSION)) != 4:
raise NotAchievedException("No mission after upload?!")
self.reboot_sitl()
if len(self.download_using_mission_protocol(mavutil.mavlink.MAV_MISSION_TYPE_MISSION)) != 4:
raise NotAchievedException("No mission after reboot?!")
self.set_parameters({
"MIS_OPTIONS": 1,
})
self.reboot_sitl()
if len(self.download_using_mission_protocol(mavutil.mavlink.MAV_MISSION_TYPE_MISSION)) != 0:
raise NotAchievedException("Mission did not clear after reboot with option set")
def IgnorePilotYaw(self):
'''test RTL_OPTIONS bit 2, "IgnorePilotYaw"'''
self.takeoff(10, mode='GUIDED')
self.fly_guided_move_local(10, 10, 10, timeout=100)
orig_yaw = self.assert_receive_message('VFR_HUD').heading
def ensure_heading_stays_constant(mav, m):
print(m.get_type())
if m.get_type() != "VFR_HUD":
return
new_yaw = m.heading
if abs(orig_yaw - new_yaw) > 15:
raise NotAchievedException(f"Vehicle yawed! ({orig_yaw=} {new_yaw=}")
self.context_push()
self.install_message_hook_context(ensure_heading_stays_constant)
self.change_mode('RTL')
self.set_parameter('RTL_OPTIONS', 1 << 2)
self.set_rc(4, 2000)
self.delay_sim_time(5)
self.context_pop()
self.wait_disarmed()
def PLDNoParameters(self):
''''try enabling PLD with lat/lng/alt all zero'''
self.set_parameter('SIM_PLD_LAT', 0)
self.set_parameter('SIM_PLD_LON', 0)
self.set_parameter('SIM_PLD_HEIGHT', 0)
self.set_parameter('SIM_PLD_ENABLE', 1)
self.wait_statustext('Set SIM_PLD_LAT, SIM_PLD_LAT and SIM_PLD_ALT')
def NoRC(self):
'''check what happens if we fly with no RC'''
self.set_parameters({
"SIM_RC_FAIL": 1,
"FS_THR_ENABLE": 0,
"AUTO_OPTIONS": 3,
})
self.upload_simple_relhome_mission([
(mavutil.mavlink.MAV_CMD_NAV_TAKEOFF, 0, 0, 20),
(mavutil.mavlink.MAV_CMD_NAV_RETURN_TO_LAUNCH, 0, 0, 0),
])
self.reboot_sitl()
def verify_yaw(mav, m):
if m.get_type() != 'ATTITUDE':
return
thresh_degs = 10
current_degs = math.degrees(abs(m.yawspeed))
if current_degs > thresh_degs:
raise NotAchievedException(
f"Excessive yaw: {current_degs} deg/s > {thresh_degs} deg/s"
)
self.install_message_hook_context(verify_yaw)
self.change_mode('AUTO')
self.wait_ready_to_arm()
self.arm_vehicle()
self.wait_disarmed()
def WaitAndMaintainAttitude_RCFlight(self):
'''just test WaitAndMaintainAttitude works'''
WaitAndMaintainAttitude(self, 0, 0, epsilon=1).run()
self.takeoff(20, mode='LOITER')
WaitAndMaintainAttitude(self, 0, 0, epsilon=1).run()
self.start_subtest("Full forward stick")
self.set_rc(2, 1000)
WaitAndMaintainAttitude(self, 0, -30, epsilon=1, minimum_duration=10, timeout=60).run()
self.start_subtest("Full left stick")
self.set_rc(2, 1500)
self.set_rc(1, 1000)
WaitAndMaintainAttitude(self, -30, 0, epsilon=1).run()
self.start_subtest("Circular angle")
self.set_rc(2, 1000)
self.set_rc(1, 1000)
WaitAndMaintainAttitude(self, -20, -23, epsilon=1, timeout=120).run()
self.start_subtest("Center the sticks")
self.set_rc(2, 1500)
self.set_rc(1, 1500)
WaitAndMaintainAttitude(self, 0, 0, epsilon=1).run()
self.do_RTL()
def LuaParamSet(self):
'''test param-set.lua applet'''
self.set_parameters({
'SCR_ENABLE': 1,
})
self.context_push()
self.install_applet_script_context("param-set.lua")
self.reboot_sitl()
orig_parameter_values = self.get_parameters([
'RTL_ALT_M',
'DISARM_DELAY',
])
self.wait_ready_to_arm()
self.start_subtest("set RTL_ALT_M freely")
self.context_collect("STATUSTEXT")
self.set_parameter("RTL_ALT_M", 0.23)
self.set_parameter("RTL_ALT_M", 0.28)
error = self.statustext_in_collections("Internal Error")
if error is not None:
raise NotAchievedException("Script errored out in positive test.")
self.context_stop_collecting("STATUSTEXT")
self.start_subtest("Unable to set DISARM_DELAY freely")
self.context_push()
self.context_collect('STATUSTEXT')
self.context_collect('PARAM_ERROR')
old_disarm_delay_value = self.get_parameter('DISARM_DELAY')
self.send_set_parameter_direct('DISARM_DELAY', 78)
self.wait_statustext('param-set: param set denied (DISARM_DELAY)', check_context=True)
self.assert_received_message_field_values('PARAM_ERROR', {
"target_system": 250,
"target_component": 250,
"param_id": 'DISARM_DELAY',
"param_index": -1,
"error": mavutil.mavlink.MAV_PARAM_ERROR_PERMISSION_DENIED,
}, check_context=True, very_verbose=True)
self.assert_parameter_value('DISARM_DELAY', old_disarm_delay_value)
self.context_pop()
self.start_subtest("Disabling applet via parameter should allow freely setting DISARM_DELAY")
self.set_parameter("PARAM_SET_ENABLE", 0)
self.set_parameter("DISARM_DELAY", 56)
self.start_subtest("Re-enabling applet via parameter should stop freely setting DISARM_DELAY")
self.context_push()
self.context_collect('STATUSTEXT')
self.context_collect('PARAM_ERROR')
self.set_parameter("PARAM_SET_ENABLE", 1)
old_disarm_delay_value = self.get_parameter('DISARM_DELAY')
self.send_set_parameter_direct('DISARM_DELAY', 78)
self.wait_statustext('param-set: param set denied (DISARM_DELAY)', check_context=True)
self.assert_received_message_field_values('PARAM_ERROR', {
"target_system": 250,
"target_component": 250,
"param_id": 'DISARM_DELAY',
"param_index": -1,
"error": mavutil.mavlink.MAV_PARAM_ERROR_PERMISSION_DENIED,
}, check_context=True, very_verbose=True)
self.assert_parameter_value('DISARM_DELAY', old_disarm_delay_value)
self.context_pop()
self.start_subtest("Ensure that parameter values are persistent")
self.set_parameter('DISARM_DELAY', 111)
self.reboot_sitl()
self.assert_parameter_value('DISARM_DELAY', 111)
self.set_parameters(orig_parameter_values)
self.context_pop()
def do_land(self):
self.change_mode('LAND')
self.wait_disarmed()
def PeriphMultiUARTTunnel(self):
'''test peripheral multi-uart tunneling'''
self.progress("Building Periph")
periph_board = 'sitl_periph_can_to_serial'
periph_builddir = util.reltopdir('build-periph')
util.build_SITL(
'bin/AP_Periph',
board=periph_board,
clean=False,
configure=True,
debug=True,
extra_configure_args=[
'--out', periph_builddir,
],
)
binary_path = ""
self.progress("Starting Periph simulation")
self.context_push()
original_speedup = self.speedup
self.speedup = 1
periph_exp = None
ex = None
try:
binary_path = pathlib.Path(periph_builddir, periph_board, 'bin', 'AP_Periph')
periph_rundir = util.reltopdir('run-periph')
if not os.path.exists(periph_rundir):
os.mkdir(periph_rundir)
periph_exp = util.start_SITL(
binary_path,
cwd=periph_rundir,
stdout_prefix="periph",
gdb=self.gdb,
valgrind=self.valgrind,
customisations=[
'-I', str(1),
'--serial0', 'mcast:',
'--serial1', 'tcp:2',
'--serial2', 'tcp:3',
],
speedup=self.speedup
)
self.expect_list_add(periph_exp)
self.progress("Reconfiguring for multicast")
self.customise_SITL_commandline([
"--serial5=mcast:",
],
model="octa-quad:@ROMFS/models/Callisto.json",
defaults_filepath=self.model_defaults_filepath('Callisto'),
wipe=True,
)
self.set_parameters({
'SERIAL5_PROTOCOL': 2,
"CAN_P1_DRIVER": 1,
})
self.reboot_sitl()
self.set_parameters({
"CAN_D1_UC_SER_EN": 1,
"CAN_D1_UC_S1_IDX": 1,
"CAN_D1_UC_S1_NOD": 125,
"CAN_D1_UC_S1_PRO": 2,
"CAN_D1_UC_S2_IDX": 2,
"CAN_D1_UC_S2_NOD": 125,
"CAN_D1_UC_S2_PRO": 2,
})
self.reboot_sitl()
self.progress("Connect to the serial port on the peripheral, which should be talking mavlink")
self.drain_mav()
mav2 = mavutil.mavlink_connection(
"tcp:localhost:5772",
robust_parsing=True,
source_system=9,
source_component=9,
)
self.assert_receive_message("HEARTBEAT", mav=mav2, very_verbose=True, timeout=5)
self.drain_mav()
self.progress("Connect to the other serial port on the peripheral, which should also be talking mavlink")
self.drain_mav()
mav3 = mavutil.mavlink_connection(
"tcp:localhost:5773",
robust_parsing=True,
source_system=10,
source_component=10,
)
self.assert_receive_message("HEARTBEAT", mav=mav3, very_verbose=True, timeout=5)
self.drain_mav()
tstart = self.get_sim_time()
while True:
now = self.get_sim_time()
if now - tstart > 30:
break
self.assert_receive_message('HEARTBEAT', mav=mav2, verbose=2)
self.drain_mav()
self.assert_receive_message('HEARTBEAT', mav=mav3, verbose=2)
self.drain_mav()
except Exception as e:
self.print_exception_caught(e)
ex = e
finally:
if periph_exp is not None:
self.progress("Stopping Periph")
self.expect_list_remove(periph_exp)
util.pexpect_close(periph_exp)
self.speedup = original_speedup
self.context_pop()
self.reboot_sitl()
if ex is not None:
raise ex
def RCProtocolFailsafe(self):
'''ensure we failsafe when the RC protocol failsafe is set'''
self.takeoff(10, mode='LOITER')
self.set_parameter('SIM_RC_FAIL', 3)
self.wait_mode('RTL')
self.wait_disarmed()
def LUAConfigProfile(self):
'''test the config_profiles.lua example script'''
self.customise_SITL_commandline(
[],
defaults_filepath=self.model_defaults_filepath('Callisto'),
model="octa-quad:@ROMFS/models/Callisto.json",
wipe=True,
)
self.install_example_script_context("config_profiles.lua")
self.set_parameters({
'SCR_ENABLE': 1,
})
self.context_push()
self.context_push()
self.context_collect('STATUSTEXT')
self.reboot_sitl()
self.assert_prearm_failure('CFG: Reboot needed for config change', other_prearm_failures_fatal=False)
self.context_pop()
self.reboot_sitl()
self.wait_ready_to_arm()
self.set_parameter("CFG_PAY_SEL", 1)
self.wait_parameter_values({
"GRIP_ENABLE": 1,
"RC8_OPTION": 19,
})
self.set_parameter("CFG_PAY_SEL", 3)
self.wait_parameter_values({
"GRIP_ENABLE": 0,
"RC8_OPTION": 0,
})
self.set_parameter("CFG_PAY_SEL", 2)
self.wait_parameter_values({
"GRIP_ENABLE": 1,
"RC8_OPTION": 19,
})
self.progress("Apply default parameters")
self.set_parameter("CFG_PAY_SEL", 0)
self.wait_parameter_values({
"GRIP_ENABLE": 0,
"RC8_OPTION": 0,
})
self.set_parameter("CFG_PAY_SEL", 22)
self.wait_statustext("Invalid profile selected for PAY")
self.start_subtest("Test the different modes (nothing / apply / defaults)")
self.context_pop()
self.start_subtest("Testing of different validations")
def set_config_profiles_config_domains(new_domains):
path = pathlib.Path(self.installed_script_path("config_profiles.lua"))
content = path.read_text()
start_marker = '-- This is a marker for the start of the config_domains; it is used to swap these out for CI testing'
end_marker = '-- This is a marker for the end of the config_domains; it is used to swap these out for CI testing'
pattern = re.compile(
rf"({re.escape(start_marker)})(.*?){re.escape(end_marker)}",
re.DOTALL
)
if not pattern.search(content):
raise ValueError("Start or end marker not found in file")
replaced = pattern.sub(rf"\1\n{new_domains}\n{end_marker}", content)
path.write_text(replaced)
def assert_prearm_failure_for_domains_string(domains_string, prearm_failure_reason):
self.start_subtest(f"Testing domains for ({prearm_failure_reason})")
set_config_profiles_config_domains(domains_string)
self.context_push()
self.context_collect('STATUSTEXT')
self.reboot_sitl()
self.assert_prearm_failure(prearm_failure_reason, other_prearm_failures_fatal=False, timeout=120)
self.context_pop()
domain_param_not_in_defaults = """
-- a table of param indexes to help avoid annoying conflicts:
local param_index = {
["enable"] = 1,
["pm_filter"] = 2,
["DMN_A"] = 10,
}
local config_domains = {
DOMAIN_A = {
param_name = "DMN_A",
param_sel_index = 3,
all_param_defaults = { -- all parameters present in the params for each option
},
profiles = {
[1] = {
name = "Param Not in defaults",
params = {
["ACRO_BAL_PITCH"] = 1,
},
},
},
}
}
"""
assert_prearm_failure_for_domains_string(domain_param_not_in_defaults, "Not in defaults")
domain_param_not_in_defaults = """
local param_index = {
["enable"] = 1,
["pm_filter"] = 2,
["DMN_A"] = 10,
["DMN_B"] = 11,
}
local config_domains = {
DOMAIN_A = {
param_name = "DMN_A",
param_sel_index = 5,
all_param_defaults = { -- all parameters present in the params for each option
["ACRO_BAL_PITCH"] = 1,
},
profiles = {
[1] = {
name = "exists in two domains",
params = {
["ACRO_BAL_PITCH"] = 1,
},
},
},
},
DOMAIN_B = {
param_name = "DMN_B",
param_sel_index = 3,
all_param_defaults = { -- all parameters present in the params for each option
["ACRO_BAL_PITCH"] = 1,
},
profiles = {
[1] = {
name = "param in two domains",
params = {
["ACRO_BAL_PITCH"] = 1,
},
},
},
},
}
"""
assert_prearm_failure_for_domains_string(domain_param_not_in_defaults, "CFG: ACRO_BAL_PITCH exists in two")
domain_profile_param_same_as_domain_default_param_value = """
local param_index = {
["enable"] = 1,
["pm_filter"] = 2,
["DMN_A"] = 10,
}
local config_domains = {
DOMAIN_A = {
param_name = "DMN_A",
param_sel_index = 5,
all_param_defaults = { -- all parameters present in the params for each option
["BATT_CAPACITY"] = 6,
},
profiles = {
[1] = {
name = "param value same as default",
params = {
["BATT_CAPACITY"] = 6,
},
},
},
},
}
"""
assert_prearm_failure_for_domains_string(
domain_profile_param_same_as_domain_default_param_value,
"has the same value as"
)
domain_profile_param_must_be_set_when_mode_is_apply_defaults = """
local param_index = {
["enable"] = 1,
["pm_filter"] = 2,
["DMN_A"] = 10,
}
local config_domains = {
DOMAIN_A = {
param_name = "DMN_A",
param_sel_index = 0,
all_param_defaults = { -- all parameters present in the params for each option
["BATT_CAPACITY"] = must_be_set,
},
profiles = {
[1] = {
name = "param must be set",
params = {
["BATT_CAPACITY"] = 6,
},
},
},
},
}
"""
assert_prearm_failure_for_domains_string(
domain_profile_param_must_be_set_when_mode_is_apply_defaults,
"BATT_CAPACITY in DMN_A is must-be-set but al"
)
def ScriptParamRegistration(self):
'''test parameter script registration'''
self.set_parameters({
'SCR_ENABLE': 1,
})
script_content = """
local PARAM_TABLE_KEY = 10
local PARAM_TABLE_PREFIX = "TST_"
assert(param:add_table(PARAM_TABLE_KEY, PARAM_TABLE_PREFIX, 16), 'could not add param table')
-- add a parameter and bind it to a variable
function bind_add_param(name, idx, default_value)
assert(param:add_param(PARAM_TABLE_KEY, idx, name, default_value), string.format('could not add param %s', PARAM_TABLE_PREFIX .. name))
return Parameter(PARAM_TABLE_PREFIX .. name)
end
local PARAM_A = bind_add_param("A", 5, 22)
local PARAM_B = bind_add_param("B", 1, 33)
function update()
gcs:send_text(3, string.format("test script running"))
return update, 1000
end
return update, 1000
"""
self.install_script_content_context("test.lua", script_content)
self.reboot_sitl()
self.wait_statustext('test script running')
self.assert_parameter_value('TST_A', 22)
self.assert_parameter_value('TST_B', 33)
all_params = self.fetch_all_parameters()
for pname in "TST_A", "TST_B":
if pname not in all_params:
raise ValueError(f"{pname} not in fetched-all-parameters")
self.start_subtest("Remove parameter at runtime")
script_content = """
local PARAM_TABLE_KEY = 10
local PARAM_TABLE_PREFIX = "TST_"
assert(param:add_table(PARAM_TABLE_KEY, PARAM_TABLE_PREFIX, 16), 'could not add param table')
-- add a parameter and bind it to a variable
function bind_add_param(name, idx, default_value)
assert(param:add_param(PARAM_TABLE_KEY, idx, name, default_value), string.format('could not add param %s', PARAM_TABLE_PREFIX .. name))
return Parameter(PARAM_TABLE_PREFIX .. name)
end
local PARAM_B = bind_add_param("B", 1, 33)
function update()
gcs:send_text(3, string.format("test script running"))
return update, 1000
end
return update, 1000
"""
self.install_script_content_context("test.lua", script_content)
self.scripting_restart()
self.wait_statustext('restart')
self.wait_statustext('test script running')
self.assert_parameter_value('TST_B', 33)
all_params = self.fetch_all_parameters()
for pname in ["TST_B"]:
if pname not in all_params:
raise ValueError(f"{pname} not in fetched-all-parameters")
for pname in ["TST_A"]:
if pname in all_params:
raise ValueError(f"{pname} in fetched-all-parameters when it should have gone away")
def tests2b(self):
'''return list of all tests'''
ret = ([
self.MotorVibration,
Test(self.DynamicNotches, attempts=4),
self.PositionWhenGPSIsZero,
self.DynamicRpmNotches,
self.DynamicRpmNotchesRateThread,
self.PIDNotches,
self.mission_NAV_LOITER_TURNS,
self.mission_NAV_LOITER_TURNS_off_center,
self.StaticNotches,
self.LuaParamSet,
self.RefindGPS,
Test(self.GyroFFT, attempts=1, speedup=8),
Test(self.GyroFFTHarmonic, attempts=4, speedup=8),
Test(self.GyroFFTAverage, attempts=1, speedup=8),
Test(self.GyroFFTContinuousAveraging, attempts=4, speedup=8),
self.WPYawBehaviour1RTL,
self.GyroFFTPostFilter,
self.GyroFFTMotorNoiseCheck,
self.CompassReordering,
self.SixCompassCalibrationAndReordering,
self.CRSF,
self.MotorTest,
self.AltEstimation,
self.EKFSource,
self.GSF,
self.GSF_reset,
self.AP_Avoidance,
self.RTL_ALT_FINAL_M,
self.SMART_RTL,
self.SMART_RTL_EnterLeave,
self.SMART_RTL_Repeat,
self.RTL_TO_RALLY,
self.RTLYaw,
self.FlyEachFrame,
self.ScriptParamRegistration,
self.GPSBlending,
self.GPSWeightedBlending,
self.GPSBlendingLog,
self.GPSBlendingAffinity,
self.DataFlash,
Test(self.DataFlashErase, attempts=8),
self.Callisto,
self.PerfInfo,
self.ModeAllowsEntryWhenNoPilotInput,
self.Replay,
self.FETtecESC,
self.ProximitySensors,
self.GroundEffectCompensation_touchDownExpected,
self.GroundEffectCompensation_takeOffExpected,
self.DO_CHANGE_SPEED,
self.MISSION_START,
self.AUTO_LAND_TO_BRAKE,
self.WPNAV_SPEED,
self.RTLStoppingDistanceSpeed,
self.WPNAV_SPEED_UP,
self.WPNAV_SPEED_DN,
self.DO_WINCH,
self.SensorErrorFlags,
self.GPSForYaw,
self.DefaultIntervalsFromFiles,
self.GPSTypes,
self.MultipleGPS,
self.WatchAlts,
self.GuidedEKFLaneChange,
self.Sprayer,
self.AutoContinueOnRCFailsafe,
self.EK3_RNG_USE_HGT,
self.NoRC,
self.RCOverridesNoRCReceiver,
self.TerrainDBPreArm,
self.ThrottleGainBoost,
self.ScriptMountPOI,
self.ScriptMountAllModes,
self.ScriptCopterPosOffsets,
self.MountSolo,
self.FlyMissionTwice,
self.FlyMissionTwiceWithReset,
self.MissionIndexValidity,
self.InvalidJumpTags,
self.IMUConsistency,
self.AHRSTrimLand,
self.IBus,
self.WaitAndMaintainAttitude_RCFlight,
self.GuidedYawRate,
self.RudderDisarmMidair,
self.NoArmWithoutMissionItems,
self.DO_CHANGE_SPEED_in_guided,
self.ArmSwitchAfterReboot,
self.RPLidarA1,
self.RPLidarA2,
self.MISSION_OPTION_CLEAR_MISSION_AT_BOOT,
self.SafetySwitch,
self.RCProtocolFailsafe,
self.BrakeZ,
self.MAV_CMD_DO_FLIGHTTERMINATION,
self.MAV_CMD_DO_LAND_START,
self.MAV_CMD_DO_SET_GLOBAL_ORIGIN,
self.MAV_CMD_SET_EKF_SOURCE_SET,
self.MAV_CMD_NAV_TAKEOFF,
self.MAV_CMD_NAV_TAKEOFF_command_int,
self.Ch6TuningWPSpeed,
self.DualTuningChannels,
self.PILOT_THR_BHV,
self.GPSForYawCompassLearn,
self.CameraLogMessages,
self.LoiterToGuidedHomeVSOrigin,
self.GuidedModeThrust,
self.CompassMot,
self.AutoRTL,
self.EK3_OGN_HGT_MASK_climbing,
self.EK3_OGN_HGT_MASK,
self.FarOrigin,
self.GuidedForceArm,
self.GuidedWeatherVane,
self.LUAConfigProfile,
self.Clamp,
self.GripperReleaseOnThrustLoss,
self.GripperInitialPosition,
self.REQUIRE_LOCATION_FOR_ARMING,
self.LoggingFormat,
self.MissionRTLYawBehaviour,
self.BatteryInternalUseOnly,
self.MAV_CMD_MISSION_START_p1_p2,
self.ScriptingAHRSSource,
self.CommonOrigin,
self.AHRSOriginRecorded,
self.TestTetherStuck,
self.ScriptingFlipMode,
self.ScriptingFlyVelocity,
self.EK3_EXT_NAV_vel_without_vert,
self.CompassLearnCopyFromEKF,
self.AHRSAutoTrim,
self.Ch6TuningLoitMaxXYSpeed,
self.IgnorePilotYaw,
self.TestEKF3CompassFailover,
self.test_EKF3_option_disable_lane_switch,
self.PLDNoParameters,
self.PeriphMultiUARTTunnel,
self.EKF3SRCPerCore,
])
return ret
def testcan(self):
ret = ([
self.CANGPSCopterMission,
self.TestLogDownloadMAVProxyCAN,
])
return ret
def BattCANSplitAuxInfo(self):
'''test CAN battery periphs'''
self.start_subtest("Swap UAVCAN backend at runtime")
self.set_parameters({
"CAN_P1_DRIVER": 1,
"BATT_MONITOR": 4,
"BATT2_MONITOR": 8,
"BATT_SERIAL_NUM": 0,
"BATT2_SERIAL_NUM": 0,
"BATT_OPTIONS": 128,
"BATT2_OPTIONS": 128,
})
self.reboot_sitl()
self.delay_sim_time(2)
self.set_parameters({
"BATT_MONITOR": 8,
"BATT2_MONITOR": 4,
})
self.delay_sim_time(2)
self.set_parameters({
"BATT_MONITOR": 4,
"BATT2_MONITOR": 8,
})
self.delay_sim_time(2)
self.set_parameters({
"BATT_MONITOR": 8,
"BATT2_MONITOR": 4,
})
self.delay_sim_time(2)
def BattCANReplaceRuntime(self):
'''test CAN battery periphs'''
self.start_subtest("Replace UAVCAN backend at runtime")
self.set_parameters({
"CAN_P1_DRIVER": 1,
"BATT_MONITOR": 11,
})
self.reboot_sitl()
self.delay_sim_time(2)
self.set_parameters({
"BATT_MONITOR": 8,
})
self.delay_sim_time(2)
def testcanbatt(self):
ret = ([
self.BattCANReplaceRuntime,
self.BattCANSplitAuxInfo,
])
return ret
def tests(self):
ret = []
ret.extend(self.tests1a())
ret.extend(self.tests1b())
ret.extend(self.tests1c())
ret.extend(self.tests1d())
ret.extend(self.tests1e())
ret.extend(self.tests2a())
ret.extend(self.tests2b())
return ret
def disabled_tests(self):
return {
"Parachute": "See https://github.com/ArduPilot/ardupilot/issues/4702",
"GroundEffectCompensation_takeOffExpected": "Flapping",
"GroundEffectCompensation_touchDownExpected": "Flapping",
"FlyMissionTwice": "See https://github.com/ArduPilot/ardupilot/pull/18561",
"CompassMot": "Causes an arithmetic exception in the EKF",
"SMART_RTL_EnterLeave": "Causes a panic",
"SMART_RTL_Repeat": "Currently fails due to issue with loop detection",
"RTLStoppingDistanceSpeed": "Currently fails due to vehicle going off-course",
}
class AutoTestCopterTests1a(AutoTestCopter):
def tests(self):
return self.tests1a()
class AutoTestCopterTests1b(AutoTestCopter):
def tests(self):
return self.tests1b()
class AutoTestCopterTests1c(AutoTestCopter):
def tests(self):
return self.tests1c()
class AutoTestCopterTests1d(AutoTestCopter):
def tests(self):
return self.tests1d()
class AutoTestCopterTests1e(AutoTestCopter):
def tests(self):
return self.tests1e()
class AutoTestCopterTests2a(AutoTestCopter):
def tests(self):
return self.tests2a()
class AutoTestCopterTests2b(AutoTestCopter):
def tests(self):
return self.tests2b()
class AutoTestCAN(AutoTestCopter):
def tests(self):
return self.testcan()
class AutoTestBattCAN(AutoTestCopter):
def tests(self):
return self.testcanbatt()