Testing Actuator_Motors, changing from VTOL to fixed-wing, and Actuator_Servos in Gazebo SITL

Hi!
Could I get some help getting my actuator_servo and actuator_motor publishings connected to my SITL? I’m also looking to properly transition to fixed-wing, if I can get help there too.
Current command to do fixed-wing, as I understand it:

# Create and publish VehicleCommand message to change mode
            cmd = VehicleCommand()
            cmd.command = 84  # Command to change flight mode (see https://docs.px4.io/main/en/msg_docs/VehicleCommand.html, line 84)
            cmd.param1 = 1.0     # Parameter to indicate the desired mode (check specific parameter for your system)
            cmd.target_system = 1
            cmd.source_system = 1
            cmd.target_component = 0
            cmd.source_component = 0
            self.vehicle_command_pub.publish(cmd)
            self.get_logger().info("Published VehicleCommand message to change mode to fixed-wing")

Background:

  • I want to test automating actuator and motor outputs so I can wrap my simulation code with ROS 2 and have it auto-fly in real life.
  • In the test code and setup provided below, I take the provided offboard_control.cpp example (converted to python), and throw in a timer so that after 20 seconds, a VTOL transition should occur that allows for the sample outputs to servo and motor to happen (and theoretically get some crazy plane crash :slight_smile: ).

The problem:

  • At 200 seconds, the plane just hovers back down and lands. No actuator work was done.
  • The “mode” that the plane turns into (as reported by QGroundControl) becomes a weird number, which leads me to believe I am not properly transitioning to fixed-wing mode.
  • None of the servos or motors seem to behave any differently.
    Here is an image of the actuator setup for the SITL in the QGroundControl:

Here is the public repo:

https://github.com/FredJones4/vtol_ctrl_ros2

This repo here should be a public repository and easily viewable. The intent with sharing the repo is:

  1. Allow you to see the complete configuration
  2. Prevent the bombardment of code on the comments section (for the sake of future readers after I push more to this code, I will still add the notes down below).
  3. Allow access to the terminal output of xrce_dds agent, the ros node, and the gazebo simulation.
    image

I don’t have anything beyond some basic example code, so I feel fine sharing this. Please let me know if I am breaking community guidelines in doing so; if I am, I will remove it.

Gazebo simulation log: vtol_ctrl_ros2/GZ_notes.md at main · FredJones4/vtol_ctrl_ros2 · GitHub

Ros 2 Node (called ros2_px4_interface, just like the package): vtol_ctrl_ros2/ros2NodeNotes.md at main · FredJones4/vtol_ctrl_ros2 · GitHub

DDS client log: vtol_ctrl_ros2/someDDSClientNotes.md at main · FredJones4/vtol_ctrl_ros2 · GitHub
Note about the DDS log: It was running for a long, long time, so I just grabbed a small sample. Putting it side by side with the ros2 notes and comparing numbers will help.

Here is the simulation before starting the node:

GZ Notes:

make px4_sitl gz_standard_vtol
[0/1] cd /home/cmhales/PX4-Autopilot/build/px4_sitl_default/src/modules/simulation/gz_bridg...v PX4_SIM_MODEL=gz_standard_vtol /home/cmhales/PX4-Autopilot/build/px4_sitl_default/bin/px4

______  __   __    ___ 
| ___ \ \ \ / /   /   |
| |_/ /  \ V /   / /| |
|  __/   /   \  / /_| |
| |     / /^\ \ \___  |
\_|     \/   \/     |_/

px4 starting.

