Use acados in real C++ projects

Hi,

I’m using acados to make an nmpc controller and I have made some demos with both python and C++, which work with acados effectively. So I really want to use acados in my project. But here comes a question that how I can migrate acados into the project, a robot control framework, for future use in a real robot? Should I copy some files from acados into my nmpc controller document as a library? Could anyone tell me how to do this?

Thanks in advance for your support!

2 Likes

Hi there,

I have been using Acados successfully in a professional setting on several real robots over the past few months. From my perspective, your question does not necessarily seem to be related to Acados itself. It rather seems related to how you want to structure your project in terms of 3rd party libraries (Acados) and auto-generated code. If you would like to write a robot control framework, that is an important design question that you should answer for yourself depending on your requirements.

For reference, we’ve been using ROS and CMake/catkin: What worked well for us is to add Acados as an external project via CMake (ExternalProject — CMake 3.27.6 Documentation). In the CMakeLists.txt of the package that contains the Acados-generated code, we add a custom target (add_custom_target — CMake 3.27.6 Documentation) and execute the Python script that generates all the C-code via the COMMAND flag of add_custom_target. That way, we always generate the code on the fly (e.g., in continuous integration, etc.) without needing to check in the generated code into our versioning system.

That is just what worked for our particular setting. If you want more examples, you can also check out other projects that make use of Acados: Other Projects that feature acados — acados documentation
Files · main · Intelligent Control Systems / CRS · GitLab
GitHub - commaai/openpilot: openpilot is an open source driver assistance system. openpilot performs the functions of Automated Lane Centering and Adaptive Cruise Control for 250+ supported car makes and models.

Hope that helps :slight_smile:

Hey great question… I’m just trying to port a solver and using some approaches you’ve mentioned. I’m using ROS2 in colcon workspace. I’m using the following CMakeLists.txt to run my py code to generate the solver in the cmake_binary_directory and install the solver in my install/lib and install/include/{$project_name}/solver/ directories…
e.g.


cmake_minimum_required(VERSION 3.8)
message("Generate Model OCP")
file(GLOB ACADOS_FILES "*.py")

file(COPY ${ACADOS_FILES} DESTINATION ${CMAKE_CURRENT_BINARY_DIR})

set(CUR_DIRECTORY_NAME model_control)
set(GENERATOR model_ocp.py)

add_custom_target(acados_model ALL
    COMMAND python ${CMAKE_CURRENT_BINARY_DIR}/${GENERATOR}
    COMMENT "Build ${CMAKE_CURRENT_BINARY_DIR}/${GENERATOR}"

    )

install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/c_generated_code/
    DESTINATION include/${PROJECT_NAME}/${CUR_DIRECTORY_NAME}/
    FILES_MATCHING PATTERN "*.h")


install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/c_generated_code/
    DESTINATION lib 
    FILES_MATCHING PATTERN "*.so")

Thats working fine all the generated files are where they should be, but what are the dependencies to access the c interface for the system? acadosConfig.cmake from the installation has dependecies on gFortran and OpenBlas. Are they necessary?

The c interface documentation mentions that only the headers in interfaces/acados_c/ are required but they aren’t installed in the installation directory.

How do you use acados? Via the python interface or the cinterface.?

Hello,

Thanks a lot for replying! I followed the documentation you supposed and it works!

Thank you and best wishes for you!

Hi there,

I’m using C++ calling the C code that is generated by python interface, which works fine with the method that the first answer mentioned above. For your problem, as far as I know, you might write a ROS node to use python code directly, such as a publisher so as to communicate with your other ROS nodes, instead of using C interface again to use your solver in your project. That’s what I think is the most convenient way to use acados in a project. If you still want to use the C code that is generated by python interface, you can see those below as an example:

I used hpipm in my solver, so I wrote this in CMakeList:

include acados

include_directories(“./Controllers/nmpc_acados/acados_cpp_demo/include/blasfeo/include”)
include_directories(“./Controllers/nmpc_acados/acados_cpp_demo/include/hpipm/include”)
include_directories(“./Controllers/nmpc_acados/acados_cpp_demo/include/acados”)
include_directories(“./Controllers/nmpc_acados/acados_cpp_demo/include”)
include_directories(“./Controllers/nmpc_acados/acados_python_demo/c_generated_code”)

load acados libs

link_directories(“./Controllers/nmpc_acados/acados_cpp_demo/lib”)
file(GLOB ocp_solver
“./Controllers/nmpc_acados/acados_python_demo/c_generated_code/acados_solver_srbd.c”
“./Controllers/nmpc_acados/acados_python_demo/c_generated_code/srbd_constraints/srbd_constr_h_fun_jac_uxt_zt.c”
“./Controllers/nmpc_acados/acados_python_demo/c_generated_code/srbd_constraints/srbd_constr_h_fun.c”
“./Controllers/nmpc_acados/acados_python_demo/c_generated_code/srbd_model/srbd_expl_vde_adj.c”
“./Controllers/nmpc_acados/acados_python_demo/c_generated_code/srbd_model/srbd_expl_ode_fun.c”
“./Controllers/nmpc_acados/acados_python_demo/c_generated_code/srbd_model/srbd_expl_vde_forw.c”
)

create libraries

add_library(ocp_shared_lib SHARED ${ocp_solver})
target_link_libraries(your_node ocp_shared_lib)

And additionally, you should also copy the files under “/include” and “/lib” folders from the ACADOS_SOURCE_DIR to your folders.

