Which servo to which set_actuator value?

Hi!
Could I get some help understanding how to control the correct servo with set_actuator() from mavsdk.telemetry class?

Here is the specific script I’m using to test the command below. I’m using a link to a public repository for ease of use, but I’ll also paste the code down here below.

testMavlink/2_1_test_both_control.py at 99de3397fe9f9fefd6590aeb5b083ca9c48ce22c · FredJones4/testMavlink · GitHub

I was hoping that the specific commands I have commented on in the test code could be mapped in QGroundControl’s Actuator setup by assigning the motor/aileron/etc. to the same number position in FMU Aux or FMU MAIN. More on that below.

I’m using a Holybro Pixhawk 6X, and I want to use it to control a tiltrotor. My plan was to set up HITL tiltrotor for testing purposes, but I had issues setting it up on Ubuntu 22.04; I didn’t realize it wasn’t officially supported.

Again, I would brute-force testing on physical hardware to see which control controls which servo in QGroundControl, but I can’t get into offboard mode with the code because I’m controlling when indoors (maybe spoof gps signal somehow for indoor testing?)

I’m also confused because when I read on PX4 documentation about control groups, it seems different from what I am using in my code. Here’s what I’m referring to:

Could I get some direction on this? Any help would be appreciated.

Blockquote

“”"

In PX4 v1.9.0 Only first four Control Groups are supported
(PX4-Autopilot/src/modules/mavlink/mavlink_receiver.cpp at v1.9.0 · PX4/PX4-Autopilot · GitHub)

This is confusing, since only 2 control groups can be used anyway. What does it mean?

This site may have the answer:

https://jalpanchal1.gitbooks.io/px4-developer-guide/content/en/concept/mixing.html


Code for Reference

import asyncio
from mavsdk import System
from mavsdk.offboard import OffboardError, ActuatorControl, ActuatorControlGroup, PositionNedYaw
import json

LOCAL_HOST_TEST = "udp://:14540"
CURR_USB_CONNECTION = "/dev/ttyACM1"
LIKELY_USB_PI_CONNECTION = "/dev/ttyUSB0"

def print_pretty_dict(d, indent=4):
    """
    Prints the contents of a dictionary (and nested dictionaries) in a pretty format.
    """
    def print_dict(d, level=0):
        for key, value in d.items():
            if isinstance(value, dict):
                print(' ' * (level * indent) + f"{key}:")
                print_dict(value, level + 1)
            else:
                print(' ' * (level * indent) + f"{key}: {value}")

    print_dict(d)

def clamp(value, min_value, max_value):
    """Clamps the value between min_value and max_value."""
    return max(min_value, min(value, max_value))

async def set_actuator_control_homemade(drone, elevator, aileron, rudder, starboard_front_motor, 
                                        rear_motor, front_motor, starboard_rear_motor, forward_propulsion_motor):
    """Sets the actuator control with two control groups and specific control modifications."""
    
    group1_controls = [
        float('nan'),  # NaN for RC ROLL
        float('nan'),  # NaN for RC PITCH
        float('nan'),  # NaN for RC YAW
        float('nan'),  # NaN for RC Throttle
        clamp(elevator, -1, 1),
        clamp(aileron, -1, 1),
        clamp(rudder, -1, 1),
        clamp(-aileron, -1, 1),
    ]

    group2_controls = [
        float('nan'),  # Unused
        float('nan'),  # Unused
        float('nan'),  # Unused
        clamp(starboard_front_motor, 0, 1),
        clamp(rear_motor, 0, 1),
        clamp(front_motor, 0, 1),
        clamp(starboard_rear_motor, 0, 1),
        clamp(forward_propulsion_motor, 0, 1)
    ]
    
    right_aileron = clamp(-aileron, -1, 1)  # Reverse aileron for right
    group2_controls[1] = right_aileron  # Update right aileron in the control group

    control_group1 = ActuatorControlGroup(controls=group1_controls)
    control_group2 = ActuatorControlGroup(controls=group2_controls)

    try:
        await drone.offboard.set_actuator_control(ActuatorControl(groups=[control_group1, control_group2]))
    except OffboardError as e:
        print(f"Setting actuator control failed with error: {e}")