INFO  [px4] startup script: /bin/sh etc/init.d-posix/rcS 0
INFO  [init] found model autostart file as SYS_AUTOSTART=4004
INFO  [param] selected parameter default file parameters.bson
INFO  [param] importing from 'parameters.bson'
INFO  [parameters] BSON document size 404 bytes, decoded 404 bytes (INT32:14, FLOAT:6)
INFO  [param] selected parameter backup file parameters_backup.bson
ERROR [param] Parameter FW_L1_PERIOD not found.
INFO  [dataman] data manager file './dataman' size is 7866640 bytes
INFO  [init] starting gazebo with world: /home/cmhales/PX4-Autopilot/Tools/simulation/gz/worlds/default.sdf
WARN  [init] PX4_GZ_MODEL_NAME or PX4_GZ_MODEL not set using PX4_SIM_MODEL.
INFO  [gz_bridge] world: default, model name: standard_vtol_0, simulation model: standard_vtol
Warning: Ignoring XDG_SESSION_TYPE=wayland on Gnome. Use QT_QPA_PLATFORM=wayland to run on Wayland anyway.
INFO  [lockstep_scheduler] setting initial absolute time to 8000 us
INFO  [commander] LED: open /dev/led0 failed (22)
INFO  [tone_alarm] home set
INFO  [mavlink] mode: Normal, data rate: 4000000 B/s on udp port 18570 remote port 14550
INFO  [mavlink] partner IP: 127.0.0.1
INFO  [tone_alarm] notify negative
INFO  [mavlink] mode: Onboard, data rate: 4000000 B/s on udp port 14580 remote port 14540
INFO  [mavlink] mode: Onboard, data rate: 4000 B/s on udp port 14280 remote port 14030
INFO  [mavlink] mode: Gimbal, data rate: 400000 B/s on udp port 13030 remote port 13280
INFO  [logger] logger started (mode=all)
INFO  [logger] Start file log (type: full)
INFO  [logger] [logger] ./log/2024-08-13/22_58_42.ulg	
INFO  [logger] Opened full log file: ./log/2024-08-13/22_58_42.ulg
INFO  [mavlink] MAVLink only on localhost (set param MAV_{i}_BROADCAST = 1 to enable network)
INFO  [mavlink] MAVLink only on localhost (set param MAV_{i}_BROADCAST = 1 to enable network)
INFO  [px4] Startup script returned successfully
pxh> INFO  [uxrce_dds_client] synchronized with time offset 1723589921582647us
INFO  [uxrce_dds_client] successfully created rt/fmu/out/failsafe_flags data writer, topic id: 80
INFO  [uxrce_dds_client] successfully created rt/fmu/out/position_setpoint_triplet data writer, topic id: 149
INFO  [uxrce_dds_client] successfully created rt/fmu/out/sensor_combined data writer, topic id: 168
INFO  [uxrce_dds_client] successfully created rt/fmu/out/timesync_status data writer, topic id: 191
INFO  [uxrce_dds_client] successfully created rt/fmu/out/vehicle_control_mode data writer, topic id: 212
INFO  [uxrce_dds_client] successfully created rt/fmu/out/vehicle_gps_position data writer, topic id: 215
INFO  [uxrce_dds_client] successfully created rt/fmu/out/vehicle_local_position data writer, topic id: 219
INFO  [uxrce_dds_client] successfully created rt/fmu/out/vehicle_odometry data writer, topic id: 224
INFO  [uxrce_dds_client] successfully created rt/fmu/out/vehicle_status data writer, topic id: 229
INFO  [uxrce_dds_client] successfully created rt/fmu/out/vehicle_attitude data writer, topic id: 206
INFO  [uxrce_dds_client] successfully created rt/fmu/out/vehicle_global_position data writer, topic id: 213
INFO  [commander] Ready for takeoff!
WARN  [health_and_arming_checks] Preflight Fail: Yaw estimate error
INFO  [commander] Armed by internal command	
INFO  [tone_alarm] arming warning
INFO  [commander] Takeoff detected	
INFO  [commander] Landing detected	
INFO  [commander] Disarmed by landing	
INFO  [tone_alarm] notify neutral
INFO  [logger] closed logfile, bytes written: 108044716
ERROR [timesync] Time jump detected. Resetting time synchroniser.

ROS log:

