Documenting your node’s interface

A node’s NoDL document already states its parameters, topics, services, and actions. The nodl_docgen extension renders that document into your package’s documentation while the docs are being built, so the interface page is generated from the same file the rest of the toolchain reads.

Nothing is pasted, so nothing goes stale. If the document changes, the page changes with it. If the document stops loading, the build reports an error at the directive, rather than publishing a page that no longer matches the node.

This page is the authoring walkthrough. For the directive’s full option surface, see the nodl_docgen package page.

1. Depend on the extension

nodl_docgen is only needed when documentation is built, which is what doc_depend means:

<doc_depend>nodl_docgen</doc_depend>

2. Enable the extension

Add it to extensions in your package’s doc/conf.py:

extensions = [
    'myst_parser',
    'nodl_docgen',
]

3. Render the document

Point the nodl-node directive at the NoDL file:

```{nodl-node} /../nodl/my_node.nodl.yaml
:title: my_node
```

The same directive in a reStructuredText page:

.. nodl-node:: /../nodl/my_node.nodl.yaml
   :title: my_node

The argument is a path, resolved like literalinclude resolves one: relative to the page, or relative to the Sphinx source root when it starts with /. NoDL files usually live outside doc/, hence the /../nodl/... above, which climbs out of a doc/ directory at the package root.

Use a path, not a nodl:// reference, for your own package’s documents. rosdoc2 builds a package’s docs from source with its dependencies installed but not the package itself, so a package’s own NoDL is not yet in the ament index while its docs are being built. A nodl://<package>/<name> reference is for documenting a node that comes from a dependency.

:title: is the heading, and is worth setting: a NoDL document carries no name of its own, because a node’s identity comes from the package that registers it.

A rendered example

This site dogfoods the directive. The document below is checked in at nodl/doc/examples/battery_monitor.nodl.yaml:

---
nodl_version: 2
description: |
  Watches a battery pack and reports its state of charge.

  Publishes a diagnostic warning while the charge is below a configurable threshold,
  and offers an action that runs a full calibration cycle.

parameters:
  publish_rate_hz:
    type: double
    default_value: 1.0
    description: How often the pack state is published.
    validation:
      gt: [0.0]
      lt_eq: [100.0]
  low_charge_fraction:
    type: double
    default_value: 0.2
    description: Charge fraction below which the monitor warns.
    validation:
      bounds: [0.0, 1.0]
  cell_count:
    type: int
    read_only: true
    description: Number of cells in the pack. Fixed by the hardware, and has no sensible default.
    validation:
      one_of: [[4, 6, 8]]

publishers:
  - name: ~/state
    type: sensor_msgs/msg/BatteryState
    qos: {history: KEEP_LAST, depth: 5, reliability: RELIABLE}
    description: Current pack state, published at the configured rate.
  - name: /diagnostics
    type: diagnostic_msgs/msg/DiagnosticArray
    qos: {history: KEEP_LAST, depth: 1, reliability: BEST_EFFORT}
    description: A warning entry while the charge is low.

subscriptions:
  - name: /power/raw_cells
    type: sensor_msgs/msg/BatteryState
    qos: {history: KEEP_LAST, depth: 10, reliability: BEST_EFFORT}
    description: Per-cell readings from the power board driver.

service_servers:
  - name: ~/reset_fuel_gauge
    type: std_srvs/srv/Trigger
    description: Clears the accumulated charge estimate.

action_servers:
  - name: ~/calibrate
    type: example_robot_msgs/action/CalibrateBattery
    description: Runs a full discharge and charge cycle to recalibrate the gauge.

Rendered by pointing the directive at that file, with :title: battery_monitor:

battery_monitor

Watches a battery pack and reports its state of charge.

Publishes a diagnostic warning while the charge is below a configurable threshold, and offers an action that runs a full calibration cycle.

Parameters

Name

Type

Default

Read-only

Description

publish_rate_hz double 1.0

How often the pack state is published.

  • must be less than or equal to 100.0

  • must be greater than 0.0

low_charge_fraction double 0.2

Charge fraction below which the monitor warns.

  • must be within bounds [0.0, 1.0]

cell_count int

yes

Number of cells in the pack. Fixed by the hardware, and has no sensible default.

  • must be one of [4, 6, 8]

Publishers

Name

Type

QoS

Description

~/state sensor_msgs/msg/BatteryState

KEEP_LAST(5), RELIABLE

Current pack state, published at the configured rate.

/diagnostics diagnostic_msgs/msg/DiagnosticArray

KEEP_LAST(1), BEST_EFFORT

A warning entry while the charge is low.

Subscriptions

Name

Type

QoS

Description

/power/raw_cells sensor_msgs/msg/BatteryState

KEEP_LAST(10), BEST_EFFORT

Per-cell readings from the power board driver.

Service Servers

Name

Type

Description

~/reset_fuel_gauge std_srvs/srv/Trigger

Clears the accumulated charge estimate.

Action Servers

Name

Type

Description

~/calibrate example_robot_msgs/action/CalibrateBattery

Runs a full discharge and charge cycle to recalibrate the gauge.

Reading the output

A few things in that rendering are worth pointing out, because they are decisions rather than a straight field dump:

  • Each interface category that has entries becomes a subsection with a table, and one that has none is left out entirely.

  • A column no row fills in is dropped. The service server above declares no QoS profile, so its table has no QoS column, while the topic tables do.

  • A QoS profile is summarized in one line, naming only what differs from the system default.

  • A parameter’s validators are read out as constraint sentences, and its default is shown as the YAML literal you would write in a parameter file. An empty default means the parameter has none and has to be set at startup, like cell_count above.

Descriptions are rendered as plain text. Write them as prose rather than as markup: they are read by every NoDL consumer, not only by Sphinx.

Next steps

  • NoDL Concepts covers what a NoDL document declares.

  • NoDL Schema reference is the field-by-field reference for writing one.

  • The nodl_docgen page documents the directive’s argument forms, options, include merging, failure behavior, and the summary core underneath it.