Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
99 commits
Select commit Hold shift + click to select a range
ea05487
Added info about kilted support
Jan 28, 2026
aaa98a8
Typo
Jan 28, 2026
9572aa1
Started adding actions support, let codex do it roughtly first
Feb 5, 2026
15f33de
Added examples and reformat
Feb 5, 2026
183a574
Added calling upstream server to demonstrate async action server
Feb 5, 2026
b2d00e9
Refactored the creation and deferred cleanup of one-off timers needed…
Feb 5, 2026
5eb78e6
polish
Feb 5, 2026
3d83062
Addind ai-sloppy session based API for actions
Feb 5, 2026
0d8913e
Fix pimpl, still garbage, will do it myself
Feb 5, 2026
b04cdbe
Cleaned up the AI slop and implemented an async await actions API, up…
Feb 5, 2026
be227d5
Compiling ..
Feb 5, 2026
901be7d
Compiling ..
Feb 5, 2026
a4df2bd
Still compiling
Feb 5, 2026
edb9581
Compiling
Feb 5, 2026
38b4c21
Compiles
Feb 5, 2026
6c15ac1
Updated example
Feb 5, 2026
83fceee
Documenting actions
Feb 5, 2026
00897da
Cleanup, reverted unrelated entities_test change that causes the test…
Feb 5, 2026
9cd4495
Trying to work around a gcc 11 bug
Feb 5, 2026
ee0360d
Fixing bugs
Feb 6, 2026
324cb53
segfault
Feb 6, 2026
b1c47d2
Fixed segfault
Feb 6, 2026
4c2bfe7
Ok, so essentially this async_get_result api is unusable, because the…
Feb 6, 2026
ca9028b
Had to implement getting the result using the options.result_callback…
Feb 6, 2026
fe5efb5
Fixed calling empty std::function bug because the promise assumed it …
Feb 6, 2026
ca719c1
Fixed some more timeout timer cancellations that I had forgotten, now…
Feb 6, 2026
272fe3d
Updated action examples
Feb 6, 2026
ed5bc15
Examples work
Feb 6, 2026
157c35b
Fixing compilation for humble
Feb 6, 2026
65181e5
removed fmax err to see more errors
Feb 6, 2026
eb496e5
Fix for humble
Feb 6, 2026
ac936ca
Simplified indexing of timeout timers: the address of the promise is …
Feb 6, 2026
2e76129
Clean up test
Feb 6, 2026
8ff3ca3
Added try catch for catching invalid state transitions and added reje…
Feb 6, 2026
b2c5d56
Compiling
Feb 6, 2026
ed53b0a
Compiling
Feb 6, 2026
cfb5a93
Compiles
Feb 6, 2026
9c32ae5
Ok, so the bug is this: The action server hasn't replied, but the cli…
Feb 6, 2026
39affc3
Changed to different API, still debugging why we get a result before …
Feb 6, 2026
f690977
Can repro with minimal example and without icey
Feb 7, 2026
5bf4f46
Finally fixed the test: You have to keep the goal handle referenced i…
Feb 7, 2026
10c8eb2
Fixing other unit tests
Feb 7, 2026
cdb8b03
Unit test compiles, but deadlocks due to more rclcpp bugs
Feb 7, 2026
3213af9
Debugging another segfault after set_goal
Feb 7, 2026
fe4e4e1
Debugged uaf
Feb 10, 2026
6275c81
Implemented async actions server, realized I do not have access to th…
Feb 10, 2026
ec97608
Will have to fix the rclcpp layer, copied for this the code from rclcpp
Feb 10, 2026
3c378d1
Clang-format the ROS code, like it's 2026 smh
Feb 10, 2026
8a97619
Changed the rclcpp implementation: Made the callbacks return void an …
Feb 10, 2026
657d6c4
Added create_server that implements a factory function that implemts …
Feb 10, 2026
dfbdd52
into Icey namespace
Feb 10, 2026
7ed7ebb
move, added lib
Feb 10, 2026
a1721ca
refactor
Feb 10, 2026
b32e305
ACTION!
Feb 10, 2026
57a7206
need main header
Feb 10, 2026
9da99cc
Hmm, I think it would require less changes if I copy most of the sour…
Feb 10, 2026
09d60c3
Added remaining files
Feb 10, 2026
ccaf435
Compiling
Feb 10, 2026
d006e44
Compiling
Feb 10, 2026
d948261
Compiling
Feb 10, 2026
febba08
Working towards the server async callback api
Feb 10, 2026
52fffb8
Working on action server tests
Feb 10, 2026
76b6394
Massage
Feb 10, 2026
3b9320d
links
Feb 11, 2026
47eb2c6
Clang format
Feb 11, 2026
d2b0913
Fixed action server test
Feb 11, 2026
bdf1c36
Reformat, implemented async handle cancel
Feb 11, 2026
781bb7b
Debbugging why action server won't execute
Feb 11, 2026
9048d20
Removing linking to icey to prevent linker error smh
Feb 11, 2026
af8259d
how about not destroying the ctx
Feb 11, 2026
757b896
Coroutine dies,but only difference to service server is that we have …
Feb 11, 2026
4fec5eb
Fixed lifetime issues
Feb 11, 2026
a5003b3
Updated unit test
Feb 11, 2026
86baeac
Fixed the bugs in the rclcpp action client code as well, added cacell…
Feb 13, 2026
6c310c1
Compiling
Feb 13, 2026
fe06ba0
Fixed client to return the request id, fixed holding mutexes locked u…
Feb 13, 2026
0c4de1b
Compiles
Feb 13, 2026
5edd83f
async action work, removing rclcpp files that did not require a patch
Feb 13, 2026
4148f5c
Made adoption of the new api easier: Users do not need to use icey ve…
Feb 13, 2026
3e21456
Fixed unit test
Feb 13, 2026
2dd0a4d
smart pointer is not necessary anymore for goal handle
Feb 13, 2026
5c2be9b
Implemented proper cancellation for the last service call, the get re…
Feb 13, 2026
b565818
Handling client callback
Feb 13, 2026
3a17826
Fixed action client tests
Feb 17, 2026
2b21e85
Added action server tests
Feb 17, 2026
42b9ea1
Clean up tests
Feb 17, 2026
ff7b427
Rem debug prints
Feb 17, 2026
57cab29
workaround for flaky graph change asserts
Feb 17, 2026
cbb814b
Humble compat
Feb 17, 2026
283e059
Kilted compat
Feb 17, 2026
c79d257
Updating readme, copyring, examples
Feb 18, 2026
8d584d8
Updated examples
Feb 18, 2026
0ef98bf
Goal acceptance has to be shorter than 5s so that we do not run in a …
Feb 18, 2026
7b1e77a
Fixed service client missing cleanup and changed to using the promise…
Feb 18, 2026
d7ef776
Added missing cancellations, added open robotics copyright notice, re…
Feb 18, 2026
20e28b4
Fixed doc
Feb 18, 2026
6a64718
Readme and changelog update
Feb 18, 2026
e99cf9f
Half-slop actions doc
Feb 18, 2026
afc7400
still slop
Feb 18, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## Unreleased

### Added
### Removed

- Callback-based ServiceClient::call overload
- ServiceClient::cancel because it does not cancel the timeout timer correctly

### Added

- Support for actions
- Multi-threaded executor support for the async/await API

### Fixed

- Correct result type implementation
- Missing request cleanup in ServiceClientImpl::our_to_real_req_id_
- Added missing cancellation of timeout timer in Promise destruction

## 0.3.0

### Fixed

- This release enables to use nested coroutines and fixes a coroutine state memory leak issue.
- enables nested coroutines and with it fixes coroutine state memory leak

## 0.2.0

Expand Down
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
<p align="center">
<img src="icey/doc/source/_static/icey_logo.png" alt="Project Logo" width="1000rem"/>
<img src="icey/doc/source/_static/icey_logo.png" alt="Project Logo" width="400rem"/>
</p>

---

<p align="center" height="30rem">
<img src="https://img.shields.io/github/actions/workflow/status/iv461/icey/build.yml?branch=main&job=build_and_test_ros2_kilted%20(kilted)&label=ROS%202%20Kilted&style=for-the-badge" alt="ROS 2 Humble Kilted Status" />
<img src="https://img.shields.io/github/actions/workflow/status/iv461/icey/build.yml?branch=main&job=build_and_test_ros2_jazzy%20(jazzy)&label=ROS%202%20Jazzy&style=for-the-badge" alt="ROS 2 Jazzy Build Status"/>
<img src="https://img.shields.io/github/actions/workflow/status/iv461/icey/build.yml?branch=main&job=build_and_test_ros2_humble%20(humble)&label=ROS%202%20Humble&style=for-the-badge" alt="ROS 2 Humble Build Status" />
</p>
Expand All @@ -18,7 +19,7 @@ ICEY is a new client API for modern asynchronous programming in the Robot Operat
- A consistent async/await-based API for all asynchronous operations, including service calls and TF lookup.
- Topic synchronization without boilerplate code