ros2 run ros2_px4_interface ros2_px4_interface 
counter:  0
[INFO] [1723590336.302824611] [offboard_control]: Published OffboardControlMode message
[INFO] [1723590336.307544902] [offboard_control]: Published TrajectorySetpoint message
counter:  1
[INFO] [1723590336.309821622] [offboard_control]: Published OffboardControlMode message
[INFO] [1723590336.310468801] [offboard_control]: Published TrajectorySetpoint message
counter:  2
[INFO] [1723590336.372162840] [offboard_control]: Published OffboardControlMode message
[INFO] [1723590336.374561394] [offboard_control]: Published TrajectorySetpoint message
counter:  3
[INFO] [1723590336.472817584] [offboard_control]: Published OffboardControlMode message
[INFO] [1723590336.474186525] [offboard_control]: Published TrajectorySetpoint message
counter:  4
[INFO] [1723590336.572186229] [offboard_control]: Published OffboardControlMode message
[INFO] [1723590336.573539101] [offboard_control]: Published TrajectorySetpoint message
counter:  5
[INFO] [1723590336.672504461] [offboard_control]: Published OffboardControlMode message
[INFO] [1723590336.673615627] [offboard_control]: Published TrajectorySetpoint message
counter:  6
[INFO] [1723590336.772026352] [offboard_control]: Published OffboardControlMode message
[INFO] [1723590336.773755887] [offboard_control]: Published TrajectorySetpoint message
counter:  7
[INFO] [1723590336.871478192] [offboard_control]: Published OffboardControlMode message
[INFO] [1723590336.872946451] [offboard_control]: Published TrajectorySetpoint message
counter:  8
[INFO] [1723590336.972183433] [offboard_control]: Published OffboardControlMode message
[INFO] [1723590336.973700089] [offboard_control]: Published TrajectorySetpoint message
counter:  9
[INFO] [1723590337.074135961] [offboard_control]: Published OffboardControlMode message
[INFO] [1723590337.077226372] [offboard_control]: Published TrajectorySetpoint message
counter:  10
[INFO] [1723590337.174743971] [offboard_control]: Published VehicleCommand message with command 176
[INFO] [1723590337.177140665] [offboard_control]: Published VehicleCommand message with command 400
[INFO] [1723590337.178245610] [offboard_control]: Arm command sent
[INFO] [1723590337.178918569] [offboard_control]: Published OffboardControlMode message
[INFO] [1723590337.179361609] [offboard_control]: Published TrajectorySetpoint message
counter:  11
[INFO] [1723590337.602357388] [offboard_control]: Published OffboardControlMode message
[INFO] [1723590337.603934319] [offboard_control]: Published TrajectorySetpoint message
counter:  12
[INFO] [1723590337.607501544] [offboard_control]: Published OffboardControlMode message
[INFO] [1723590337.610578616] [offboard_control]: Published TrajectorySetpoint message
counter:  13
[INFO] [1723590337.672282122] [offboard_control]: Published OffboardControlMode message
[INFO] [1723590337.674714359] [offboard_control]: Published TrajectorySetpoint message
counter:  14
[INFO] [1723590337.771540585] [offboard_control]: Published OffboardControlMode message
[INFO] [1723590337.772016404] [offboard_control]: Published TrajectorySetpoint message
counter:  15
[INFO] [1723590337.874468041] [offboard_control]: Published OffboardControlMode message
[INFO] [1723590337.879214617] [offboard_control]: Published TrajectorySetpoint message
counter:  16
...
[INFO] [1723590356.173869009] [offboard_control]: Published OffboardControlMode message
[INFO] [1723590356.176792804] [offboard_control]: Published TrajectorySetpoint message
counter:  199
[INFO] [1723590356.274240300] [offboard_control]: Published OffboardControlMode message
[INFO] [1723590356.278435388] [offboard_control]: Published TrajectorySetpoint message
counter:  200
[INFO] [1723590356.373755878] [offboard_control]: Preparing to change mode from VTOL to fixed-wing
counter:  201
[INFO] [1723590356.474259158] [offboard_control]: Published OffboardControlMode message
counter:  202
[INFO] [1723590356.572148112] [offboard_control]: Published OffboardControlMode message
counter:  203
[INFO] [1723590356.672642835] [offboard_control]: Published OffboardControlMode message
counter:  204
...
[INFO] [1723590361.174287142] [offboard_control]: Published OffboardControlMode message
counter:  249
[INFO] [1723590361.272467288] [offboard_control]: Published OffboardControlMode message
counter:  250
[INFO] [1723590361.373395313] [offboard_control]: Published OffboardControlMode message
[INFO] [1723590361.376263131] [offboard_control]: Changing mode to fixed-wing
[INFO] [1723590361.377772126] [offboard_control]: Published VehicleCommand message to change mode to fixed-wing

