mc_rtc::Configuration
general purpose configurationThe goal of this tutorial is to show you how to create a new controller for mc_rtc. We will use the JVRC1 robot in our example and we will simply try to shake the head left and right
Note: in mc_rtc, every controller derives from the MCController
class. To write a simple controller you can also derive from it and write the required functionnality. This is what we will do in these tutorials. However, for more complex controllers we advise you to use the FSM facilities
We will use mc_rtc_new_controller
provided by mc_rtc to setup a new controller project:
Note: this tool requires Git for Python which is available as python-git in Debian-like systems and GitPython in pip
In this tutorial we will create a tutorial controller named MyFirstController
, so we can use the following command:
Going into the newly created my_first_controller
folder we can see some files have been automatically generated:
This is done using CMake and your usual tool to run CMake, build the code and install it. Typically, on Linux/MacOS:
Note: sudo
is only required if mc_rtc is installed in a privileged directory
Modify your mc_rtc configuration file so that we use the JVRC1 robot and our newly installed controller:
Then run your controller as explained in the previous section. Congratulations, you just built and and ran your first controller!
We will start by creating a new Python package:
Note: this assumes you are using a posix shell
Then we will create a file my_first_controller.py
in the my_first_controller
folder:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import mc_control
import mc_rbdyn
import mc_rtc
class MyFirstController(mc_control.MCPythonController):
def __init__(self, rm, dt):
self.qpsolver.addConstraintSet(self.kinematicsConstraint)
self.qpsolver.addConstraintSet(self.contactConstraint)
self.qpsolver.addTask(self.postureTask)
def run_callback(self):
return True
def reset_callback(self, data):
pass
@staticmethod
def create(robot, dt):
env = mc_rbdyn.get_robot_module("env", mc_rtc.MC_ENV_DESCRIPTION_PATH, "ground")
return MyFirstController([robot,env], dt)
We also need to edit __init__.py
so that the controller is available in the module:
Modify your mc_rtc configuration file so that we use the JVRC1 robot and our new controller:
Note: if you have enforced Python 2 or Python 3 or if you have installed both then you should explicitly choose Python2
or Python3
.
For this to work, we need to make sure the my_first_controller
folder is on the Python path. In fact, given this configuration, mc_rtc will create your controller this way:
Typically, if you run mc_rtc_ticker
and the my_first_controller
folder is in $HOME/my_python_controllers
, then you should run as follows:
A key difference between the C++ controller and the Python controller is that the C++ controller let you bypass the call to mc_control::MCController::run()
and mc_control::MCController::reset()
functions if you like. Whereas in Python, run_callback(self)
and reset_callback(self, data)
will always be called after its C++ counterpart.
Use the mc-rtc/new-controller template project. This is equivalent to using the mc_rtc_new_controller
tool with extra goodies.
Let's first have a look at the constructor:
They are very similar. We use existing objects in the base class to setup a basic controller:
Now let's look at the reset
function. This function is called when the controller is started (either when the interface starts or when the controllers are switched online).
Here we are simply delegating to the MCController
class (implicitly in the Python case). reset_data
contains the initial robot configuration provided by the interface. The default implementation makes sure that the robot is correctly initialized and that the posture task objective is set to the current robot's posture.
Then we have the run
function. This function is going to be called for every iteration of the controller.
Here we are also delegating the call to the MCController
class (also implicitly in the Python case). The function should return true if everything goes well and false if the control should be interrupted. The default implementation runs the QP solver with the tasks and constraints provided by your program and use the resulting acceleration to update the desired robot's state.
Finally, the last piece of code allows mc_rtc to load your controller:
It's unlikely you would need to modify this but it's vital not to forget it so we are mentioning it here.
We will start by adding two properties to our controller, one will hold the joint index for the head joint we are going to move, the other will tell us in which direction we are going.
To initialize the joint index, we need to ask the robot class. In JVRC1, the head yaw joint is called NECK_Y
, hence the following code in our controller constructors:
Next we will write a function to update the target accordingly if we want the robot to look left or right:
Note: it's also possible to update the full posture target by using postureTask->posture()
to get the current target and postureTask->posture(new_posture)
to change the target but we use a more readable version here.
Finally, we modify the run
function to update the robot's behaviour according to the current task error:
Note: alternatively, we could monitor postureTask->eval().norm()
which gives the total error of the task. However here we are only concerned with the head yaw joint.
Et voilà! You can run this controller and see that the robot is moving its head from left to right. For the next tutorial we will continue using this controller, however, the code samples will not reflect the existing head-moving code.
The full sources for this controller are available here.
You might be tempted to try this controller with different robots but not knowing which joints are available in your robot, the following snippet will print out a list of one dof joint that can be used in the sample code interchangeably with NECK_Y
: