# rosrustext_rosrs

rclrs extension layer that targets tool parity (ROS-facing behavior) and user
parity (developer ergonomics) for ROS 2 features, centered on the ROS-facing
adapter and application-facing APIs.

## Extension areas and status

| Area | Tool parity | User parity |
| ---- | ---------- | ---------- |
| Lifecycle | Implemented (services/events/bond/gating) | Partial (gaps documented) |
| Parameters | Implemented (services/events) | Partial (watcher; no set-time validation hook) |
| Actions | Not implemented | Not implemented |
| Executor | Not implemented | Not implemented |

## Lifecycle parity (implemented)

* Lifecycle services: `change_state`, `get_state`, `get_available_states`,
  `get_available_transitions`
* `transition_event` publisher (one per accepted attempt, after completion)
* Optional `get_transition_graph` service (feature `transition_graph`)
* Optional `/bond` heartbeats for Nav2 compatibility (feature `bond`)
* ManagedPublisher/ManagedTimer gating while inactive (local suppression)

See `docs/adapters/ros2rust/lifecycle/parity.md` for tool vs user parity details.

## Parameters parity (implemented baseline)

* Parameter services: `get_parameters`, `get_parameter_types`, `list_parameters`,
  `describe_parameters`, `set_parameters`, `set_parameters_atomically`
* `parameter_events` publisher (emitted on successful updates only)
* `ParameterWatcher` helper for event-driven updates without polling
* No set-time validation hook (updates are observable after apply)

See `docs/adapters/ros2rust/parameters/parity.md` for tool vs user parity details.

## Known differences vs rclcpp

* `change_state` returns `success=true` once a transition is accepted; callback
  outcome is reflected in the final state and `transition_event`.

## Publish outcome visibility

Use `publish_with_outcome()` when you need to know whether a message was
suppressed due to lifecycle inactivity. `publish()` remains silent and returns
`Ok(())` either way.

```rust
use rosrustext_rosrs::lifecycle::{PublishOutcome, ManagedPublisher};

match publisher.publish_with_outcome(msg)? {
    PublishOutcome::Published => {}
    PublishOutcome::SuppressedInactive => {
        // Message was gated due to inactive lifecycle state.
    }
}
```

## Lifecycle user parity gaps (current)

* No set/replace callbacks API; `create`/`try_new` still default to no-op callbacks

## Other extensions (not implemented)

* Actions parity
* Executor extensions

## Build (ROS installed)

This crate is published on crates.io, but building requires ROS 2 and ROS message
crates available via a ROS workspace environment (Cargo patching is typical).

```bash
cargo build \
  --manifest-path crates/rosrustext_rosrs/Cargo.toml \
  --features ros2,bond,transition_graph
```

## Feature flags

* `ros2`: enable ROS-facing lifecycle services and `transition_event`
* `bond`: publish `/bond` heartbeats for Nav2 lifecycle manager compatibility
  (requires `ros2`)
* `transition_graph`: enable `GetTransitionGraph` via `rosrustext_interfaces`
  (requires `ros2`)

## Example commands (Jazzy)

```bash
ros2 lifecycle get /<node>
# Expect the current stable state (Unconfigured/Inactive/Active/Finalized)

ros2 lifecycle set /<node> configure
# success=true; a transition_event is published after completion

ros2 lifecycle set /<node> activate
# success=true; /bond heartbeats begin if `bond` is enabled
```

## Minimal usage

Recommended constructor: `LifecycleNode::create_with_callbacks`.

```rust
use rclrs::{Context, CreateBasicExecutor, SpinOptions};
use rosrustext_rosrs::lifecycle::{CallbackResult, LifecycleCallbacksWithNode, LifecycleNode};
use rosrustext_rosrs::State;

struct Callbacks;

impl LifecycleCallbacksWithNode for Callbacks {
    fn on_configure(&mut self, node: &LifecycleNode, _state: &State) -> CallbackResult {
        let _ = node.name();
        CallbackResult::Success
    }
    fn on_activate(&mut self, _node: &LifecycleNode, _state: &State) -> CallbackResult { CallbackResult::Success }
    fn on_deactivate(&mut self, _node: &LifecycleNode, _state: &State) -> CallbackResult { CallbackResult::Success }
    fn on_cleanup(&mut self, _node: &LifecycleNode, _state: &State) -> CallbackResult { CallbackResult::Success }
    fn on_shutdown(&mut self, _node: &LifecycleNode, _state: &State) -> CallbackResult { CallbackResult::Success }
    fn on_error(&mut self, _node: &LifecycleNode, _state: &State) -> CallbackResult { CallbackResult::Success }
}

fn main() -> rosrustext_rosrs::Result<()> {
    let context = Context::default();
    let mut executor = context.create_basic_executor();

    let _lifecycle = LifecycleNode::create_with_callbacks(&executor, "lifecycle_demo", Box::new(Callbacks))?;
    executor.spin(SpinOptions::default());
    Ok(())
}
```
