Skip to content

Commit d1cc22c

Browse files
Tools: Autotest: add test for MAV_CMD_DO_SET_ACTUATOR
Map each supported actuator (1..6) onto its own SERVO output with a distinct PWM range and exercise -1, -0.5, 0, +0.5, +1 against every slot, asserting that the resulting PWM matches servo_min + (servo_max - servo_min) * (value + 1) / 2 on the right servo channel. Also assert: * a single COMMAND_LONG carrying values for all six supported actuators applies them in one shot and preserves the param->actuator mapping (which exercises the param5/param6 scaling path through convert_COMMAND_LONG_to_COMMAND_INT); * a follow-up command that only moves one actuator leaves the other actuators' PWMs untouched (NaN-as-ignore semantics for both the float p1..p4 path and the scaled-int p5/p6 path); * sending the command as COMMAND_INT with a non-zero actuator-set index in z returns MAV_RESULT_DENIED, since only the first set (actuators 1..6) is supported. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 02fd99d commit d1cc22c

1 file changed

Lines changed: 130 additions & 0 deletions

File tree

Tools/autotest/ardusub.py

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -757,6 +757,135 @@ def MAV_CMD_DO_CHANGE_SPEED(self):
757757
self.wait_groundspeed(speed-0.2, speed+0.2, minimum_duration=2, timeout=60)
758758
self.disarm_vehicle()
759759