ICEY is fully compatible with the ROS 2 API since it is built on top of rclcpp. This allows for gradual adoption. It supports all major ROS features: parameters, subscriptions, publishers, timers, services, clients, and TF. Additionally, ICEY supports lifecycle nodes using a single API.
ICEY is fully compatible with the ROS 2 API since it is built on top of rclcpp. This allows for gradual adoption. It supports all major ROS features: parameters, subscriptions, publishers, timers, services, clients, actions and TF. Additionally, ICEY supports lifecycle nodes using a single API.
ICEY operates smoothly with the message_filters package, using it for synchronization. ICEY is also extensible, as demonstrated by its support for image transport camera subscription/publishers.

ICEY supports ROS 2 Humble and ROS 2 Jazzy.
Expand Down Expand Up @@ -194,7 +195,7 @@ icey_context_->create_subscription<geometry_msgs::PoseStamped>("ego_pose")
# Dependencies:

- C++20
- ROS 2 Humble or Jazzy
- ROS 2 Humble, Jazzy or Kilted
- Boost (Hana, typeinfo), not required if you need async/await only
- FMT

Expand Down
13 changes: 11 additions & 2 deletions icey/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
endif()

if(CMAKE_COMPILER_IS_GNUCXX)
add_compile_options(-Werror=return-local-addr -fmax-errors=1)
add_compile_options(-Werror=return-local-addr)
endif()
if(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
add_compile_options(-Werror=return-stack-address)
Expand All @@ -24,21 +24,30 @@ include_directories(
include
)

ament_auto_add_library(icey
src/actions/client.cpp
src/actions/server.cpp
src/actions/server_goal_handle.cpp
)

if(BUILD_TESTING)
find_package(ament_cmake_gtest REQUIRED)

ament_auto_add_gtest(test_main
test/entities_test.cpp
test/entities_async_await_test.cpp
test/action_server_test.cpp
test/action_client_test.cpp
test/node_tasks_test.cpp
test/test_main.cpp
test/filters_test.cpp
test/callback_lifetime_test.cpp
test/context_test.cpp
test/promise_test.cpp
test/nested_coroutines.cpp
)
)
target_link_libraries(test_main fmt::fmt)
set_tests_properties(test_main PROPERTIES TIMEOUT 180)

endif()

Expand Down
5 changes: 4 additions & 1 deletion icey/doc/source/api_async_primitives.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@ The asynchronous primitives like `Promise` and `Stream` are the building blocks
```{doxygenclass} icey::Stream
```

```{doxygenclass} icey::PromiseBase
```{doxygenclass} icey::impl::PromiseBase
```

```{doxygenclass} icey::impl::Promise
```

```{doxygenclass} icey::Promise
Expand Down
17 changes: 12 additions & 5 deletions icey/doc/source/api_entities.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,18 @@
All the ROS entities (subscriptions, publishers, timers etc.) are wrapped so that they are either `Stream`s or provide an asynchronous interface with `icey::Promise` (service client, TF).


```{doxygenstruct} icey::ServiceClient
```

```{doxygenstruct} icey::TransformBuffer
```

```{doxygenstruct} icey::ActionClient
```

```{doxygenstruct} icey::AsyncGoalHandle
```

```{doxygenstruct} icey::ParameterStream
```

Expand All @@ -14,15 +26,10 @@ All the ROS entities (subscriptions, publishers, timers etc.) are wrapped so tha
```{doxygenstruct} icey::PublisherStream
```

```{doxygenstruct} icey::ServiceClient
```

```{doxygenstruct} icey::TransformSubscriptionStream
```

```{doxygenstruct} icey::TransformBuffer
```

```{doxygenstruct} icey::TransformPublisherStream
```

Expand Down
1 change: 1 addition & 0 deletions icey/doc/source/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ async_flow
coroutine_basics
using_services
using_tf
using_actions
lifecycle_nodes
```

Expand Down
116 changes: 116 additions & 0 deletions icey/doc/source/using_actions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
# Actions with async/await

ICEY wraps `rclcpp_action` with coroutine-friendly APIs so goal flow stays linear instead of callback-driven.

References:
- `icey_examples/src/action_client_async_await.cpp`
- `icey_examples/src/action_server_async_await.cpp`

## Client API in three operations

Create client once:

```c++
auto ctx = std::make_shared<icey::ContextAsyncAwait>(node.get());
auto client = ctx->create_action_client<Fibonacci>("/icey_test_action_fibonacci");
```

### 1) Send goal (`send_goal`)

```c++
Fibonacci::Goal goal;
goal.order = 7;

