Contributing#
Introduction#
If you are a member of the software subteam, you will contribute documentation at some point in your CMR career. All pull requests that add or modify functionality are required to update the relevant documentation articles, with few exceptions. This guide gives a rundown of how to contribute.
Compiling#
To compile the docs, you can run cmr makedocs in your Terminal. If you didn’t make any changes to your
C++ docs, you can run cmr makedocs --skip-doxy to skip the Doxygen compilation step, which saves a lot
of time.
Sphinx tries to save you time by only recompiling the pages that were changed. However, this may create
inconsistencies in the sidebar when you delete pages or modify tables of contents. To fix this, delete
the docs/_build directory and run cmr makedocs --skip-doxy to recompile all the documentation.
Adding Pages#
This section is broken down into two, depending on whether you’re adding a page to an existing section, or creating a new section altogether.
Adding a Page to an Existing Section#
Let’s say you’ve created a new control node, and you want to add documentation for it. All you have to do
is create a page under the “controls” directory - call it whatever you want, but make sure it’s done in
snake_case and ends with .rst. All documentation articles are written in the
reStructuredText format. Unfamiliar with
this format? Don’t worry! See the Formatting section below.
You also have to add this new file to the directory’s index.rst file. In our controls example, you would
want to use controls/index.rst. Just open up that file and add the name of your file (but not the extension)
beneath the toctree directive.
Note
You do not need to add a toctree to your own page unless you want to include subpages. You might have have noticed that this page has a “Contents” section on its right side; that’s an automatically generated map of all the headings in this file. You don’t have to do anything extra to add that to your own page!
Creating a New Documentation Section#
To create a new documentation section altogether, you must create a new directory under docs/pages. Call it
the name of your section, all lowercase, in snake_case. Next, create an index.rst file in this directory. This
will contain the introductory page of your section, which summarizes the subject of that section. For example, if
you’re creating a section for Documentation docs (like the one you’re reading right now), you might include an overview
of the documentation topic, concepts and philosophy, and so on.
After your overview, you will need to include a table of contents (TOC) so that Sphinx knows which pages are included
under this section. This is done using the .. toctree:: directive; write that on its own line, make a new line,
tab once, and type the names of the other .rst pages in your section. See Adding a Page to an Existing Section
for more info on adding other pages.
Formatting#
Is this your first experience interacting with reStructuredText? That’s alright! reStructuredText is essentially just very spicy Markdown that sort of gives you heartburn after you eat it. It’s a lot more verbose than Markdown, but it also allows us to do much more fancy things with our plaintext documents. Could you imagine writing the documentation in raw HTML? Me neither. This section is your cheatsheet for formatting.
Headings#
1Title
2=======
3
4Heading
5--------
6
7Subheading
8^^^^^^^^^^^
Text Formatting#
1.. comment
2
3**bold**
4
5*italics*
6
7``inline literal``
8
9:guilabel:`GUI Label`
10
11:math:`\text_{equation} = x^2`
Hyperlinks#
1.. Hyperlinks
2
3`Linking C++ Docs`_
4
5`External site <https://github.com>`__
6
7:doc:`Other Documentation Page <./index>`
Code Blocks#
1def some_function():
2 rover = True
3 print("This line is highlighted.")
4 print("This one isn't!")
.. code-block:: python
:linenos:
:emphasize-lines: 3
def some_function():
rover = True
print("This line is highlighted.")
print("This one isn't!")
This is probably the most common way that you’ll use code blocks, but it’s possible to include literal blocks as well. See this external documentation page for more information.
Images#
.. image:: ./images/duckling.jpg
:align: center
Image paths are referenced relative to the file you’re editing. You can also use URL’s instead of downloading the images to the documentation, but it’s strongly preferred that you download them, because we can’t control if that image gets removed eventually.
You can also alter the alignment. It’s preferred that you center images, but it is entirely up to your discretion. Do what looks best!
Admonitions#
Attention
Directives at large.
Note
You can have multiple lines.
As many lines as you want. But keep these short!
.. Attention:: Directives at large.
.. note::
You can have multiple lines.
As many lines as you want. But keep these short!
These are all the types of admonitions: attention, caution, danger, error, hint, important,
note, seealso, tip, todo, warning.
You can also have admonitions with custom titles.
Surgeon General’s Warning
Smoking Causes Lung Cancer, Heart Disease, Emphysema, and May Complicate Pregnancy.
.. admonition:: Surgeon General's Warning
Smoking Causes Lung Cancer, Heart Disease, Emphysema, and May Complicate Pregnancy.
Linking C++ Docs#
We use an extension called Breathe, which lets us display the Doxygen docs from your beautifully commented and documented C++ code (right…? RIGHT???) in these here docs. These sort of documentation embeddings will be most heavily used in the Deep Dive sections of the docs, since referencing specific functions and classes during a commentary is useful.
This page includes some common examples, but for advanced usage, I implore you to check out the official Breathe directives guide <https://breathe.readthedocs.io/en/latest/directives.html>.
Embedding Entire Classes#
-
template<typename Fuser>
class cmr::FusionNode : public cmr::RosNodeProvider# Represents: a node which fuses inputs from several sensors into one message.
- Template Parameters
Fuser –
the class which does the fusing. Must be trivially_constructible. Must provide: Fuser::MessageType: a using statement defining the type of message it produces bool processData(Fuser::MessageType& result, const
std::vector<std::vector<double>>& readings, Logger& logger):
A function which processes the data from readings into message. This function will always receive data of the correct length. Returns true if everything is OK, returns false if there is some error processing the data. Upon a false, will disable this node. bool parseConfig(const YAML::Node& node, Logger& logger): Parses the configuration file for this node. See parseConfig for a description of the format.
Public Functions
-
inline virtual bool parseConfig(const YAML::Node &node)#
The confiruation should look like: frequency: 500 # hz error_count: 10 # defaults to 10 - if a sensor fails to read for 10 cycles disable the stream. 0 means never disable.
inputs:
[5, 3] # sensor ID to stream from, expected size of the sensor reading
[6, 4] any other stuff
-
inline virtual std::shared_ptr<DependencySet> requiredDependencies()#
Calculates the dependencies which this node will require. This method will be called after parseConfig, allowing a node to dynamically determine what dependencies is wants. This method will be called before handleDependencies to verify that all dependencies are met.
- Returns
the set of dependency names which this node requires.
-
inline virtual bool setUpNodeFunctions(ros::NodeHandle &nh, const DependencyMap &deps)#
Effect: creates all needed publishes, subscribers, and services for this node.
- Parameters
handle – the handle to use to create the needed ROS constructs.
deps – the map from dependency name to the node driver which satisfies the given dependency. The set of keys in the map will be equal to the set returned by requiredDependencies.
- Returns
true if the node was able to set itself up, false otherwise.
-
inline virtual bool ok()#
Determines if the node is OK. This method will be called
- Returns
true if the node is working properly. false if the node wishes to be disabled.
-
inline virtual bool prepareForDisable()#
Effect: prepares this node to be disabled. After this method completes, no more methods will be called on this object, except for the desctructor. No ROS callbacks will be called either. This method may try to set a safe state before being disabled, but has no gaurentee as to the state of any of its dependencies. The dependencies might be enabled, or disabled, or may change state during this method.
- Returns
true if this node was able to disable cleanly and set a safe state. False otherwise.
.. doxygenclass:: cmr::FusionNode
:members:
This will include all the public members of the class you specify; make sure you include the namespace
(in this case, cmr::...). If you want to include private members, specify the :private-members
option (this is analogous for protected members).
Embedding a Single Function#
Adding a single function is easy, but a little trickier than the Breathe documentation lets on. It recommends doing something like this:
.. doxygenfunction:: {function name}
This would work, but it requires that the function name is unique. This is a challenge for us, because we have a lot of cases where a function overrides or overloads another. So, we prefer to do it this way:
-
void addCallback(const ros::CallbackInterfacePtr &callback, uint64_t removal_id = 0)#
Adds a callback to this queue if it is enabled
.. doxygenclass:: cmr::CascadingCallbackQueue
:members: addCallback
:members-only:
We are technically referencing the whole class, but passing the :members-only option will only include the members
and not the actual class information that we saw in the previous section.
There are directives for including define statements, enums, and more. Check out the official Breathe directives guide <https://breathe.readthedocs.io/en/latest/directives.html>.
Further Reading#
While this guide includes the most common formatting and directives you’ll encounter on your CMR documentation journeys, it’s far from exhaustive. We are using the Furo theme for Sphinx, which provides an excellent Kitchen Sink page with all the possible stylings and directives you might imagine using, including sidebars, tables, and more. You can reference that page if you want to perform some more advanced styling; click the “Show Source” link at the bottom to see how to write that stuff in RST.
Next, check out the Python and C++ Docs for information and best practices on documenting your Python and C++ code.
Or, view the documentation deep dive.