DDS log: (not included for sheer length)

The python code that creates the node:

import rclpy
from rclpy.node import Node
from rclpy.qos import QoSProfile, ReliabilityPolicy, DurabilityPolicy
from px4_msgs.msg import OffboardControlMode, TrajectorySetpoint, VehicleCommand
from px4_msgs.msg import (ActuatorMotors, ActuatorServos, VehicleCommand, OffboardControlMode)
from std_msgs.msg import Header
import time
import math



HZ_RATE = 100.0

class ROS2PX4Interface(Node):
    def __init__(self):
        super().__init__('offboard_control')

        self.offboard_control_mode_publisher = self.create_publisher(OffboardControlMode, '/fmu/in/offboard_control_mode', 10)
        self.trajectory_setpoint_publisher = self.create_publisher(TrajectorySetpoint, '/fmu/in/trajectory_setpoint', 10)
        self.vehicle_command_publisher = self.create_publisher(VehicleCommand, '/fmu/in/vehicle_command', 10)

        self.offboard_setpoint_counter = 0

        qos_profile = QoSProfile(
            reliability=ReliabilityPolicy.BEST_EFFORT,
            durability=DurabilityPolicy.VOLATILE,
            depth=10
        )

        # Create publishers with the specified QoS profiles
        self.actuator_motors_pub = self.create_publisher(
            ActuatorMotors,
            '/fmu/in/actuator_motors',
            qos_profile
        )

        self.vehicle_command_pub = self.create_publisher(
            VehicleCommand,
            '/fmu/in/vehicle_command',
            qos_profile
        )

        self.actuator_servos_pub = self.create_publisher(
            ActuatorServos,
            '/fmu/in/actuator_servos',
            qos_profile
        )

        self.offboard_control_mode_pub = self.create_publisher(
            OffboardControlMode,
            '/fmu/in/offboard_control_mode',
            qos_profile
        )

        self.timer = self.create_timer(0.1, self.timer_callback)  # 100 ms timer, or 10 Hz clock

    def change_mode(self):
        current_time = self.get_clock().now()
        elapsed_time = (current_time - self.timer_start).nanoseconds/ 1e9

        if elapsed_time >= self.timer_period:
            self.get_logger().info("Changing mode to fixed-wing")

            # Create and publish VehicleCommand message to change mode
            cmd = VehicleCommand()
            cmd.command = 84  # Command to change flight mode (check specific value for your system)
            cmd.param1 = 1.0     # Parameter to indicate the desired mode (check specific parameter for your system)
            cmd.target_system = 1
            cmd.source_system = 1
            cmd.target_component = 0
            cmd.source_component = 0
            self.vehicle_command_pub.publish(cmd)
            self.get_logger().info("Published VehicleCommand message to change mode to fixed-wing")

            # Stop the timer after mode change
            self.timer2.destroy()

    def timer_callback(self):
        print("counter: ", self.offboard_setpoint_counter)
        if self.offboard_setpoint_counter == 10:
            # Change to Offboard mode after 10 setpoints
            self.publish_vehicle_command(VehicleCommand.VEHICLE_CMD_DO_SET_MODE, 1.0, 6.0)

            # Arm the vehicle
            self.arm()
        if self.offboard_setpoint_counter < 200: # 20 seconds
            # Publish offboard control mode and trajectory setpoint
            self.publish_offboard_control_mode()
            self.publish_trajectory_setpoint()
            if self.offboard_setpoint_counter > 10:
                self.offboard_setpoint_counter += 1
        elif self.offboard_setpoint_counter == 200:
            self.timer_start = self.get_clock().now()
            # Initialize the mode change (for example, change to fixed-wing after 5 seconds)
            self.get_logger().info("Preparing to change mode from VTOL to fixed-wing")
            self.timer_period = 5.0  # Change mode after 5 seconds
            self.timer2 = self.create_timer(1.0, self.change_mode)
            self.offboard_setpoint_counter += 1

            pass
        elif self.offboard_setpoint_counter < 400: #TEMP
            self.publish_offboard_control_mode()
            self.offboard_setpoint_counter += 1
        else:
            self.publish_offboard_control_mode()
            self.publish_actuator_items()

        # Increment the counter and stop after reaching 11
        if self.offboard_setpoint_counter < 11:
            self.offboard_setpoint_counter += 1

    def arm(self):
        """Send a command to arm the vehicle."""
        self.publish_vehicle_command(VehicleCommand.VEHICLE_CMD_COMPONENT_ARM_DISARM, 1.0)
        self.get_logger().info('Arm command sent')

    def disarm(self):
        """Send a command to disarm the vehicle."""
        self.publish_vehicle_command(VehicleCommand.VEHICLE_CMD_COMPONENT_ARM_DISARM, 0.0)
        self.get_logger().info('Disarm command sent')

    def publish_offboard_control_mode(self):
        """Publish the offboard control mode."""
        msg = OffboardControlMode()
        msg.position = True
        msg.velocity = False
        msg.acceleration = False
        msg.attitude = False
        msg.body_rate = False
        msg.timestamp = int(time.time() * 1e6)  # microseconds
        self.offboard_control_mode_publisher.publish(msg)
        self.get_logger().info('Published OffboardControlMode message')

    def publish_trajectory_setpoint(self):
        """Publish a trajectory setpoint."""
        msg = TrajectorySetpoint()
        msg.position = [0.0, 0.0, -20.0]
        msg.yaw = -3.14  # [-PI:PI]
        msg.timestamp = int(time.time() * 1e6)  # microseconds
        self.trajectory_setpoint_publisher.publish(msg)
        self.get_logger().info('Published TrajectorySetpoint message')

    def publish_vehicle_command(self, command, param1=0.0, param2=0.0):
        """Publish vehicle command."""
        msg = VehicleCommand()
        msg.param1 = param1
        msg.param2 = param2
        msg.command = command
        msg.target_system = 1
        msg.target_component = 1
        msg.source_system = 1
        msg.source_component = 1
        msg.from_external = True
        msg.timestamp = int(time.time() * 1e6)  # microseconds
        self.vehicle_command_publisher.publish(msg)
        self.get_logger().info(f'Published VehicleCommand message with command {command}')

    def publish_actuator_motors(self, motors_msg):
        self.actuator_motors_pub.publish(motors_msg)
        self.get_logger().info('Published ActuatorMotors message')


    def publish_actuator_servos(self, servos_msg):
        self.actuator_servos_pub.publish(servos_msg)
        self.get_logger().info('Published ActuatorServos message')


    def publish_actuator_items(self):
        motors_msg = ActuatorMotors()  # Populate with relevant data
        motors_msg.control = [0.0,     0.0,       0.0,        0.0,    1.0,     0.0, 
                            0.0, 0.0, 0.0, 0.0, 0.0, 0.0
        ]
        # # command_msg = VehicleCommand()  # Populate with relevant data
        servos_msg = ActuatorServos()   # Populate with relevant data
        servos_msg.control = [1.0,      1.0,        1.0,       0.0,     0.0,     0.0, 
                            0.0, 0.0]
        # control_mode_msg = OffboardControlMode()  # Populate with relevant data

        # self.publish_actuator_motors(motors_msg)
        # node.publish_vehicle_command(command_msg)
        self.publish_actuator_servos(servos_msg)
        # node.publish_offboard_control_mode(control_mode_msg)

def main(args=None):
    rclpy.init(args=args)
    node = ROS2PX4Interface()
    rclpy.spin(node)
    node.destroy_node()
    rclpy.shutdown()

if __name__ == '__main__':
    main()


The STIL was made by the following CLI commands:

cd PX4-Autopilot
make px4_sitl gz_standard_vtol