async def setup_mavlink_offboard(drone, curr_conn=LOCAL_HOST_TEST):
    """
    Abstracts away the setup of the MAVSDK MAVLink protocols.
    """
    await drone.connect(system_address=curr_conn)

    print("Waiting for drone to connect...")
    async for state in drone.core.connection_state():
        if state.is_connected:
            print(f"-- Connected to drone!")
            break

    print("Waiting for drone to have a global position estimate...")
    async for health in drone.telemetry.health():
        if health.is_global_position_ok and health.is_home_position_ok:
            print("-- Global position estimate OK")
            break

    print("-- Arming")
    await drone.action.arm()

    print("-- Setting initial setpoint")
    await drone.offboard.set_position_ned(PositionNedYaw(0.0, 0.0, 0.0, 0.0))

    print("-- Starting offboard")
    try:
        await drone.offboard.start()
    except OffboardError as error:
        print(f"Starting offboard mode failed with error code: {error._result.result}")
        print("-- Disarming")
        await drone.action.disarm()
        return False

    return True

async def set_actuator_control_test(drone, elevator, aileron, rudder, starboard_front_motor,
    rear_motor, front_motor, starboard_rear_motor, forward_propulsion_motor):
    """ Testable version of set_actuator_control function. """
    return await set_actuator_control_homemade(drone, elevator, aileron, rudder, 
                                               starboard_front_motor, rear_motor, front_motor, starboard_rear_motor, 
                                               forward_propulsion_motor)

async def test_set_actuator_control():
    """ Tests for set_actuator_control function. """
    drone = System()
    await setup_mavlink_offboard(drone)

    print("Running test cases...")
    print("-- Go 0m North, 0m East, -5m Down within local coordinate system")
    await drone.offboard.set_position_ned(PositionNedYaw(0.0, 0.0, -5.0, 0.0))
    await asyncio.sleep(10)
    
    print("Test case 1: Normal input")
    await set_actuator_control_test(drone, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5)
    await asyncio.sleep(10)
    
    print("-- Go 5m North, 10m East, -5m Down within local coordinate system")
    await drone.offboard.set_position_ned(PositionNedYaw(5.0, 10.0, -5.0, 90.0))
    await asyncio.sleep(5)
    
    print("Test case 2: Inputs outside bounds")
    await set_actuator_control_test(drone, 1.2, -1.5, 0.7, 1.5, -0.2, 0.8, 0.9, 1.1)
    await asyncio.sleep(10)  
    
    print("Test case 3: Using two control groups")
    await set_actuator_control_test(drone, 0.3, -0.3, 0.0, 0.1, 0.2, 0.9, 0.4, 0.0)
    await asyncio.sleep(10)
    
    print("Test case 4: All values at bounds")
    await set_actuator_control_test(drone, 1.0, -1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0)
    await asyncio.sleep(10)

    print("-- Go 5m North, 10m East, -5m Down within local coordinate system")
    await drone.offboard.set_position_ned(PositionNedYaw(5.0, 10.0, -5.0, 90.0))
    await asyncio.sleep(5)

    print("-- Go 0m North, 10m East, 0m Down within local coordinate system, turn to face South")
    await drone.offboard.set_position_ned(PositionNedYaw(0.0, 10.0, 0.0, 180.0))
    await asyncio.sleep(10)

    print("-- Stopping offboard")
    try:
        await drone.offboard.stop()
    except OffboardError as error:
        print(f"Stopping offboard mode failed with error code: {error._result.result}")

async def run_tests():
    """ Run all test cases. """
    await test_set_actuator_control()

if __name__ == "__main__":
    asyncio.run(run_tests())

Offboard set actuator control is broken with PX4 v1.14 and after, pretty sure. The feature was - unfortunately - removed.

1 Like

Hi!
Thanks for replying so quickly.
That is unfortunate. Is there a quick solution to do what I’m trying to do?

Oh! My bad! I meant set_actuator_control(). Is that still what you meant?

I noticed a comment you made last year about the c++ library set_actuator_async.

Does this function still work for C++?

Do you know if the v1.13 stable version would still run the set_actuator_control?

You can try v1.13.

With v1.14 and later, the recommendation was to use the ROS 2 stuff for direct motor control, from what I remember.

2 Likes