auto maybe_handle = co_await client.send_goal(
goal,
5s, // acceptance timeout
[node](auto, auto) { RCLCPP_INFO(node->get_logger(), "feedback"); });

if (maybe_handle.has_error()) {
// e.g. "TIMEOUT", "GOAL REJECTED"
co_return;
}

auto handle = maybe_handle.value();
```

What you get:
- `icey::Result<icey::AsyncGoalHandle<ActionT>, std::string>`
- explicit timeout for acceptance

### 2) Await result (`result`)

```c++
auto maybe_result = co_await handle.result(20s); // result timeout
if (maybe_result.has_error()) {
// e.g. "RESULT TIMEOUT", "FAILED"
co_return;
}

const auto &wrapped = maybe_result.value();
if (wrapped.code == rclcpp_action::ResultCode::SUCCEEDED) {
// wrapped.result is ActionT::Result
}
```

What you get:
- `icey::Result<typename ClientGoalHandle::WrappedResult, std::string>`
- result timeout and cleanup handled by ICEY

### 3) Cancel goal (`cancel`)

```c++
auto maybe_cancel = co_await handle.cancel(2s);
if (maybe_cancel.has_error()) {
// timeout / invalid-handle style errors
co_return;
}
```

What you get:
- `icey::Result<CancelResponse::SharedPtr, std::string>`

Important:
- `cancel(...)` must be awaited immediately (do not fire-and-forget it).

## Server API

`create_action_server` accepts synchronous or coroutine callbacks for `handle_goal`, `handle_cancel` and `handle_accepted `.

```c++
auto handle_goal = [](const rclcpp_action::GoalUUID &, std::shared_ptr<const Fibonacci::Goal>)
-> icey::Promise<rclcpp_action::GoalResponse> {
// can await service calls, timers, etc.
co_return rclcpp_action::GoalResponse::ACCEPT_AND_EXECUTE;
};

auto handle_cancel = [](std::shared_ptr<ServerGoalHandleFibonacci>) {
return rclcpp_action::CancelResponse::REJECT;
};

auto handle_accepted = [](std::shared_ptr<ServerGoalHandleFibonacci> gh) {
gh->succeed(std::make_shared<Fibonacci::Result>());
};

ctx->create_action_server<Fibonacci>("/icey_test_action_fibonacci",
handle_goal,
handle_cancel,
handle_accepted);
```

## What this enables (beyond syntactic sugar)

1. Compose action stages directly:
`send_goal -> result -> conditional cancel/retry` in one coroutine.
2. Perform async prechecks in server callbacks:
await service calls or timers before accepting/rejecting a goal.
3. Keep timeout policy close to business logic:
acceptance/result/cancel each have separate timeouts.
4. Write deterministic control flow:
less shared mutable callback state, fewer manually managed request IDs.

## Plain ROS 2 vs ICEY

Plain `rclcpp_action` commonly needs manual `SendGoalOptions`, multiple callbacks, and external state to connect acceptance/result/cancel steps.

ICEY keeps the same ROS executor model (`rclcpp::spin(...)`) but lets you express those steps with typed `co_await` operations and explicit per-step timeout handling.
41 changes: 41 additions & 0 deletions icey/include/icey/action/action.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Copyright 2018 Open Source Robotics Foundation, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/** \mainpage rclcpp_action: ROS Action Client Library for C++
*
* `rclcpp_action` provides the canonical C++ API for interacting with ROS Actions.
* It consists of these main components:
*
* - Action Client
* - rclcpp_action/client.hpp
* - rclcpp_action/create_client.hpp
* - rclcpp_action/client_goal_handle.hpp
* - Action Server
* - rclcpp_action/server.hpp
* - rclcpp_action/create_server.hpp
* - rclcpp_action/server_goal_handle.hpp
*/

#pragma once

#include <csignal>
#include <icey/action/client.hpp>
#include <icey/action/create_client.hpp>
#include <icey/action/create_server.hpp>
#include <icey/action/server.hpp>
#include <icey/action/server_goal_handle.hpp>
#include <icey/action/client_goal_handle.hpp>
#include <memory>

#include "rclcpp_action/visibility_control.hpp"
Loading