Minimal Dynamic Graph Manager Tutorial#
This section of the documentation provides a minimal example of the usage of Dynamic Graph Manager (DGM).
Sturcture of the Minimal DGM Tutorial#
As we can see from the above figure, when using DGM you would have four components:
the “fake robot” in this tutorial takes the place of the actual robot when using
dgm_franka;the
dg_minimal_tutorialcommunicates between the robot and DGM by reading sensor data from the robot and setting contol commands to the robot;the shared memory is a communication pipeline between the Python API and the C++ based
dg_minimal_tutorial;the Python API is where the controller is implemented.
Installation#
The source code of dg_minimal_tutorial can be found at this link. To install the package, first clone the package and build it
git clone https://github.com/BolunDai0216/dg_minimal_tutorial.git
cd dg_minimal_tutorial
mkdir build && cd build
cmake ..
cmake --build .
cmake --install . --prefix "<dir-to-dg_minimal_tutorial>/dg_minimal_tutorial/install"
Fill <dir-to-dg_minimal_tutorial> with the absolute path of the directory dg_minimal_tutorial is in.
Run Minimal Example#
In the root directory of dg_minimal_tutorial run the generated executable
./install/lib/dg_minimal_tutorial/dg_minimal_tutorial_main
Add the location of dynamic_graph_manager_cpp_bindings to PYTHONPATH, if following the installation instruction of dgm_franka you should be able to add it to PYTHONPATH using the command
export PYTHONPATH=$PYTHONPATH:~/dgm-ws/install/dynamic_graph_manager/lib/python3.8/site-packages
Open another terminal, and run the Python test file
python3 python/test.py
you should see an output similar to
Loading parameters from /home/mocap/Documents/dg_minimal_tutorial/config/dg_minimal_tutorial.yaml
Seting up cond_var
Value in shared memory is: [1. 2.]
in the Python console. If this works, then it indicates everything is working properly.
How this is created?#
This is not magic, so let’s see step by step how we can create the structure showed in the above figure.
dg_minimal_tutorial#
The class that passes the sensor data and control command between the shared memory and the “Fake Robot” is defined here. From the header file, we can see that it is overwritting three functions of the parent class dynamic_graph_manager::DynamicGraphManager():
void DGMinimalTutorial::initialize_hardware_communication_process() {
std::cout << "Initialized Hardware Communication Process" << std::endl;
}
void DGMinimalTutorial::get_sensors_to_map(dynamic_graph_manager::VectorDGMap& map) {
map["value"](0) = 1;
map["value"](1) = 2;
}
void DGMinimalTutorial::set_motor_controls_from_map(const dynamic_graph_manager::VectorDGMap& map) {
dynamicgraph::Vector value = map.at("new_value");
std::cout << "New value is: (" << value[0] << ", " << value[1] << ")" << std::endl;
}
The initialize_hardware_communication_process function is only called once when we first run the executable. Then, as define in control_period (it is defined in nanoseconds, 1e6 ns = 1 ms), we the functions get_sensors_to_map and set_motor_controls_from_map are ran at 1000 Hz. At each iteration, get_sensors_to_map sets the value of value in the shared memory to [1, 2], and set_motor_controls_from_map reads the value of new_value in the shared memory and prints it to the console.
dg_minimal_tutorial_main#
This file creates a ROS node that: (1) reads from the yaml file; (2) creates the shared memory; (3) runs the loop between dg_minimal_tutorial and the shared memory; (4) reads sensor data and sets control commands.
int main(int /*argc*/, char* /*argv*/[]) {
std::string yaml_params_file = std::string(CONFIG_PATH);
std::cout << "Loading parameters from " << yaml_params_file << std::endl;
YAML::Node param = YAML::LoadFile(yaml_params_file);
dg_minimal_tutorial::DGMinimalTutorial dgm;
dgm.initialize(param);
dgm.run();
// Wait until ROS is shutdown
std::cout << "Wait for shutdown, press CTRL+C to close." << std::endl;
dynamic_graph_manager::ros_spin();
dynamic_graph_manager::ros_shutdown();
}
The path of the config file CONFIG_PATH is set within CMakeLists.txt. In dg_minimal_tutorial, the CONFIG_PATH variable is set by the following line:
target_compile_definitions(dg_minimal_tutorial_main PUBLIC CONFIG_PATH="${PROJECT_SOURCE_DIR}/install/${CONFIG_DESTINATION}/dg_minimal_tutorial.yaml")
An executable of this file is generated when building dg_minimal_tutorial, which should be ran before running the Python API.
Python API#
In Python, we first need to
import dynamic_graph_manager_cpp_bindings
since it is likely not in a standard location, before importing it, we would first need to enable Python to find the shared library file (.so file) that contains dynamic_graph_manager_cpp_bindings. We do this by setting the environment variable PYTHONPATH to the directory that contains the .so file:
export PYTHONPATH=$PYTHONPATH:~/dgm-ws/install/dynamic_graph_manager/lib/python3.8/site-packages
Then, we can read from and write to the shared memory
config_file = ("<dir-to-dg_minimal_tutorial>/dg_minimal_tutorial/config/dg_minimal_tutorial.yaml")
head = dynamic_graph_manager_cpp_bindings.DGMHead(config_file)
head.read()
value = head.get_sensor("value")
print(f"Value in shared memory is: {value}")
head.set_control("new_value", np.array([1.0, 2.0]))
head.write()
The line
head = dynamic_graph_manager_cpp_bindings.DGMHead(config_file)
creates a dynamic graph head (dgh) that is used to communicate with the shared memory. The lines
head.read()
value = head.get_sensor("value")
print(f"Value in shared memory is: {value}")
reads from the shared memory using the dgh and prints the value of value in the shared memory. In this case, it should print Value in shared memory is: [1. 2.]. Then, we can write to the shared memory using the lines
head.set_control("new_value", np.array([1.0, 2.0]))
head.write()
We are specifying the part of the shared memory we are writing to is new_value and the value of new_value we want to set is [1.0, 2.0], we use a NumPy array to pass the value.
Next Steps#
Now that you have a basic understanding of DGM, you should move on and try it on FR3.