Ship Landing Ignition Simulation

A topic to discuss developing moving platform / ship landing simulation using Ignition

1 Like

thanks @rhys ! I’m looking forward to this

1 Like

Ignition Version

There are two branches of the ardupilot_gazebo Ignition plugin:

They branches differ in the version of Ignition linked and a minor change to one of the interfaces handling joint state. The Garden branch is available because this is the branch that contains support for Metal rendering on macOS, however I expect this to be of interest to a minority of users. The next LTS version is Fortress and it would be good to move to using this, however @tridge found some odd behaviour when running this on Ubuntu Focal (20.04):

  • The simulation could take a long time to launch, which looks like a race condition or other block waiting on some event.
  • When running the X8 simulation on Fortress, the EKF status was unhealthy and it appeared as though lock-step was broken. The vehicle lost control on takeoff and crashed.

In both cases it is hard to understand how the minor changes between branches could introduce the behaviour. The X8 appears to behave when running Ignition Garden (i.e. HEAD) on macOS. This is the EKF report for a GUIDED takeoff:

Transition to FBWA and then QLAND also works.

For the moment I recommend continuing to use Ignition Edifice and the corresponding ardupilot_gazebo branch. This also has the advantage that Edifice will run on an Ubuntu VM as it does not require OpenGL >= 4.3 (which Fortress does). I do not have a physical Linux box at present which makes it difficult to replicate this type of issue.

Initial test world using a truck as a landing platform

Summary of what the ship landing Lua script expects (please correct if I’ve misunderstood and expand on anything I’ve missed).

  1. Flying vehicle (FV), a quad plane, is controlled in Ignition via SITL-JSON.
  2. Surface vehicle (SV), a ship or rover, also controlled in Ignition via SITL-JSON

The script controlling the FV requires state information about the SV

  • position, orientation (confirm frame)
  • linear velocity, angular velocity (confirm frame)
  • anything else?
  1. For an articulated landing platform (SV::LP) such as a trailer there needs to be an additional provider of the state information. Some options:

    3.1. Treat the trailer as a third vehicle that does not respond to any PWM commands and solely reports its FDM state via SITL-JSON. This may be possible by treating the trailer as a nested SDF model that contains its own ardupilot plugin element configured with another (third) port. This would be the cleanest solution as it is entirely self-contained using existing code and only requires suitable configuration.

    3.2 Extract the state information about the trailer pose from Ignition and publish / broadcast this in a format that can be consumed by ArduPilot. An approach would be to subscribe to Ignition’s messaging layer (ign-transport) and forward the appropriate topics using pymavlink or similar. There is an experimental set of Python bindings for the Ignition messaging layer here: python-ignition that could be used to build a Python module running the bridge. There is a bit more work involved than for (3.1):
    (i) extract the pose of the trailer from the SV model state message
    (ii) transform the pose from the Gazebo world frame to the NED earth frame (I assume the frame transformation is the same as for the FDM data passed back via SITL-JSON)
    (iii) populate the appropriate pymavlink objects and publish / broadcast
    (iv) package the script and dependencies so that they are easy to use in examples: the bindings library is not packaged for distribution, and is not be standalone (as it links to a version of the ignition libraries) - so some thought on the best way to do this is needed.

Next steps:

  • Provide an example with a landing platform fixed to the SV and the FV initially at rest on the SV.
  • Set up launch scripts etc.
  • The SV can run in AUTO with WP for a simple circuit type mission. This can be modified later for move complex manoeuvring.

Findings:

The ‘easy’ option seems to work. Nesting the trailer as a SDF model and providing it with its own IMU and ardupilot plugin with a SITL port of 9012 instead of 9002 allows you to connect another SITL session to it. I’ve used the Rover vehicle type as a proxy for the trailer beacon and designated it instance 2. The X8 can be situated on the trailer with its own port 9022 and run up as instance 3. I’m not familiar enough with the MAVProxy multi-vehicle settings to get everything in one map (tried setting vehicle according to Multiple Vehicles with MAVProxy — MAVProxy documentation but clearly have missed a trick).

It’s not that friendly to configure at the moment because the model xml needs to be customised to override the default port for the plugin - I think the way this is done for some projects is to create template slots in the model xml and use jinja or equivalent to generate the xml instances based on parameters: not needed for a proof of concept though.

The maps in the figure are out of sync because Python freezes until you resume focus - but they are all tracking correctly.

1 Like

I don’t expect to have a lot of dev time on ignition so I will add some CI, that would help to catch regression and test different models !

1 Like

@khancyr that would be a great help thanks.

Some progress on the multi-vehicle behaviour. There is a bug in the ardupilot_gazebo plugin that can cause the incorrect pose to be assigned to a vehicle when there are multiple vehicles with IMUs present. It’s a one line fix so I’ll prepare a PR for that.

