Examples¶
This page is the runtime view of the library. Use it once the lifecycle vocabulary is familiar and you want to see how the contract feels in actual nodes and components.
Each example shows where ROS resources are created and what stays dormant until activation.
Activation opens message flow, callbacks, or service handling depending on the component type.
Observe the steady-state behavior you actually care about: publish, subscribe, tick, call, relay.
Deactivate and cleanup reveal what remains allocated, what is gated, and what disappears from the graph.
The final comparison point is always explicit teardown, never implicit destruction.
Read these examples before the API reference when you want the lifecycle model in one pass. They are ordered from the smallest hook surface to full component composition.
If you are new to the repository, run minimal_node.py first. It shows the native ROS 2
transition flow without adding topic resources or runtime behavior.
Across all examples, the same lifecycle contract holds: configure creates long-lived ROS
resources, activate starts behavior, deactivate stops or gates behavior, and cleanup
releases resources.
The companion repository also ships a scenario-driven comparison:
lifecore_ros2_examples sensor watchdog comparison.
Use it when you want the same watchdog behavior shown as plain ROS 2, classic lifecycle, and lifecore_ros2. That README includes the shared sensor publisher command, the commands to run each variant, and the expected /sensor/status and log signals.
Example map¶
Example |
What it demonstrates |
When to read it |
Command to run |
|---|---|---|---|
Smallest |
Start here if you want the base transition flow without topic resources. |
|
|
Lifecycle-managed state with no ROS resource ownership. |
Read after the node example to see lifecycle management applied to owned state rather than ROS resources. |
|
|
Standalone |
Read before the publisher example to understand timer gating in isolation. |
|
|
Library-owned publisher and timer composing in one node without overrides. |
Read after the timer example to see two library components wiring together. |
|
|
Activation-gated delivery with a managed subscription. |
Read when you want to see inactive message drops and subscriber ownership. |
|
|
|
Read after the subscriber example to see lifecycle-aware parameter ownership without turning the node into a config system. |
|
|
|
Read after |
|
|
|
Read when you want to see how inactive requests are answered with a default response. |
|
|
|
Read when you want to see how inactive |
|
|
Full |
Read when you need a concrete pattern for long-lived handles plus runtime behavior. |
|
|
Three sibling components transitioning together inside one node without any ordering. |
Read after the timer example to see three library components composing in one node. |
|
|
Three sibling components with explicit internal dependencies declared at registration time inside one node. |
Read after |
|
|
|
Read when you want to see how lifecycle transitions update health state and how a watchdog can read it. |
|
|
|
Read after |
|
After launching an example, drive it with ros2 lifecycle set /<node_name> configure, then
activate, deactivate, and cleanup. Each module docstring includes the expected output
for those transitions, so you can compare logs and graph changes without reading the code first.
Suggested reading path¶
Start with
minimal_node.pyfor the smallest ownership boundary.Continue with
minimal_state_component.pyto see lifecycle management applied to owned state with no ROS resources.Move to timer, publisher, and subscriber examples to see activation gating on long-lived ROS resources.
Continue with
minimal_parameter.pyto see lifecycle-aware parameter ownership and runtime update policy.Continue with
minimal_parameter_observer.pyto see remote parameter observation without turning observation into ownership or validation authority.Continue with service server and client examples to compare inbound versus outbound gating behavior.
Finish with
telemetry_publisher.py,composed_pipeline.py, andcomposed_ordered_pipeline.pyfor full lifecycle separation across multiple responsibilities.Read
composed_ordered_pipeline.pyaftertelemetry_publisher.pyto see howdependenciesdeclared atadd_component(...)impose a guaranteed transition order across independently managed components.Read
minimal_health_status.pyto see how lifecycle transitions updatehealth.levelin real time and how node-level aggregation works.Read
minimal_watchdog.pyto see aLifecycleWatchdogComponentobserving a faulting component and producing DEGRADED, ERROR, and STALE diagnostics.
Companion Comparison¶
After the bundled examples, use the sensor watchdog comparison in lifecore_ros2_examples
when you want one applied walkthrough that compares plain ROS 2, classic lifecycle, and lifecore_ros2 on the same watchdog node.
That companion README is the source of truth for its run commands, lifecycle transition commands, and expected /sensor/status and log signals.
Minimal Node¶
Source: examples/minimal_node.py
What it demonstrates¶
The smallest managed setup: one LifecycleComponentNode owns one LifecycleComponent with only
_on_configure overridden.
What to look for¶
configureruns the component hook explicitly and nothing else.The node owns the component; the component does not manage node lifetime.
activateanddeactivatesucceed with the native silent base behavior.No topics or ROS resources are created, retained, or released in this example.
Minimal State Component¶
Source: examples/minimal_state_component.py
What it demonstrates¶
A LifecycleComponent that owns lifecycle-managed state with no ROS resource ownership.
The node calls update() on the component after each activation to demonstrate external mutation.
What to look for¶
configureresets the counter to0; cleanup, shutdown, and error also reset it.deactivatepreserves the counter so accumulated state survives a deactivate / re-activate cycle.The component owns its state; the node calls
update()— the component does not push state outward.No publishers, subscriptions, or timers are created; the lifecycle contract is purely about state.
Minimal Timer¶
Source: examples/minimal_timer.py
What it demonstrates¶
A standalone LifecycleTimerComponent where the library owns the ROS timer and ticks are
routed to on_tick only while the component is active.
What to look for¶
configurecreates the ROS timer with the configured period; ticks fire but are silently dropped.activateopens the gate so each tick reacheson_tick;deactivatecloses it again without destroying the timer.cleanuplets the library cancel and destroy the timer through_release_resources.The component never owns a publisher or subscription, so the example isolates the timer contract on its own.
Minimal Publisher¶
Source: examples/minimal_publisher.py
What it demonstrates¶
A LifecyclePublisherComponent and a LifecycleTimerComponent composing in one node.
The library owns both the ROS publisher and the ROS timer; activation gating is handled
entirely without overrides.
What to look for¶
configurecreates both the publisher on/chatterand the timer.PublisherTimerreceivesPeriodicPublisherat construction and callsemit_next()inon_tick— no overrides needed.activateenables ticks and publication;deactivategates both without any_on_deactivateoverride.cleanupreleases the publisher and timer automatically through the library.
Minimal Subscriber¶
Source: examples/minimal_subscriber.py
What it demonstrates¶
A LifecycleSubscriberComponent where delivery is gated by lifecycle state instead of being
embedded directly in the node.
What to look for¶
configurecreates the subscription on/chatter.The node owns one subscriber component;
on_messageis the component contract.activateallows delivery;deactivatesilently drops messages by design.cleanupreleases the subscription and removes the topic from the subscriber graph.
Minimal Parameter¶
Source: examples/minimal_parameter.py
What it demonstrates¶
A LifecycleParameterComponent that owns component-scoped node parameters.
One parameter is writable only while active (gain), one is static
(mode), and the validation hook rejects invalid active updates.
What to look for¶
configuredeclaressensor_params.gainandsensor_params.modeon the parent node if they do not already exist.activateenables runtime writes forgainonly;moderemains read-only for the full lifecycle.validate_parameter_updateis the simplest per-parameter validation hook; the full owned-parameter pipeline ison_pre_set_owned_parameters→on_validate_owned_parameters(which callsvalidate_parameter_updateby default) →on_post_set_owned_parameters.Invalid active writes are rejected by
validate_parameter_updatewith a clear reason.cleanupclears the component’s runtime tracking and callback registration, but the ROS 2 parameters stay declared on the node and are reused on the next configure.
Minimal Parameter Observer¶
Source: examples/minimal_parameter_observer.py
What it demonstrates¶
A LifecycleParameterObserverComponent that watches a parameter owned by
another ROS 2 node. The component may read an initial value during
configure and later react to /parameter_events, but it never declares,
owns, validates, or rejects remote parameter updates.
What to look for¶
configurecreates the parameter-event subscription and optionally attempts an initial remote read.Missing remote nodes or parameters do not fail
configureby default; the watch recordsUNKNOWN_NODE,UNKNOWN_PARAMETER,UNAVAILABLE, orVALUE_AVAILABLE.watch_parameter(..., callback=...)is the per-watch reaction point;on_observed_parameter_eventis the component-wide hook when one reaction should apply to all observed parameters.activategates both watch-specific callbacks and the component-wide hook; the component still keeps the latest observed snapshot queryable while inactive.cleanupdestroys the observer-owned subscription and leaves no ownership claim on the remote parameter.
Minimal Service Server¶
Source: examples/minimal_service_server.py
What it demonstrates¶
A LifecycleServiceServerComponent where the library owns the ROS service and request
handling is gated by lifecycle state through on_service_request.
What to look for¶
configurecreates the ROS service on/trigger; the service appears inros2 service list.activateopens the gate so each request reacheson_service_request.deactivatedoes not destroy the service: incoming requests get a default-constructed response withsuccess=Falseandmessage="component inactive", and a warning is logged.cleanupdestroys the service through_release_resources.
Minimal Service Client¶
Source: examples/minimal_service_client.py
What it demonstrates¶
A LifecycleServiceClientComponent where the library owns the ROS client and outbound
call() / call_async() are gated by lifecycle state.
What to look for¶
configurecreates the ROS client for/trigger; no calls are issued yet.activatemakescall(),call_async(), andwait_for_service()safe to invoke.call(..., timeout_service=...)waits for the service to become available and raisesTimeoutErrorif it does not appear in time.deactivatemakes new calls raiseRuntimeError; futures already returned bycall_async()are not cancelled and remain owned by the application.cleanupdestroys the client through_release_resources.
Telemetry Publisher¶
Source: examples/telemetry_publisher.py
What it demonstrates¶
A publisher component with all four hooks overridden to separate ROS resources, runtime behavior, and non-ROS handles.
What to look for¶
configureacquires both the ROS publisher and the simulated sensor handle.One component owns the publisher, timer, and sensor handle as a single lifecycle unit.
activatestarts sampling;deactivatepauses it without releasing the sensor handle.cleanupreleases the sensor handle and then lets the library release the publisher.
Composed Pipeline¶
Source: examples/composed_pipeline.py
What it demonstrates¶
Three library components — a timer, a publisher, and a subscriber — inside one
LifecycleComponentNode. All three configure, activate, deactivate, and clean up
together through the native ROS 2 lifecycle. No activation overrides needed.
What to look for¶
configurecreates the publisher and subscription, and starts the timer in a ready (not firing) state.The node wires
SineTimertoSinePublisherby passing the publisher at construction — noget_componentcall needed.activateenables end-to-end flow;deactivategates all three components without any manual_on_deactivateoverride.cleanupreleases the publisher, timer, and subscription automatically through the library.
Composed Ordered Pipeline¶
Source: examples/composed_ordered_pipeline.py
What it demonstrates¶
A timer, publisher, and subscriber component wired with explicit dependencies
declared at add_component(...) so the library resolves transition order
from those declarations, not from registration order. No _on_activate or
_on_deactivate overrides are needed — the library gates each component
automatically.
What to look for¶
Components are registered in a deliberately scrambled order (sink first, timer second, publisher last). Dependencies are declared at the registration site, so ordering intent is visible where the node is assembled. Publisher is configured first because both timer and sink depend on it.
SineTimerholds a direct reference toSinePublisherand callsemit_next()inon_tick— no rawcreate_timerorcreate_publishercall appears anywhere in the example.SineTimerandLoggingSinkdo not expose pass-through ordering kwargs in their constructors;dependenciesstay on the node side of the composition boundary.deactivateandcleanuppropagate in reverse dependency order: timer and sink before publisher.
Minimal Health Status¶
Source: examples/minimal_health_status.py
What it demonstrates¶
Two components — sensor and heartbeat — whose health.level progresses
through UNKNOWN → OK → DEGRADED → ERROR as lifecycle transitions are driven.
Node-level aggregation via node.health (worst-of across registered components) is
logged at each stage.
What to look for¶
configuresets both components toOK;node.healthreportsOK.activateonsensorreturnsFAILURE;health.levelbecomesDEGRADED;node.healthreflects the worst-of.cleanuponsensorraises;health.levelbecomesERRORwithlast_errorpopulated;heartbeatresets toUNKNOWNvia a clean cleanup path.node.healthalways returns the most severe level across all registered components.
Minimal Watchdog¶
Source: examples/minimal_watchdog.py
What it demonstrates¶
A LifecycleWatchdogComponent registered alongside a SensorComponent whose
_on_activate returns FAILURE (DEGRADED health). The watchdog polls
sensor.health every second and logs WARN when the level is DEGRADED, adding a
STALE WARN after the configured threshold.
What to look for¶
The watchdog has
priority=10so it activates before the sensor (priority 0). When the sensor then fails to activate, the watchdog is already polling.Each tick while active:
DEGRADEDlevel → WARN log;ERROR→ ERROR log withlast_error;OKandUNKNOWN→ silent.After
stale_thresholdseconds of persistent non-OK level, an additional WARN labelledSTALEis emitted with elapsed time and threshold.deactivatestops polling immediately;cleanupclears per-target tracking state.The watchdog never calls any lifecycle transition method — it is purely read-only.