760+
def MAV_CMD_DO_SET_ACTUATOR(self):
761+
'''Test MAV_CMD_DO_SET_ACTUATOR command'''
762+
# Map each supported actuator (1..6) onto a distinct SERVO output
763+
# with a distinct PWM range so any cross-actuator bleed-through
764+
# would change a PWM we did not intend to touch.
765+
# k_actuator1..k_actuator6 = 184..189.
766+
actuator_servos = {
767+
1: {"channel": 9, "min": 1100, "max": 1900, "trim": 1500},
768+
2: {"channel": 10, "min": 1200, "max": 1800, "trim": 1500},
769+
3: {"channel": 11, "min": 1050, "max": 1950, "trim": 1500},
770+
4: {"channel": 12, "min": 1000, "max": 2000, "trim": 1500},
771+
5: {"channel": 13, "min": 1150, "max": 1850, "trim": 1500},
772+
6: {"channel": 14, "min": 1075, "max": 1925, "trim": 1500},
773+
}
774+
775+
params = {}
776+
for actuator_num, cfg in actuator_servos.items():
777+
chan = cfg["channel"]
778+
params[f"SERVO{chan}_FUNCTION"] = 183 + actuator_num # k_actuator1==184
779+
params[f"SERVO{chan}_MIN"] = cfg["min"]
780+
params[f"SERVO{chan}_MAX"] = cfg["max"]
781+
params[f"SERVO{chan}_TRIM"] = cfg["trim"]
782+
self.set_parameters(params)
783+
self.reboot_sitl()
784+
785+
self.wait_ready_to_arm()
786+
self.arm_vehicle()
787+
788+
# Actuator value mapping (matches AP_Actuators::set_actuator):
789+
# internal_value = (input_value + 1) * 0.5 (maps -1..1 to 0..1)
790+
# pwm = servo_min + (servo_max - servo_min) * internal_value
791+
792+
def set_actuators(values):
793+
'''Send MAV_CMD_DO_SET_ACTUATOR (COMMAND_LONG) with values
794+
for actuators 1..6.
795+
796+
values is a dict {actuator_num: value}; missing entries are
797+
sent as NaN ("ignore") per the MAVLink spec.
798+
'''
799+
params = [float('nan')] * 6
800+
for actuator_num, value in values.items():
801+
params[actuator_num - 1] = value
802+
self.run_cmd(
803+
187, # MAV_CMD_DO_SET_ACTUATOR
804+
p1=params[0],
805+
p2=params[1],
806+
p3=params[2],
807+
p4=params[3],
808+
p5=params[4],
809+
p6=params[5],
810+
p7=0, # actuator-set index 0 (actuators 1..6)
811+
)
812+
813+
def expected_pwm(actuator_num, value):
814+
cfg = actuator_servos[actuator_num]
815+
internal = (value + 1) * 0.5
816+
return int(cfg["min"] + (cfg["max"] - cfg["min"]) * internal)
817+
818+
# Exercise each supported actuator independently across the full
819+
# input range, asserting the right servo lands on the right PWM.
820+
for actuator_num in sorted(actuator_servos):
821+
cfg = actuator_servos[actuator_num]
822+
for value, label in [
823+
(-1.0, "minimum"),
824+
(0.0, "center"),
825+
(1.0, "maximum"),
826+
(0.5, "75%"),
827+
(-0.5, "25%"),
828+
]:
829+
self.progress(
830+
f"Testing actuator {actuator_num} set to {value} ({label})"
831+
)
832+
set_actuators({actuator_num: value})
833+
self.wait_servo_channel_value(
834+
cfg["channel"],
835+
expected_pwm(actuator_num, value),
836+
timeout=5,
837+
)
838+
839+
# A single command carrying values for every supported actuator
840+
# must apply all six in one shot. Pick distinct values so a
841+
# mis-ordered param->actuator mapping would be caught.
842+
self.progress("Testing all six actuators set in one COMMAND_LONG")
843+
combined = {1: -1.0, 2: -0.6, 3: -0.2, 4: 0.2, 5: 0.6, 6: 1.0}
844+
set_actuators(combined)
845+
for actuator_num, value in combined.items():
846+
cfg = actuator_servos[actuator_num]
847+
self.wait_servo_channel_value(
848+
cfg["channel"],
849+
expected_pwm(actuator_num, value),
850+
timeout=5,
851+
)
852+
853+
# NaN ("ignore") slots must leave the previous value untouched.
854+
# Re-set actuators 1 and 5 to center (covering both the float
855+
# p1..p4 path and the scaled-int p5/p6 path), then send a
856+
# command that only moves actuator 4 and verify 1 and 5 stayed
857+
# at center.
858+
self.progress("Testing NaN slots leave previous values untouched")
859+
set_actuators({1: 0.0, 5: 0.0})
860+
self.wait_servo_channel_value(
861+
actuator_servos[1]["channel"], expected_pwm(1, 0.0), timeout=5,
862+
)
863+
self.wait_servo_channel_value(
864+
actuator_servos[5]["channel"], expected_pwm(5, 0.0), timeout=5,
865+
)
866+
set_actuators({4: 0.0})
867+
self.wait_servo_channel_value(
868+
actuator_servos[4]["channel"], expected_pwm(4, 0.0), timeout=5,
869+
)
870+
self.wait_servo_channel_value(
871+
actuator_servos[1]["channel"], expected_pwm(1, 0.0), timeout=2,
872+
)
873+
self.wait_servo_channel_value(
874+
actuator_servos[5]["channel"], expected_pwm(5, 0.0), timeout=2,
875+
)
876+
877+
# An actuator-set index other than 0 is rejected because only
878+
# actuators 1..6 of the first set are supported.
879+
self.progress("Testing actuator-set index != 0 is rejected")
880+
self.run_cmd_int(
881+
187, # MAV_CMD_DO_SET_ACTUATOR
882+
p1=0, p2=0, p3=0, p4=0,
883+
x=0, y=0, z=1,
884+
want_result=mavutil.mavlink.MAV_RESULT_DENIED,
885+
)
886+
887+
self.disarm_vehicle()
888+
760889
def GPSForYaw(self):
761890
'''Test consumption of heading from NMEA GPS and its propagation to ATTITUDE'''
762891

@@ -1352,6 +1481,7 @@ def tests(self):
13521481
self.MAV_CMD_DO_CHANGE_SPEED,
13531482
self.MAV_CMD_CONDITION_YAW,
13541483
self.MAV_CMD_DO_REPOSITION,
1484+
self.MAV_CMD_DO_SET_ACTUATOR,
13551485
self.TerrainMission,
13561486
self.SetGlobalOrigin,
13571487
self.BackupOrigin,

0 commit comments

Comments
 (0)