The SITL follow example scripts were very helpful and answered my questions about setting up multiple vehicle configurations. I have not managed to get follow mode working, but 3 iris quads running different missions is working fine. macOS now defaults to zsh and there are some bash features used in the scripts (expr substr) that don’t have equivalents on macOS but these are easily fixed. I did not manage to get multi-cast working though, and instead reverse engineered a TCP config using the -n3 --auto-sysid example from the wiki. Will post it all in due course.

FOLLOW working: needed MAV> set fwdpos 1.

I think that’s all the pieces needed to set the simulation up now, needs tidying up and a couple of PRs.

Worked example available here: Add truck quadplane landing example [do not merge] by srmainwaring · Pull Request #62 · ArduPilot/SITL_Models · GitHub

If you are more ease with docker I can show you how to launch the swarm from a docker-compose

That would be great - and would solve the issue of where to store the custom models if we installed them to the container. Can we access the mount point from a Gazebo session running natively on the host? (I’m expecting yes if we can add a directory in docker to the ignition gazebo resources path).

Also a really good way to provide some easy to start worked examples using different AP features. It took me a while to hunt down the settings for getting follow to work - there is a lot of information in discourse but sometimes it can take some finding.

Customising Ignition models using ERB templates

To launch multiple ArduPilot controlled vehicles in the same Ignition session, each vehicle must have a unique control port assigned in the ArduPilot plugin. The issue is that in the model.sdf files, each of the models statically define a fixed port (9002), and the question is how to modify these without asking users to make copies of each model and manually edit the SDF.

The Ignition tutorials suggest using embedded ruby (ERB) templates: Tutorial: ERB Template and provide an example of how to inject multiple copies of a model into a world file.

With some modification, we can use this approach to modify the model files as well as it turns out ERB templates can themselves reference additional templates.

An example: we have two model templates and one world template.

model_cylinder.sdf.erb

<%
  # defaults
  if !defined?(model_name) then model_name = 'cylinder' end
  if !defined?(radius) then radius = 1 end
  if !defined?(length) then length = 1 end

  # sdf header and footer
  header=''
  footer=''
  if defined?(sdf)
    header='<?xml version="1.0" ?>
<sdf version="1.7">'
    footer='</sdf>'
  end
%>
<%= header %>
<model name="<%= model_name %>">
  <link name="base_link">
    <visual name="visual">
      <geometry>
        <cylinder>
          <radius><%= radius%></radius>
          <length><%= length%></length>
        </cylinder>
      </geometry>
    </visual>
  </link>
</model>
<%= footer %>

model_box.sdf.erb

<%
  # defaults
  if !defined?(model_name) then model_name = 'box' end
  if !defined?(x) then x = 1 end
  if !defined?(y) then y = 2 end
  if !defined?(z) then z = 3 end

  # sdf header and footer
  header=''
  footer=''
  if defined?(sdf)
    header='<?xml version="1.0" ?>
<sdf version="1.7">'
    footer='</sdf>'
  end
%>
<%= header %>
<model name="<%= model_name %>">
  <link name="base_link">
    <visual name="visual">
      <geometry>
        <box>
          <size><%= x %> <%= y %> <%= z %></size>
        </box>
      </geometry>
    </visual>
  </link>
</model>
<%= footer %>

example_world.sdf.erb

<?xml version="1.0" ?>
<sdf version="1.7">
  <world name="example_world">
    <plugin filename="libignition-gazebo-physics-system.so"
      name="ignition::gazebo::systems::Physics">
    </plugin>
    <plugin filename="libignition-gazebo-user-commands-system.so"
      name="ignition::gazebo::systems::UserCommands">
    </plugin>
    <plugin filename="libignition-gazebo-scene-broadcaster-system.so"
      name="ignition::gazebo::systems::SceneBroadcaster">
    </plugin>

    <scene>
      <ambient>1.0 1.0 1.0</ambient>
      <background>0.8 0.8 0.8</background>
      <sky></sky>
    </scene>

    <light type="directional" name="sun">
      <cast_shadows>true</cast_shadows>
      <pose>0 0 10 0 0 0</pose>
      <diffuse>0.8 0.8 0.8 1</diffuse>
      <specular>0.8 0.8 0.8 1</specular>
      <attenuation>
        <range>1000</range>
        <constant>0.9</constant>
        <linear>0.01</linear>
        <quadratic>0.001</quadratic>
      </attenuation>
      <direction>-0.5 0.1 -0.9</direction>
    </light>
    <%
      def add_box model_name, x, y, z
        ERB.new(File.read('model_box.sdf.erb')).result(binding)
      end

      def add_cylinder model_name, radius, length
        ERB.new(File.read('model_cylinder.sdf.erb')).result(binding)
      end
    %>
    <%= add_box('box', 1, 2, 3) %>
    <%= add_cylinder('cylinder', 1, 2) %>
  </world>
</sdf>

To generate the SDF file for the model:

$ erb -T 1 sdf=true model_box.sdf.erb > model_box.sdf

To generate the SDF file for the world:

