Contents¶
Introduction¶
In the previous tutorial we set up a controller that pubs & subs. Now we want to modify the simulator to do the same. Then the simulator will be able to publish the state as well as subscribe to the control input.
Just like for the controller we will make the simulator into a class with the appropriate Callback methods. However, unlike the controller we will not be able to do everything within the Callback. The simulator should act as our ground-truth, and should be persistent. A Callback function is only called when a new message is published to the corresponding topic, and it is therefore not appropriate to advance the state from within the Callback.
Instead we will add the control input as a class member variable. This variable will then be overwritten with the latest input from the controller whenever the Callback method is executed i.e whenever a new control input is published by the controller. All the while the simulator will continue to advance the state, using the applyModel method of our CRS-based dynamics model. This is exactly the same structure used in CRS' ros4crs/ros_simulator/, which we will refer to to guide our architectural choices.
We will in fact use several different files and directories as a reference for improving our simulator, ensuring that we have good compatibility with the rest of CRS. You can use any of these as a template to create the simulator or just write one from scratch! The only important thing is the structure.
src/ros4crs/ros_backtracker/as a potential templatesrc/ros4crs/ros_simulator/could also be used as template, requires carefully removing unnecessary files and linking correct ones.- Our previous
simulatorpackage, i.e.src/ros4crs/parafoil_simulator/
Step-by-Step¶
Setup Folder Structure¶
Just as we did when we created the barebones ROS simulator, we can duplicate & rename ros_backtracker as a starting point as it already containts the entire architecture of a ROS package.
ros_backtracker
├── app
│ ├── crash_detector_node.cpp
│ ├── input_filter_node.cpp
│ └── linear_backtracker_node.cpp
├── CMakeLists.txt
├── config
│ └── linear_backtrack_config.yaml
├── include
│ └── ros_backtracker
│ ├── crash_detector.h
│ ├── input_filter.h
│ └── linear_backtracker.h
├── launch
│ └── default.launch
├── package.xml
└── src
├── crash_detector.cpp
├── input_filter.cpp
└── linear_backtracker.cpp
If you decide to duplicate the ros_backtracker package as a foundation for the simulator you can go ahead and delete the crash_detector and input_filter source, header, and application files. In the rest of this tutorial we will assume you have copied the folder architecture and will refer to "renaming" files and "removing" unnecessary functions.
Header Files¶
We want to end up with the following methods and member variables as part of our simulator class
advanceStatemethod to propagate state forwardinputCallbackto update alast_inputmember variable
To update include/ros_backtracker/linear_backtracker.h or build the file ourselves we can follow these steps
- Name file & folder appropriately e.g.
include/parafoil_simulator/parafoil_3dof_simulator.h - Update
ifndef&define - Update
includes - Update namespace
- Remove any unnecessary bits if working from a template e.g. from
ros_backtracker:- Remove
errorstate - Remove
collision_sub - Remove
errorCallback - Remove error parameters
- Remove
- Rename class e.g.
Parafoil3dofSimulator - Add model member variable
model - Remove
nh_private - Add member variables for
current_state_andlast_input_ - Setup sub/pub architecture
state_pubinput_sub
- Setup
inputCallback(can replacestateCallbackof template) including@brief,@param, arguments - Update constructor including
@brief,@param, arguments - Add public method
advanceState(double timestep) - Add public state publish method
publishStates()
#ifndef PARAFOIL_SIMULATOR_PARAFOIL_3DOF_SIMULATOR_H
#define PARAFOIL_SIMULATOR_PARAFOIL_3DOF_SIMULATOR_H
#include <crs_msgs/parafoil_3dof_input.h>
#include <crs_msgs/parafoil_3dof_state.h>
#include <numeric>
#include <ros/ros.h>
#include <parafoil_3dof_model/parafoil_3dof_model_discrete.h>
#include <parafoil_3dof_model/parafoil_3dof_params.h>
#include <parafoil_3dof_model/parafoil_3dof_state.h>
#include <parafoil_3dof_model/parafoil_3dof_input.h>
namespace parafoil_simulator
{
class Parafoil3dofSimulator
{
private:
// Model
std::unique_ptr<crs_models::parafoil_3dof_model::DiscreteParafoil3dofModel> model_;
// Node handles
ros::NodeHandle nh_;
// Publisher
ros::Publisher state_pub_;
// Subscriber
ros::Subscriber input_sub_;
// Model state and input
crs_models::parafoil_3dof_model::parafoil_3dof_state current_state_ = {0.0, 0.0, -100.0, 0.785398, 0.0};
crs_models::parafoil_3dof_model::parafoil_3dof_input last_input_ = {0.0};
/**
* @brief Input callback.
*
* @param control_input
*/
void inputCallback(crs_msgs::parafoil_3dof_input::ConstPtr control_input);
public:
/**
* @brief Construct a new 3dof parafoil simulator.
*
* @param nh
*/
Parafoil3dofSimulator(ros::NodeHandle nh);
void advanceState(double timestep);
void publishStates();
};
} // namespace parafoil_simulator
#endif // PARAFOIL_SIMULATOR_PARAFOIL_3DOF_SIMULATOR_H
Source Files¶
Now we just need to fill in the source file to match the header file either by writing it ourselves or updating our template e.g. src/ros_backtracker.cpp
- Ensure filename matches header e.g.
parafoil_3dof_simulator.cpp - Update
includes - Update namespace
- Remove unnecessary bits from template
- Remove
errorCallback - Remove
errorstateif/else
- Remove
- Update classname to match header e.g.
Parafoil3dofSimulator - Remove
nh_private - Update pub/sub architecture (don't forget msg types)
- Update Constructor
- Add
model_¶msinstantiation - Add
state_pub_andinput_sub_instantiation
- Add
- Add
advanceStatemethod which callsapplyModelmethod to propagate state - Add
inputCallbackto updatelast_input_with latestcontrol_inputmessage - Add
publishStatesmethod
#include "parafoil_simulator/parafoil_3dof_simulator.h"
namespace parafoil_simulator
{
Parafoil3dofSimulator::Parafoil3dofSimulator(ros::NodeHandle nh)
{
crs_models::parafoil_3dof_model::parafoil_3dof_params params = {9.89, 4.72, 0.341, 0.43};
model_ = std::make_unique<crs_models::parafoil_3dof_model::DiscreteParafoil3dofModel>(params);
state_pub_ = nh.advertise<crs_msgs::parafoil_3dof_state>("state", 1);
input_sub_ = nh.subscribe("control_input", 1, &Parafoil3dofSimulator::inputCallback, this);
}
void Parafoil3dofSimulator::advanceState(double timestep)
{
current_state_ = model_->applyModel(current_state_, last_input_, timestep);
}
void Parafoil3dofSimulator::inputCallback(crs_msgs::parafoil_3dof_input::ConstPtr control_input)
{
last_input_.delta_a = control_input->delta_a;
}
void Parafoil3dofSimulator::publishStates()
{
crs_msgs::parafoil_3dof_state msg;
msg.x = current_state_.pos_x;
msg.y = current_state_.pos_y;
msg.z = current_state_.pos_z;
msg.psi = current_state_.psi;
msg.psi_rate = current_state_.psi_rate;
state_pub_.publish(msg);
}
} // namespace parafoil_simulator
Application Files¶
Update app/simulation_node.cpp from previous parafoil_simulator
- Create
simulatorpointer to our new simulator class e.g.parafoil_simulator::Parafoil3dofSimulator - Call class methods defined above to advance the state and then publish the current state. Note that we do not need to call the
inputCallbackas this will be executed automatically whenever a control input is published by the controller
#include <ros/ros.h>
#include <ros_crs_utils/parameter_io.h>
#include "parafoil_simulator/parafoil_3dof_simulator.h"
int main(int argc, char** argv)
{
ros::init(argc, argv, "simulation_node");
ros::NodeHandle nh = ros::NodeHandle();
ros::Rate loop_rate(10);
auto* simulator = new parafoil_simulator::Parafoil3dofSimulator(nh);
while (ros::ok())
{
simulator->advanceState(0.1);
simulator->publishStates();
ros::spinOnce();
loop_rate.sleep();
}
return 0;
}
NOTE in future should combine into more elegant advanceSimulator shown in ros_simulator
Launch Files¶
Finally make sure to update the simulator/ launch file if you haven't already such that it spawns both a simulator and controller node
<launch>
<group ns = "test_ns">
<node pkg="parafoil_simulator" name="parafoil_simulator" type="simulation_node" output="screen">
</node>
<node pkg="parafoil_controllers" name="parafoil_controller" type="parafoil_3dof_controller_node" output="screen">
</node>
</group>
</launch>
Admin Files¶
Update CmakeLists.txt
- Update project name
- If used
ros_backtrackeras template make sure to addfind_package(casadi) - Change library to add correct file from
src - Add
simulation_nodeas executable - Link
simulation_nodewith casadi
cmake_minimum_required(VERSION 2.8.3)
project(parafoil_simulator)
set(CMAKE_CXX_STANDARD 17)
find_package(catkin_simple REQUIRED)
find_package(casadi)
catkin_simple()
catkin_package()
#############
# Libraries #
#############
include_directories(
include
${catkin_INCLUDE_DIRS}
)
#############
# Libraries #
#############
cs_add_library(${PROJECT_NAME}
src/parafoil_3dof_simulator.cpp
)
###############
# Executables #
###############
cs_add_executable(simulation_node
app/simulation_node.cpp)
target_link_libraries(simulation_node ${PROJECT_NAME} casadi)
##########
# Export #
##########
cs_install()
cs_export(INCLUDE_DIRS ${CATKIN_DEVEL_PREFIX}/include)
Update package.xml
- Update package name
- Update maintainer name & email if necessary
<?xml version="1.0"?>
<package format="2">
<name>parafoil_simulator</name>
<version>0.0.1</version>
<description>TODO </description>
<author email="norrisg@ethz.ch">Griffin Norris</author>
<maintainer email="norrisg@ethz.ch">Griffin Norris</maintainer>
<license>BSD 2-Clause </license>
<buildtool_depend>catkin</buildtool_depend>
<buildtool_depend>catkin_simple</buildtool_depend>
<depend>roscpp</depend>
<depend>dynamic_models</depend>
<depend>crs_msgs</depend>
<depend>ros_crs_utils</depend>
<depend>commons</depend>
<!-- Optional Dependencies -->
<depend>parafoil_3dof_model</depend>
<export></export>
</package>
Review¶
Should have following structure:
parafoil_simulator
├── app
│ └── simulation_node.cpp
├── CMakeLists.txt
├── include
│ └── parafoil_simulator
│ └── parafoil_3dof_simulator.h
├── launch
│ └── parafoil_3dof_launch.launch
├── package.xml
└── src
└── parafoil_3dof_simulator.cpp
Up Next¶
Now you have a working ROS architecture which you can launch with roslaunch! The built in simulator in CRS is really just a scaled up version of these very same concepts which allows several different models,and several different sensors. Speaking of sensors shouldn't we add those to our architecture? Yep, we should and that's what we'll do in the next tutorial!