By the way, the path should be replaced with your C code path witch is generated by python interface.

Hope that helps!

Hi thanks,
Yeah the the python interface is great however I’m building some plugins in a cpp framework so they are not ideal. Though I have had a rudimentary look at the c interface and that doesn’t look too friendly either!

I was able to get acados installed at buildtime using the ExternalProject Framework as per this snipped from CMakeLists.txt:

include(ExternalProject)
ExternalProject_Add(
    acados_external
    PREFIX ${CMAKE_CURRENT_BINARY_DIR}/acados
    CMAKE_ARGS -DACADOS_INSTALL_DIR=${CMAKE_INSTALL_PREFIX}
    GIT_REPOSITORY git@github.com:acados/acados.git
    GIT_SUBMODULES
    INSTALL_COMMAND pip install -e ${CMAKE_CURRENT_BINARY_DIR}/acados/src/acados_external/interfaces/acados_template
    )

include_directories(
    ${CMAKE_INSTALL_PREFIX}/include
    ${CMAKE_INSTALL_PREFIX}/include/blasfeo/include
    ${CMAKE_INSTALL_PREFIX}/include/hpipm/include
    )

There’s a bit of non-standard include install layout, I probably should copy the blasfeo and hpipm include directories into their correct spaces but for now will do. The ExternalProject_Add is definitely the way to do this.

Hello everyone!

Can acados be applied to the STM32F427VIT6 microcontroller chip? I performed simulation verification in MATLAB API and generated c code. How to integrate it into the chip?

I hope you can help me clarify my doubts! Thanks!

@lukasfro do you have an example of your ExternalProject_Add( ) call?

I have been trying to get mine to work in different environments and established what was working for me was largely using the ACADOS_ROOT variable to access ACADOS from outside the cmake workspace.

Have you got an example that does a nice clean build and install? Currently when removing the external acados_root project, my ExternalProject_Add() creates a mess of half installed files spread throughout the build directory.

Hi @peterm,

unfortunately, I cannot share the full CMakeLists.txt with you, as it is proprietary code. The jist boils down to the following:

ExternalProject_Add(
  acados_src
  GIT_REPOSITORY https://github.com/acados/acados.git
  # Optional: Choose a specific version you want to install.
  GIT_TAG ${ACADOS_GIT_TAG}

  CMAKE_ARGS
  # Add your custom cmake arguments here
)

If you also want to install the Python interface, make use of the following CMake command: ExternalProject_Add_Step and use the COMMAND option within. This let’s you install the Python interface as pip package.

Lastly, you need to install the include directories (including the subpackages: acados, acados_c, blasfeo, hpipm, osqp), the libs (libacados.so, libblasfeo.so, libhpipm.so, libosqp.so), and the python interface (acados_template) to your desired locations. We are using catkin, hence these are CATKIN_PACKAGE/GLOBAL_PYTHON_DESTINATION, CATKIN_PACKAGE/GLOBAL_INCLUDE_DETINATION, and CATKIN_PACKAGE/GLOBAL_LIB_DESTINATION.

The whole ordeal took quite some time to sort out and streamline. And even now, it’s not perfect as we expose some headers that we would ideally not like to in our stack.

Hey @lukasfro thanks for replying.

Yeah I eventually too, got a system that ‘worked’. I was working in ros2 ament workspace which is similar to a catkin workspace without the devel environment. I was initially using the externalproject_add but for simplicity just threw the acados project into my workspace directly. I found that it helps with using cmake directives such as find_package(). The ros2 ament workspace can handle native cmake projects without any package.xml being specified…

The main problem I found was getting the python acados_template to work as it had a fixed path relationships between the renderer, libs and include. It seemed to me that the interface directory had to be at the same level as the include and lib dirs. But because this was non-standard in the ros2/ros1/debian layouts, I put the interface directory into share/acados/. This broke the python interface. So my solution was to symbolically link the include and lib dirs into the share/acados/ directory. Then the paths set in the utils.py file worked.

I also found that the external dependencies includes would be placed in funnily (funny as in weird) paths such as include/hpipm/include/hpipm/*.h

My final install layout that was working ended up being

install/
     bin/
          t_renderer
     include/
          acados/
          hpipm/
              include/
                    hpipm/
     lib/
     share/
        acados/
            include/ --> ../../include/
            lib/ --> ../../lib/
            bin/ --> ../../bin/
            interfaces/
                acados_template/

then dependent packages could call their own python scripts to build ocp and sim instances and generate their libraries.

I’m going to put a bit of effort over the coming months to try and get acados into a format that is packageable via debian and the python interface via pip. Its not going to be fast, but if you’d like to contribute your thoughts its listed here
Making Install Nicer and packagable. · Issue #983 · acados/acados · GitHub

If the python script that generates the c code is executed continuously how are you publishing the optimal control sequence to the ROS network?

Hi @snDev,

the Python script to generate the solver code is run at compile time, not at runtime. I.e., every time you build your ROS package with catkin build your_mpc_package, the solver code gets re-generated. The optimal control sequence of the actual MPC problem is then published via the C++ package that uses the generated solver code.

@lukasfro Thanks! it makes sense now

I wanted to avoid having the code generation as part of my build process (I’m especially not so fond of generated headers…), so I wrote a quick C++ wrapper which dynamically loads a .so file of the generated solver. Super quick and dirty implementation and there is a ton of features missing, so feel free to make a PR.

2 Likes