$ erb -T 1 example_world.sdf.erb > example_world.sdf

Providing the model.sdf.erb files with defaults and a conditional ensures the definition of the model is in one place. The template can be used to create the default standalone model that is released, and may also be used to embed the model into new worlds that may be dynamically generated at runtime. If the generation of the world is included in the launch script, the generated world can be placed in a subdirectory along side the subdirectories for each SITL vehicle which keeps everything organised and reduces the need to release many custom worlds for different configurations.

This will also be useful for creating worlds with swarms of vehicles…

otherwise the sdf is a xml file so that is easy to modify with python

here is an example of swarm with docker :

it was used with BalenaOS but it should work directly with docker-compose too.

@tridge I think it’s all good to go now.

Add truck quadplane landing example by srmainwaring · Pull Request #62 · ArduPilot/SITL_Models · GitHub should contain everything you need including launch scripts, model port configuration, world generation etc.

There’s one upstream PR to merge before the landing example goes in after a rebase, other than that I think it’s done.

The truck is speed limited to 5 m/s in AUTO as requested, and the default quadplane is the Alti Transition. The friction on the trailer deck may be a little low so the plane will slip if manoeuvring aggressively. The mass may also be a little low, but that should not affect things too much in the first instance.

Setting the follow camera

@tridge - there was an ignition PR to provide a service to adjust the follow camera offsets:

For example:

ign service -s /gui/follow/offset --reqtype ignition.msgs.Vector3d --reptype ignition.msgs.Boolean --timeout 2000 --req "x: 15, y: 5, z: 5"

Updated updated example to offset camera to look back and down to the right. With larger offsets the behaviour is ok.

1 Like

Changing Simulation Speed

The plugin currently does not implement speedups requested from SITL (i.e. the frame_rate in the servo packet sent by the controller is ignored). However the simulation speed can be controlled from the Ignition GUI.

  • Click in the scene and enter ESC to select the World
  • The World component should display in the Component Inspector
  • Click on Physics
  • Set the Real Time Factor to speed up / slow down the simulation (in practice you may get between a 1.5x to 5x speed up depending on what’s being simulated and the network connection to JSON-SITL when using lock-step)
1 Like

Quick truck-landing demo. Thanks to Rhys for all your help!

3 Likes

Demo looks great! Next step is to get the ocean model working on Ignition and provide a ship for you to land on.

1 Like

Camera Sensors

Possibly for a different topic… I’ve been working on extracting camera sensor data published by Ignition for forwarding onto other clients. Here’s a sample of what is possible.

The first image is a Gazebo simulation of a copter outfitted with a number of different simulated camera sensors - regular camera, depth camera, rgbd camera, and thermal camera.

The second image shows a version of the ign_rviz project modified to run on macOS (any tool capable of display ROS image types would do - choices are currently a bit limited on macOS). It is subscribing to ROS2 topics with message type sensor_msgs/Images. In the background there is a ROS2 python node running a bridge between Ignition and ROS2 message types. The Ignition messages are protocol buffers with Python bindings and are accessed via pybind11 bindings to a couple of the Ignition libraries (ign_msgs and ign_transport). The same technique should permit forwarding to ArduPilot via pymavlink given appropriate image converters (there would be no need to depend on ROS in this case).

Next steps are to incorporate the work done for visual avoidance in AirSim and determine the best way to structure the parts to make it easier to distribute and use

1 Like

So, you’ve written your own ignition-ros bridge and didn’t use https://github.com/ignitionrobotics/ros_ign/tree/melodic/ros_ign_image?

Hi @soldierofhell, I’m aware of the ign_ros bridge work (I used the image bridge code to check the pixel format mappings). Don’t worry - I’m not trying to build an alternative ros bridge! Rather, I’m looking to provide some tools that make it easier to get at the information available in Ignition. Providing an image mapping to ROS was a fairly quick piece of Python programming which let me check that I was getting the data out of Ignition correctly using standard tools. The end objective in this case is to get the data back into ArduPilot, skipping ROS entirely.

Here’s another example: Ignition publishes a model state message that contains all the joint and link information for a model. There is currently no mapping using the ign_ros bridge of this message into ROS equivalent messages for the joint states (position and velocities). I wanted to analyse the joint velocities and PID states of the joint controllers used for the propellers on a model but had no easy way of doing that. You can’t plot the data in Ignition because there is no way to filter on the different elements of the message. On the other hand once you have the messages in Python you can filter and split the entire model message into a number of simpler message streams that can be picked up by various standard plotting tools. The python bindings for the transport layer are here https://github.com/srmainwaring/python-ignition, I haven’t published the repo containing the mapping code, but I’ve attached the scripts I’m using so you can see that they’re quite easy to work with. You could do it all in C++, but that feels like hard work to me.

model_bridge.py.zip (2.2 KB)
sensor_bridge.py.zip (2.3 KB)

If there was wider interest in using this I’d be happy look into developing it into something more robust for wider use.

1 Like