|
1 |
| -# Writing storage plugins |
| 1 | +# Writing storage plugins for Rosbag2 |
2 | 2 |
|
3 |
| -There are different interfaces for storage plugins depending on your need: The general `ReadWriteStorage` and the more specific `ReadableStorage`. |
| 3 | +Storage plugins provide the actual underlying storage format for Rosbag2. |
4 | 4 |
|
5 |
| -## Writing a general plugin |
| 5 | +Plugins can implement the following APIs to provide a storage plugin, specified in `rosbag2_storage::storage_interfaces`: |
| 6 | +* `ReadOnlyInterface` which covers only reading files |
| 7 | +* `ReadWriteInterface` can both write new files and read existing ones. It is a superset of ReadOnly |
6 | 8 |
|
7 |
| -Assume you write a plugin `MyStorage` which can both save messages and read messages. |
8 |
| -Its header file could be `my_storage.hpp` and `MyStorage` will derive from `rosbag2_storage::storage_interfaces::ReadWriteInterface`. |
9 |
| -**Important:** While implementing the interface provided by `rosbag2_storage::storage_interfaces::ReadWriteInterface`, make sure that all resources such as file handles or database connections are closed or destroyed in the destructor, no additional `close` call should be necessary. |
| 9 | +## Creating a ReadWrite plugin |
| 10 | + |
| 11 | +Goal: Create a plugin named `my_storage`, in package `rosbag2_storage_my_storage`, implemented by class `my_namespace::MyStorage`. |
| 12 | + |
| 13 | +The following code snippets shows the necessary pieces to provide this plugin. |
10 | 14 |
|
11 |
| -In order to find the plugin at runtime, it needs to be exported to the pluginlib. |
12 |
| -Add the following lines to `my_storage.cpp`: |
13 | 15 |
|
14 | 16 | ```
|
| 17 | +// my_storage.cpp |
| 18 | +#include "rosbag2_storage/storage_interfaces/read_write_interface.hpp" |
| 19 | +
|
| 20 | +namespace my_namespace { |
| 21 | +
|
| 22 | +class MyStorage : public rosbag2_storage::storage_interfaces::ReadWriteInterface |
| 23 | +{ |
| 24 | +public: |
| 25 | + MyStorage(); |
| 26 | + ~MyStorage() override; // IMPORTANT: All cleanup must happen in the destructor, such as closing file handles or database connections |
| 27 | +
|
| 28 | + // ReadWriteInterface's virtual overrides here |
| 29 | +}; |
| 30 | +
|
| 31 | +// Implementations |
| 32 | +
|
| 33 | +} // namespace my_namespace |
| 34 | +
|
| 35 | +// The following block exposes our class to pluginlib so that it can be discovered at runtime. |
15 | 36 | #include "pluginlib/class_list_macros.hpp"
|
16 |
| -PLUGINLIB_EXPORT_CLASS(MyStorage, rosbag2_storage::storage_interfaces::ReadWriteInterface) |
| 37 | +PLUGINLIB_EXPORT_CLASS(my_namespace::MyStorage, |
| 38 | + rosbag2_storage::storage_interfaces::ReadWriteInterface) |
17 | 39 | ```
|
18 | 40 |
|
19 |
| -Furthermore, we need some meta-information in the form of a `plugin_description.xml` file. |
| 41 | +Next, our package must provide a file named `plugin_description.xml`. |
20 | 42 | Here, it contains
|
21 | 43 |
|
22 | 44 | ```
|
23 |
| -<library path="my_storage_lib"> |
24 |
| - <class name="my_storage" type="MyStorage" base_class_type="rosbag2_storage::storage_interfaces::ReadWriteInterface"> |
| 45 | +<library path="rosbag2_storage_my_storage"> |
| 46 | + <class |
| 47 | + name="my_storage" |
| 48 | + type="MyStorage" |
| 49 | + base_class_type="rosbag2_storage::storage_interfaces::ReadWriteInterface" |
| 50 | + > |
| 51 | + <description>Rosbag2 storage plugin providing the MyStorage file format.</description> |
25 | 52 | </class>
|
26 | 53 | </library>
|
27 | 54 | ```
|
28 |
| -`my_storage_lib` is the name of the library (ament package) while `my_storage` is an identifier used by the pluginlib to load it. |
29 | 55 |
|
30 |
| -In addition, in the `CMakeLists.txt` the `plugin_description.xml` file needs to be added to the index to be found at runtime: |
| 56 | +`rosbag2_storage_my_storage` is the name of the library from `package.xml` while `my_storage` is an identifier used by the pluginlib to refer to the plugin. |
31 | 57 |
|
32 |
| -`pluginlib_export_plugin_description_file(rosbag2_storage plugin_description.xml)` |
| 58 | +Finally, the `CMakeLists.txt` must add our `plugin_description.xml` file to the ament index to be found at runtime: |
33 | 59 |
|
34 |
| -The first argument `rosbag2_storage` denotes the library we add our plugin to (this will always be `rosbag2_storage`), while the second argument is the path to the plugin description file. |
| 60 | +``` |
| 61 | +pluginlib_export_plugin_description_file(rosbag2_storage plugin_description.xml) |
| 62 | +``` |
35 | 63 |
|
36 |
| -## Writing a plugin for reading only |
| 64 | +The first argument `rosbag2_storage` denotes the library we add our plugin to (this will always be `rosbag2_storage` for this plugin type), while the second argument is the path to the plugin description file. |
37 | 65 |
|
38 |
| -When writing plugins to only provide functionality for reading, derive from `rosbag2_storage::storage_interfaces::ReadOnlyInterface`. |
| 66 | +## Creating a ReadOnly plugin |
39 | 67 |
|
40 |
| -If the read-only plugin is called `my_readonly_storage` in a library `my_storage_lib`, it will be registered using |
| 68 | +When writing plugins to only provide functionality for reading, derive your implementation class from `rosbag2_storage::storage_interfaces::ReadOnlyInterface` instead. |
| 69 | +This is the only functional difference, it will require only a subset of the interface overrides. |
41 | 70 |
|
42 | 71 | ```
|
| 72 | +// my_readonly_storage.cpp |
| 73 | +#include "rosbag2_storage/storage_interfaces/read_only_interface.hpp" |
| 74 | +
|
| 75 | +namespace my_namespace { |
| 76 | +
|
| 77 | +class MyReadOnlyStorage : public rosbag2_storage::storage_interfaces::ReadOnlyInterface |
| 78 | +{ |
| 79 | +public: |
| 80 | + MyReadOnlyStorage(); |
| 81 | + ~MyReadOnlyStorage() override; // IMPORTANT: All cleanup must happen in the destructor, such as closing file handles or database connections |
| 82 | +
|
| 83 | + // ReadOnlyInterface's virtual overrides here |
| 84 | +}; |
| 85 | +
|
| 86 | +// Implementations |
| 87 | +
|
| 88 | +} // namespace my_namespace |
| 89 | +
|
| 90 | +// The following block exposes our class to pluginlib so that it can be discovered at runtime. |
43 | 91 | #include "pluginlib/class_list_macros.hpp"
|
44 |
| -PLUGINLIB_EXPORT_CLASS(MyReadonlyStorage, rosbag2_storage::storage_interfaces::ReadOnlyInterface) |
| 92 | +PLUGINLIB_EXPORT_CLASS(my_namespace::MyReadOnlyStorage, |
| 93 | + rosbag2_storage::storage_interfaces::ReadOnlynterface) |
45 | 94 | ```
|
46 |
| -with the plugin description |
| 95 | + |
47 | 96 | ```
|
48 |
| -<library path="my_storage_lib"> |
49 |
| - <class name="my_readonly_storage" type="MyReadonlyStorage" base_class_type="rosbag2_storage::storage_interfaces::ReadOnlyInterface"> |
| 97 | +<!-- plugin_description.xml --> |
| 98 | +<library path="rosbag2_storage_my_storage"> |
| 99 | + <class |
| 100 | + name="my_readonly_storage" |
| 101 | + type="my_namespace::MyReadOnlyStorage" |
| 102 | + base_class_type="rosbag2_storage::storage_interfaces::ReadOnlyInterface" |
| 103 | + > |
| 104 | + <description>Rosbag2 storage plugin providing read functionality for MyStorage file format.</description> |
50 | 105 | </class>
|
51 | 106 | </library>
|
52 | 107 | ```
|
| 108 | + |
53 | 109 | and the usual pluginlib export in the CMakeLists:
|
54 | 110 |
|
55 |
| -`pluginlib_export_plugin_description_file(rosbag2_storage plugin_description.xml)` |
| 111 | +``` |
| 112 | +# CMakeLists.txt |
| 113 | +pluginlib_export_plugin_description_file(rosbag2_storage plugin_description.xml) |
| 114 | +``` |
| 115 | + |
| 116 | +## Providing plugin-specific configuration |
| 117 | + |
| 118 | +Some storage plugins may have configuration parameters unique to the format that you'd like to allow users to provide from the command line. |
| 119 | +Rosbag2 provides a CLI argument `--storage-config-file` which allows users to pass the path to a file. |
| 120 | +This file can contain anything, its format is specified by the storage implementation, it is passed as a path all the way to the plugin, where it may be used however desired. |
| 121 | +Plugins are recommended to document the expected format of this file so that users can write well-formatted configurations. |
| 122 | + |
| 123 | +### Extending CLI from a storage plugin |
| 124 | + |
| 125 | +Commandline arguments can be a much more convenient way to expose configuration to users than writing out a file. |
| 126 | +The `ros2bag` package, which creates the `ros2 bag` command, provides an entrypoint for plugins to extend the CLI. |
| 127 | + |
| 128 | +All a package needs to do is expose a Python setuptools entrypoint to the group `ros2bag.storage_plugin_cli_extension`, with an entrypoint keyed by the name of the storage plugin. For example, here is `setup.cfg` from `rosbag2_storage_mcap`: |
| 129 | + |
| 130 | +``` |
| 131 | +[options.entry_points] |
| 132 | +ros2bag.storage_plugin_cli_extension = |
| 133 | + mcap = ros2bag_mcap_cli |
| 134 | +``` |
| 135 | + |
| 136 | +This registers an entrypoint in group `ros2bag.storage_plugin_cli_extension`, for the plugin named `mcap`, that is implemented by a Python module called `rosbag2_mcap_cli`. |
| 137 | + |
| 138 | +The exposed entrypoint can be installed as a Python module by any method, for example via `ament_cmake_python`'s `ament_python_install_package` macro, or by having a pure-python `ament_python` package with a `setup.py`. |
| 139 | + |
| 140 | +The functions this entry point may provide: |
| 141 | + |
| 142 | +* `get_preset_profiles(): List[Tuple[str, str]]` - provide a list of string pairs containing (name, description) of _preset profiles_, or predefined configurations, for writing storage files. The first item will be used as default. Consider returning 'none' as the first profile. |
| 143 | + |
| 144 | +NOTE: For each of these lists, the string literal 'none' will be used to indicate the feature is disable/not used. |
| 145 | + |
| 146 | +NOTE: Any entry point may exclude any of the extension functions, and a warning will be printed for each extention point omitted. When the function for a list of values is not provided, or returns `None`, by default `'none'` will be provided as the only option. |
0 commit comments