Sprint 4 - Lifecycle comparison example¶
Objective. Prove the value proposition with one concrete example before adding more library surface.
Deliverable. A reader can compare plain ROS 2, classic ROS 2 lifecycle, and
lifecore_ros2 in the same sensor watchdog scenario.
—
Scope¶
Create lifecore_ros2_examples/examples/lifecycle_comparison/ in the
companion examples repository with three implementations of the same node:
plain ROS 2 node
classic ROS 2 lifecycle node
lifecore_ros2component-oriented lifecycle node
The scenario should include:
one subscriber receiving sensor samples
one publisher emitting watchdog/status output
one timer checking freshness
activation behavior that is visible in logs or tests
cleanup behavior that releases ROS resources explicitly
The scenario is named sensor_watchdog. It receives sensor values on
/sensor/value with std_msgs/msg/Float64 and publishes watchdog status on
/sensor/status with std_msgs/msg/String. Status changes should be visible
both on the status topic and in logs.
Target tree¶
lifecore_ros2_examples/
`-- examples/
`-- lifecycle_comparison/
|-- README.md
|-- sensor_value_publisher_node.py
|-- ros2_plain/
| `-- sensor_watchdog_node.py
|-- ros2_lifecycle_classic/
| `-- sensor_watchdog_lifecycle_node.py
`-- lifecore_ros2/
`-- sensor_watchdog_lifecore_node.py
Decisions already made¶
The comparison belongs in the companion examples repository because it is an applied, scenario-driven example rather than a minimal library primitive.
The first scenario is a single-node sensor watchdog, because it exercises the lifecycle boundary without requiring domain-specific dependencies.
The example must compare structure and lifecycle behavior, not performance or launch orchestration.
Sprint decisions¶
Use
std_msgs/msg/Float64for sensor samples on/sensor/value.Use
std_msgs/msg/Stringfor watchdog status on/sensor/status.Emit watchdog status through both topic publication and logs.
Keep each implementation autonomous. Do not use shared helper code between the three variants, because the point of the example is to compare structure.
Add one minimal plain ROS 2 sensor publisher node as a shared runtime stimulus for all three watchdog variants. This node is not shared implementation code; it only publishes
std_msgs/msg/Float64samples on/sensor/valueso the examples can be run and tested consistently.Use runtime ROS 2 tests where lifecycle behavior depends on real publishers, subscribers, timers, spin, transitions, and cleanup.
Lifecycle behavior contract¶
Configure:
Plain ROS 2 has no lifecycle configure transition. It creates the subscriber, publisher, and timer immediately in
__init__.Classic lifecycle creates the subscriber, lifecycle publisher, and timer in
on_configure.lifecore_ros2creates resources through the component-oriented lifecycle model during configure.
Activate:
Plain ROS 2 starts active immediately and logs that behavior explicitly.
Classic lifecycle activates its publisher and allows watchdog status output.
lifecore_ros2activates the relevant components; subscriber handling, timer checks, and status publication become effective only after activation.
Deactivate:
Plain ROS 2 has no native deactivation path.
Classic lifecycle deactivates publication and gates watchdog behavior while inactive.
lifecore_ros2gates subscriber handling, timer checks, and status publication while inactive.
Cleanup:
Classic lifecycle explicitly destroys or releases the subscriber, publisher, and timer created during configure.
lifecore_ros2releases the same resources through component cleanup.Plain ROS 2 relies on
destroy_node()during shutdown.
Shutdown:
All variants should terminate cleanly without leaving active timers, publishers, or subscriptions behind.
Lifecycle variants should not add recovery or orchestration behavior.
Error:
Keep error handling minimal and observable with clear logs.
Do not introduce custom state machines, retry loops, or recovery flows.
Execution plan¶
[x] Step 1 - Create the
lifecore_ros2_examples/examples/lifecycle_comparison/tree with a README and autonomous directories for the three variants.[x] Step 2 - Implement the plain ROS 2 version first, adapting the draft
SensorWatchdogNodeso it publishesWAITING_FOR_FIRST_SAMPLE,OK, andSTALEstatuses on a timer and logs the same status transitions.[x] Step 3 - Implement a minimal plain ROS 2 sensor publisher node that emits
std_msgs/msg/Float64samples on/sensor/value. This node is shared as an external runtime driver for the three comparison variants.[x] Step 4 - Implement the classic lifecycle version with native
rclpylifecycle semantics, keeping resource creation in configure, publication activation in activate, runtime gating in deactivate, and explicit release in cleanup.[x] Step 5 - Implement the
lifecore_ros2version usingLifecycleComponentNodeand component-oriented lifecycle behavior. The node should show the same runtime contract with less lifecycle plumbing in the application node.[x] Step 6 - Add tests that smoke import all example modules and exercise the runtime behavior that matters: status publication, activation gating, deactivation gating, and cleanup behavior where it is observable without fragile timing.
[x] Step 7 - Update documentation links after the example is real, including commands to run each variant and expected log or topic signals.
Acceptance criteria¶
The three example modules exist and can be imported.
The shared plain ROS 2 sensor publisher node exists, can be imported, and publishes
std_msgs/msg/Float64samples on/sensor/value.The three variants use
/sensor/value,/sensor/status,std_msgs/msg/Float64, andstd_msgs/msg/Stringconsistently.Each variant is autonomous and readable without shared helper modules.
The plain ROS 2 variant starts receiving, checking, and publishing immediately after node startup.
The classic lifecycle variant creates resources in configure, publishes only after activation, gates behavior after deactivation, and releases resources in cleanup.
The
lifecore_ros2variant demonstrates the same lifecycle contract through component-oriented structure.Runtime tests verify that the
lifecore_ros2variant gates subscriber, timer, and publication behavior while inactive.Watchdog status is visible on
/sensor/statusand in logs.The README explains how to run each variant, how to run the shared sensor publisher, and which status/log signals to observe.
The example does not imply that
lifecore_ros2is a launcher, lifecycle manager, or multi-node orchestrator.
—
Why this sprint comes first¶
The market need is implicit. A strong comparison example is the clearest way to show that the library is not just cleaner code, but safer lifecycle structure.
The example should make these tradeoffs visible:
plain ROS 2 is simple but easy to make active too early
classic lifecycle is controlled but verbose
lifecore_ros2keeps lifecycle semantics native while reducing internal lifecycle sprawl
—
Validation¶
[x] Smoke import all watchdog modules and the shared sensor publisher module.
[x] Run runtime ROS 2 tests for the behavior that depends on real publishers, subscribers, timers, spin, lifecycle transitions, and cleanup.
[x] Verify the
lifecore_ros2variant gates subscriber and timer behavior while inactive.[x] Verify the classic lifecycle variant publishes only after activation and gates behavior after deactivation.
[ ] Verify the plain and classic variants remain runnable documentation, not regression mirrors.
[x] Document expected commands and log signals.
Suggested targeted commands, run from lifecore_ros2_examples:
uv run ruff check examples/lifecycle_comparison tests
uv run pyright
uv run pytest tests
Escalate to the full validation gate if implementation touches
src/lifecore_ros2/ or changes existing lifecycle semantics:
uv run ruff check .
uv run pyright
uv run pytest
—
Scope boundaries¶
In scope:
one scenario, three implementations
one minimal shared plain ROS 2 sensor publisher used as an external runtime stimulus
dependency-light example suitable for the companion examples repo
README/docs link after the example is real
Out of scope:
domain-specific message packages
multi-node orchestration
launch-file tutorials
performance, latency, or CPU benchmarking
shared helper code that hides structural differences between variants
public API changes in
lifecore_ros2unless the implementation exposes a concrete library gapcore-repo implementation for this scenario
—
Success signal¶
[ ] A new reader understands the project value in under 30 seconds by reading or running the comparison.
[ ] The example supports the message “build predictable ROS 2 nodes”.
[ ] The example does not imply that
lifecore_ros2is a launcher, lifecycle manager, or multi-node orchestrator.