Describe

describe produces a NoDL document from a running ROS 2 node — the “NoDL backward” workflow. It is the interpret half of a two-step split:

Observe records everything, unfiltered. Describe interprets.

nodl_observe captures a live node at full fidelity into a rosgraph_msgs/Node message (every endpoint, raw RMW QoS integers, type hashes, all auto-declared infrastructure). describe consumes that Node and emits a NoDL document: it drops observation-only noise, maps RMW integers to NoDL string enums, recovers the action type, filters framework infrastructure, and produces something that validates against the schema.

It is a pure data transform with zero graph access — given the same Node message it always produces the same document, so it can run offline against a captured observation with no ROS graph.

Usage

ros2 nodl describe NODE_NAME [--from FILE] [--no-params] [--keep-hidden]
                             [--strict] [--raw] [--timeout SEC] [--topic NAME]
                             [-o OUT.{yaml,json}]

Option

Effect

NODE_NAME

Fully-qualified name of the target node, e.g. /ns/talker.

--from FILE

Interpret a previously captured Node from .yaml/.mcap instead of observing live. No ROS graph required.

--no-params

Drop the parameters map (live: also skips remote parameter-service calls).

--keep-hidden

Skip the infrastructure filter so auto-declared endpoints/params survive (debugging).

--strict

Treat any gap (or an invalid document) as failure → nonzero exit. For CI / “is this node fully describable” checks.

--raw

Emit the raw rosgraph_msgs/Node instead of NoDL.

--timeout SEC

Live only: max time to wait for discovery and observation.

--topic NAME

Live only: latched topic the observation is published on (default /nodl/observed_node).

-o FILE

Write to FILE (format inferred from .yaml/.yml/.json) instead of stdout.

# Describe a live node to stdout
$ ros2 nodl describe /ns/talker

# Interpret a captured observation offline, writing JSON
$ ros2 nodl describe /ns/talker --from talker.mcap -o talker.json

A draft generator

describe is a best-effort draft generator, not a guaranteed-final spec. It reliably recovers every field it can infer from an observation, so in practice the output validates and is ready to commit. Where a field is genuinely unknowable from a running node, describe does not guess: it records a gap.

  • By default, gaps are reported on stderr and the verb still exits 0 — a usable draft was produced; a human finishes the unknowable parts (e.g. a node description).

  • --strict turns any gap, or a document that fails validation, into a nonzero exit.

The unknowable fields are intentionally left for the author rather than invented (see Limitations).

What gets mapped

Node identity is dropped: a NoDL document never declares its own name/namespace (Node identity). Empty interface arrays are omitted, so a minimal node yields just nodl_version: 2.

Topics — publishers & subscriptions

rosgraph_msgs/Topic

NoDL topic_endpoint

name

name (absolute name as observed)

type.name

type (type.hash is dropped — no NoDL field)

qos

qos (always present on topics — see below)

QoS

RMW integer policies are mapped to NoDL’s string enums. NoDL excludes the RMW *_UNKNOWN values, so a non-introspectable policy is handled per its requiredness:

Policy

Requiredness

UNKNOWN / unobservable →

history

required

SYSTEM_DEFAULT

reliability

required

SYSTEM_DEFAULT

durability

optional

omitted

liveliness

optional

omitted

  • depth is emitted only when history is KEEP_LAST (the schema requires it there, and it is meaningless otherwise).

  • Durations (deadline, lifespan, liveliness_lease_duration) become *_ns integers. An infinite/unset duration (or zero) is omitted — absent already means “not enforced / never expires”.

Services — servers & clients

rosgraph_msgs/Service

NoDL service_endpoint

name

name

request_type.name

type (one service type; response_type is dropped)

request_qos / response_qos

omitted — service QoS is not observable

Actions — servers & clients

NoDL models an action as one opaque endpoint (name + type); its constituent goal/result/cancel services and feedback/status topics are not re-emitted. rosgraph_msgs/Action carries no top-level type field, so describe recovers the action type from the auto-generated constituent service type — e.g. example_interfaces/action/Fibonacci_SendGoalexample_interfaces/action/Fibonacci.

Parameters

Keyed by name, from the observed descriptor and its current value:

Source

NoDL parameter_definition

descriptor type

type (int/double/string/bool + *_array)

current value

default_value (the observed value becomes the declared default)

description, read_only, additional_constraints

passed through

floating_point_range / integer_range

validation: { bounds: [from, to] }

Infrastructure filter

Every node carries machinery the framework declares for it. describe drops it so the document reflects the authored interface, matching on both the unqualified name and the type (so a user endpoint that merely reuses one of these names survives):

  • Publishers: rosout, parameter_events

  • Subscriptions: parameter_events

  • Services: the six ~/*_parameters services and ~/get_type_description

  • Parameters: use_sim_time, start_type_description_service, qos_overrides.*

Use --keep-hidden to retain them.

Limitations

describe reports what is observable; it never asserts a value it did not see. The following are intentionally absent from the output:

  • Service QoS — not introspectable at runtime (always UNKNOWN).

  • Type hashes — observation-only; no NoDL field.

  • Action QoSaction_endpoint has no QoS field; default action QoS is assumed by consumers.

  • Descriptions — node- and endpoint-level text is not observable.

  • Parameter dynamic_typing and byte-array values — no NoDL representation.

Note

Status: v2 development. The describe verb and the observation contract are not yet stable. The field-by-field normative mapping (and open design questions, such as QoS requiredness) lives with the project’s design notes.

See also